Comparing version 3.0.0-alpha.2 to 3.0.0-alpha.3
@@ -348,3 +348,3 @@ /** | ||
function prepareList(slice) { | ||
const length = slice.length - 1 // Skip close. | ||
const length = slice.length | ||
let index = 0 // Skip open. | ||
@@ -606,3 +606,2 @@ let containerBalance = 0 | ||
tag('>') | ||
setData('fencedCodeInside', true) | ||
setData('slurpOneLineEnding', true) | ||
@@ -623,4 +622,24 @@ } | ||
const count = getData('fencesCount') | ||
// Send an extra line feed if we saw data. | ||
if (getData('flowCodeSeenData')) lineEndingIfNeeded() | ||
// One special case is if we are inside a container, and the fenced code was | ||
// not closed (meaning it runs to the end). | ||
// In that case, the following line ending, is considered *outside* the | ||
// fenced code and block quote by micromark, but CM wants to treat that | ||
// ending as part of the code. | ||
if ( | ||
count !== undefined && | ||
count < 2 && | ||
// @ts-expect-error `tightStack` is always set. | ||
data.tightStack.length > 0 && | ||
!getData('lastWasTag') | ||
) { | ||
lineEnding() | ||
} | ||
// But in most cases, it’s simpler: when we’ve seen some data, emit an extra | ||
// line ending when needed. | ||
if (getData('flowCodeSeenData')) { | ||
lineEndingIfNeeded() | ||
} | ||
tag('</code></pre>') | ||
@@ -627,0 +646,0 @@ if (count !== undefined && count < 2) lineEndingIfNeeded() |
@@ -79,4 +79,3 @@ /** | ||
check: constructFactory(onsuccessfulcheck), | ||
interrupt: constructFactory(onsuccessfulcheck, {interrupt: true}), | ||
lazy: constructFactory(onsuccessfulcheck, {lazy: true}) | ||
interrupt: constructFactory(onsuccessfulcheck, {interrupt: true}) | ||
} | ||
@@ -136,3 +135,2 @@ | ||
// Otherwise, resolve, and exit. | ||
// Note: TS can’t handle recursive types. | ||
context.events = resolveAll(resolveAllConstructs, context.events, context) | ||
@@ -238,3 +236,3 @@ | ||
undefined, | ||
'expected code to not have been consumed' | ||
'expected code to not have been consumed: this might be because `return x(code)` instead of `return x` was used' | ||
) | ||
@@ -456,3 +454,6 @@ assert( | ||
return construct.tokenize.call( | ||
fields ? Object.assign({}, context, fields) : context, | ||
// If we do have fields, create an object w/ `context` as its | ||
// prototype. | ||
// This allows a “live binding”, which is needed for `interrupt`. | ||
fields ? Object.assign(Object.create(context), fields) : context, | ||
effects, | ||
@@ -459,0 +460,0 @@ ok, |
@@ -10,9 +10,4 @@ /** @type {InitialConstruct} */ | ||
export type State = import('micromark-util-types').State | ||
export type Point = import('micromark-util-types').Point | ||
export type StackState = Record<string, unknown> | ||
export type StackItem = [Construct, StackState] | ||
export type Result = { | ||
flowContinue: boolean | ||
lazy: boolean | ||
continued: number | ||
flowEnd: boolean | ||
} |
@@ -9,2 +9,3 @@ /** | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-util-types').Point} Point | ||
*/ | ||
@@ -15,8 +16,5 @@ | ||
* @typedef {[Construct, StackState]} StackItem | ||
* | ||
* @typedef {{flowContinue: boolean, lazy: boolean, continued: number, flowEnd: boolean}} Result | ||
*/ | ||
import assert from 'assert' | ||
import {blankLine} from 'micromark-core-commonmark' | ||
import {factorySpace} from 'micromark-factory-space' | ||
@@ -27,2 +25,3 @@ import {markdownLineEnding} from 'micromark-util-character' | ||
import {types} from 'micromark-util-symbol/types.js' | ||
import {splice} from 'micromark-util-chunked' | ||
@@ -34,4 +33,2 @@ /** @type {InitialConstruct} */ | ||
const containerConstruct = {tokenize: tokenizeContainer} | ||
/** @type {Construct} */ | ||
const lazyFlowConstruct = {tokenize: tokenizeLazyFlow} | ||
@@ -43,7 +40,3 @@ /** @type {Initializer} */ | ||
const stack = [] | ||
/** @type {Construct} */ | ||
const inspectConstruct = {tokenize: tokenizeInspect, partial: true} | ||
let continued = 0 | ||
/** @type {Result|undefined} */ | ||
let inspectResult | ||
/** @type {TokenizeContext|undefined} */ | ||
@@ -53,2 +46,4 @@ let childFlow | ||
let childToken | ||
/** @type {number} */ | ||
let lineStartOffset | ||
@@ -59,2 +54,12 @@ return start | ||
function start(code) { | ||
// First we iterate through the open blocks, starting with the root | ||
// document, and descending through last children down to the last open | ||
// block. | ||
// Each block imposes a condition that the line must satisfy if the block is | ||
// to remain open. | ||
// For example, a block quote requires a `>` character. | ||
// A paragraph requires a non-blank line. | ||
// In this phase we may match all or just some of the open blocks. | ||
// But we cannot close unmatched blocks yet, because we may have a lazy | ||
// continuation line. | ||
if (continued < stack.length) { | ||
@@ -65,3 +70,3 @@ const item = stack[continued] | ||
item[0].continuation, | ||
'expected `contination` to be defined on container construct' | ||
'expected `continuation` to be defined on container construct' | ||
) | ||
@@ -71,7 +76,8 @@ return effects.attempt( | ||
documentContinue, | ||
documentContinued | ||
checkNewContainers | ||
)(code) | ||
} | ||
return documentContinued(code) | ||
// Done. | ||
return checkNewContainers(code) | ||
} | ||
@@ -81,2 +87,7 @@ | ||
function documentContinue(code) { | ||
assert( | ||
self.containerState, | ||
'expected `containerState` to be defined after continuation' | ||
) | ||
if (self.containerState._closeFlow) closeFlow() | ||
continued++ | ||
@@ -87,14 +98,54 @@ return start(code) | ||
/** @type {State} */ | ||
function documentContinued(code) { | ||
// If we’re in a concrete construct (such as when expecting another line of | ||
// HTML, or we resulted in lazy content), we can immediately start flow. | ||
if (inspectResult && inspectResult.flowContinue) { | ||
return flowStart(code) | ||
function checkNewContainers(code) { | ||
// Next, after consuming the continuation markers for existing blocks, we | ||
// look for new block starts (e.g. `>` for a block quote). | ||
// If we encounter a new block start, we close any blocks unmatched in | ||
// step 1 before creating the new block as a child of the last matched | ||
// block. | ||
if (continued === stack.length) { | ||
// No need to `check` whether there’s a container, of `exitContainers` | ||
// would be moot. | ||
// We can instead immediately `attempt` to parse one. | ||
if (!childFlow) { | ||
return documentContinued(code) | ||
} | ||
// If we have concrete content, such as block HTML or fenced code, | ||
// we can’t have containers “pierce” into them, so we can immediately | ||
// start. | ||
if (childFlow.currentConstruct && childFlow.currentConstruct.concrete) { | ||
return flowStart(code) | ||
} | ||
// If we do have flow, we’d be interrupting it w/ a new container. | ||
self.interrupt = true | ||
} | ||
self.interrupt = | ||
childFlow && | ||
childFlow.currentConstruct && | ||
childFlow.currentConstruct.interruptible | ||
// Check if there is a new container. | ||
self.containerState = {} | ||
return effects.check( | ||
containerConstruct, | ||
thereIsANewContainer, | ||
thereIsNoNewContainer | ||
)(code) | ||
} | ||
/** @type {State} */ | ||
function thereIsANewContainer(code) { | ||
if (childFlow) closeFlow() | ||
exitContainers(continued) | ||
return documentContinued(code) | ||
} | ||
/** @type {State} */ | ||
function thereIsNoNewContainer(code) { | ||
self.parser.lazy[self.now().line] = continued !== stack.length | ||
lineStartOffset = self.now().offset | ||
return flowStart(code) | ||
} | ||
/** @type {State} */ | ||
function documentContinued(code) { | ||
// Try new containers. | ||
self.containerState = {} | ||
return effects.attempt( | ||
@@ -117,4 +168,5 @@ containerConstruct, | ||
) | ||
continued++ | ||
stack.push([self.currentConstruct, self.containerState]) | ||
self.containerState = undefined | ||
// Try another. | ||
return documentContinued(code) | ||
@@ -126,3 +178,4 @@ } | ||
if (code === codes.eof) { | ||
exitContainers(0, true) | ||
if (childFlow) closeFlow() | ||
exitContainers(0) | ||
effects.consume(code) | ||
@@ -133,3 +186,2 @@ return | ||
childFlow = childFlow || self.parser.flow(self.now()) | ||
effects.enter(types.chunkFlow, { | ||
@@ -147,4 +199,6 @@ contentType: constants.contentTypeFlow, | ||
if (code === codes.eof) { | ||
continueFlow(effects.exit(types.chunkFlow)) | ||
return flowStart(code) | ||
writeToChild(effects.exit(types.chunkFlow), true) | ||
exitContainers(0) | ||
effects.consume(code) | ||
return | ||
} | ||
@@ -154,4 +208,7 @@ | ||
effects.consume(code) | ||
continueFlow(effects.exit(types.chunkFlow)) | ||
return effects.check(inspectConstruct, documentAfterPeek) | ||
writeToChild(effects.exit(types.chunkFlow)) | ||
// Get ready for the next line. | ||
continued = 0 | ||
self.interrupt = undefined | ||
return start | ||
} | ||
@@ -163,21 +220,113 @@ | ||
/** @type {State} */ | ||
function documentAfterPeek(code) { | ||
assert(inspectResult, 'expected `inspectResult` to be defined after peek') | ||
exitContainers(inspectResult.continued, inspectResult.flowEnd) | ||
continued = 0 | ||
return start(code) | ||
} | ||
/** | ||
* @param {Token} token | ||
* @param {boolean} [eof] | ||
* @returns {void} | ||
*/ | ||
function continueFlow(token) { | ||
function writeToChild(token, eof) { | ||
assert(childFlow, 'expected `childFlow` to be defined when continuing') | ||
const stream = self.sliceStream(token) | ||
if (eof) stream.push(null) | ||
token.previous = childToken | ||
if (childToken) childToken.next = token | ||
childToken = token | ||
assert(childFlow, 'expected `childFlow` to be defined when continuing') | ||
childFlow.lazy = inspectResult ? inspectResult.lazy : false | ||
childFlow.defineSkip(token.start) | ||
childFlow.write(self.sliceStream(token)) | ||
childFlow.write(stream) | ||
// Alright, so we just added a lazy line: | ||
// | ||
// ```markdown | ||
// > a | ||
// b. | ||
// | ||
// Or: | ||
// | ||
// > ~~~c | ||
// d | ||
// | ||
// Or: | ||
// | ||
// > | e | | ||
// f | ||
// ``` | ||
// | ||
// The construct in the second example (fenced code) does not accept lazy | ||
// lines, so it marked itself as done at the end of its first line, and | ||
// then the content construct parses `d`. | ||
// Most constructs in markdown match on the first line: if the first line | ||
// forms a construct, a non-lazy line can’t “unmake” it. | ||
// | ||
// The construct in the third example is potentially a GFM table, and | ||
// those are *weird*. | ||
// It *could* be a table, from the first line, if the following line | ||
// matches a condition. | ||
// In this case, that second line is lazy, which “unmakes” the first line | ||
// and turns the whole into one content block. | ||
// | ||
// We’ve now parsed the non-lazy and the lazy line, and can figure out | ||
// whether the lazy line started a new flow block. | ||
// If it did, we exit the current containers between the two flow blocks. | ||
if (self.parser.lazy[token.start.line]) { | ||
let index = childFlow.events.length | ||
while (index--) { | ||
if ( | ||
// The token starts before the line ending… | ||
childFlow.events[index][1].start.offset < lineStartOffset && | ||
// …and either is not ended yet… | ||
(!childFlow.events[index][1].end || | ||
// …or ends after it. | ||
childFlow.events[index][1].end.offset > lineStartOffset) | ||
) { | ||
// Exit: there’s still something open, which means it’s a lazy line | ||
// part of something. | ||
return | ||
} | ||
} | ||
const indexBeforeExits = self.events.length | ||
let indexBeforeFlow = indexBeforeExits | ||
/** @type {boolean|undefined} */ | ||
let seen | ||
/** @type {Point|undefined} */ | ||
let point | ||
// Find the previous chunk (the one before the lazy line). | ||
while (indexBeforeFlow--) { | ||
if ( | ||
self.events[indexBeforeFlow][0] === 'exit' && | ||
self.events[indexBeforeFlow][1].type === types.chunkFlow | ||
) { | ||
if (seen) { | ||
point = self.events[indexBeforeFlow][1].end | ||
break | ||
} | ||
seen = true | ||
} | ||
} | ||
assert(point, 'could not find previous flow chunk') | ||
exitContainers(continued) | ||
// Fix positions. | ||
index = indexBeforeExits | ||
while (index < self.events.length) { | ||
self.events[index][1].end = Object.assign({}, point) | ||
index++ | ||
} | ||
// Inject the exits earlier (they’re still also at the end). | ||
splice( | ||
self.events, | ||
indexBeforeFlow + 1, | ||
0, | ||
self.events.slice(indexBeforeExits) | ||
) | ||
// Discard the duplicate exits. | ||
self.events.length = index | ||
} | ||
} | ||
@@ -187,19 +336,11 @@ | ||
* @param {number} size | ||
* @param {boolean} end | ||
* @returns {void} | ||
*/ | ||
function exitContainers(size, end) { | ||
function exitContainers(size) { | ||
let index = stack.length | ||
// Close the flow. | ||
if (childFlow && end) { | ||
childFlow.write([codes.eof]) | ||
childToken = undefined | ||
childFlow = undefined | ||
} | ||
// Exit open containers. | ||
while (index-- > size) { | ||
self.containerState = stack[index][1] | ||
const entry = stack[index] | ||
self.containerState = entry[1] | ||
assert( | ||
@@ -215,131 +356,12 @@ entry[0].exit, | ||
/** @type {Tokenizer} */ | ||
function tokenizeInspect(effects, ok) { | ||
let subcontinued = 0 | ||
inspectResult = { | ||
flowContinue: false, | ||
lazy: false, | ||
continued: 0, | ||
flowEnd: false | ||
} | ||
return inspectStart | ||
/** @type {State} */ | ||
function inspectStart(code) { | ||
if (subcontinued < stack.length) { | ||
const entry = stack[subcontinued] | ||
self.containerState = entry[1] | ||
assert( | ||
entry[0].continuation, | ||
'expected `continuation` to be defined on container construct' | ||
) | ||
return effects.attempt( | ||
entry[0].continuation, | ||
inspectContinue, | ||
inspectLess | ||
)(code) | ||
} | ||
assert( | ||
childFlow, | ||
'expected `childFlow` to be defined when starting inspection' | ||
) | ||
// If we’re continued but in a concrete flow, we can’t have more | ||
// containers. | ||
if (childFlow.currentConstruct && childFlow.currentConstruct.concrete) { | ||
assert( | ||
inspectResult, | ||
'expected `inspectResult` to be defined when starting inspection' | ||
) | ||
inspectResult.flowContinue = true | ||
return inspectDone(code) | ||
} | ||
self.interrupt = | ||
childFlow.currentConstruct && childFlow.currentConstruct.interruptible | ||
self.containerState = {} | ||
return effects.attempt( | ||
containerConstruct, | ||
inspectFlowEnd, | ||
inspectDone | ||
)(code) | ||
} | ||
/** @type {State} */ | ||
function inspectContinue(code) { | ||
subcontinued++ | ||
assert( | ||
self.containerState, | ||
'expected `containerState` to be defined when continuing inspection' | ||
) | ||
return self.containerState._closeFlow | ||
? inspectFlowEnd(code) | ||
: inspectStart(code) | ||
} | ||
/** @type {State} */ | ||
function inspectLess(code) { | ||
assert( | ||
childFlow, | ||
'expected `childFlow` to be defined when inspecting less' | ||
) | ||
if (childFlow.currentConstruct && childFlow.currentConstruct.lazy) { | ||
// Maybe another container? | ||
self.containerState = {} | ||
return effects.attempt( | ||
containerConstruct, | ||
inspectFlowEnd, | ||
// Maybe flow, or a blank line? | ||
effects.attempt( | ||
lazyFlowConstruct, | ||
inspectFlowEnd, | ||
effects.check(blankLine, inspectFlowEnd, inspectLazy) | ||
) | ||
)(code) | ||
} | ||
// Otherwise we’re interrupting. | ||
return inspectFlowEnd(code) | ||
} | ||
/** @type {State} */ | ||
function inspectLazy(code) { | ||
// Act as if all containers are continued. | ||
subcontinued = stack.length | ||
assert( | ||
inspectResult, | ||
'expected `inspectResult` to be defined when inspecting lazy' | ||
) | ||
inspectResult.lazy = true | ||
inspectResult.flowContinue = true | ||
return inspectDone(code) | ||
} | ||
// We’re done with flow if we have more containers, or an interruption. | ||
/** @type {State} */ | ||
function inspectFlowEnd(code) { | ||
assert( | ||
inspectResult, | ||
'expected `inspectResult` to be defined when inspecting flow end' | ||
) | ||
inspectResult.flowEnd = true | ||
return inspectDone(code) | ||
} | ||
/** @type {State} */ | ||
function inspectDone(code) { | ||
assert( | ||
inspectResult, | ||
'expected `inspectResult` to be defined when done inspecting' | ||
) | ||
inspectResult.continued = subcontinued | ||
self.interrupt = undefined | ||
self.containerState = undefined | ||
return ok(code) | ||
} | ||
function closeFlow() { | ||
assert( | ||
self.containerState, | ||
'expected `containerState` to be defined when closing flow' | ||
) | ||
assert(childFlow, 'expected `childFlow` to be defined when closing it') | ||
childFlow.write([codes.eof]) | ||
childToken = undefined | ||
childFlow = undefined | ||
self.containerState._closeFlow = undefined | ||
} | ||
@@ -359,13 +381,1 @@ } | ||
} | ||
/** @type {Tokenizer} */ | ||
function tokenizeLazyFlow(effects, ok, nok) { | ||
return factorySpace( | ||
effects, | ||
effects.lazy(this.parser.constructs.flow, ok, nok), | ||
types.linePrefix, | ||
this.parser.constructs.disable.null.includes('codeIndented') | ||
? undefined | ||
: constants.tabSize | ||
) | ||
} |
@@ -31,2 +31,3 @@ /** | ||
defined: [], | ||
lazy: {}, | ||
constructs, | ||
@@ -33,0 +34,0 @@ content: create(content), |
@@ -342,4 +342,3 @@ /** | ||
function prepareList(slice) { | ||
const length = slice.length - 1 // Skip close. | ||
const length = slice.length | ||
let index = 0 // Skip open. | ||
@@ -593,3 +592,2 @@ | ||
tag('>') | ||
setData('fencedCodeInside', true) | ||
setData('slurpOneLineEnding', true) | ||
@@ -609,5 +607,22 @@ } | ||
function onexitflowcode() { | ||
const count = getData('fencesCount') // Send an extra line feed if we saw data. | ||
const count = getData('fencesCount') // One special case is if we are inside a container, and the fenced code was | ||
// not closed (meaning it runs to the end). | ||
// In that case, the following line ending, is considered *outside* the | ||
// fenced code and block quote by micromark, but CM wants to treat that | ||
// ending as part of the code. | ||
if (getData('flowCodeSeenData')) lineEndingIfNeeded() | ||
if ( | ||
count !== undefined && | ||
count < 2 && // @ts-expect-error `tightStack` is always set. | ||
data.tightStack.length > 0 && | ||
!getData('lastWasTag') | ||
) { | ||
lineEnding() | ||
} // But in most cases, it’s simpler: when we’ve seen some data, emit an extra | ||
// line ending when needed. | ||
if (getData('flowCodeSeenData')) { | ||
lineEndingIfNeeded() | ||
} | ||
tag('</code></pre>') | ||
@@ -614,0 +629,0 @@ if (count !== undefined && count < 2) lineEndingIfNeeded() |
@@ -88,5 +88,2 @@ /** | ||
interrupt: true | ||
}), | ||
lazy: constructFactory(onsuccessfulcheck, { | ||
lazy: true | ||
}) | ||
@@ -143,3 +140,2 @@ } | ||
addResult(initialize, 0) // Otherwise, resolve, and exit. | ||
// Note: TS can’t handle recursive types. | ||
@@ -401,3 +397,6 @@ context.events = resolveAll(resolveAllConstructs, context.events, context) | ||
return construct.tokenize.call( | ||
fields ? Object.assign({}, context, fields) : context, | ||
// If we do have fields, create an object w/ `context` as its | ||
// prototype. | ||
// This allows a “live binding”, which is needed for `interrupt`. | ||
fields ? Object.assign(Object.create(context), fields) : context, | ||
effects, | ||
@@ -404,0 +403,0 @@ ok, |
@@ -10,9 +10,4 @@ /** @type {InitialConstruct} */ | ||
export type State = import('micromark-util-types').State | ||
export type Point = import('micromark-util-types').Point | ||
export type StackState = Record<string, unknown> | ||
export type StackItem = [Construct, StackState] | ||
export type Result = { | ||
flowContinue: boolean | ||
lazy: boolean | ||
continued: number | ||
flowEnd: boolean | ||
} |
@@ -9,2 +9,3 @@ /** | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-util-types').Point} Point | ||
*/ | ||
@@ -15,10 +16,8 @@ | ||
* @typedef {[Construct, StackState]} StackItem | ||
* | ||
* @typedef {{flowContinue: boolean, lazy: boolean, continued: number, flowEnd: boolean}} Result | ||
*/ | ||
import {blankLine} from 'micromark-core-commonmark' | ||
import {factorySpace} from 'micromark-factory-space' | ||
import {markdownLineEnding} from 'micromark-util-character' | ||
import {splice} from 'micromark-util-chunked' | ||
/** @type {InitialConstruct} */ | ||
/** @type {InitialConstruct} */ | ||
export const document = { | ||
@@ -32,7 +31,2 @@ tokenize: initializeDocument | ||
} | ||
/** @type {Construct} */ | ||
const lazyFlowConstruct = { | ||
tokenize: tokenizeLazyFlow | ||
} | ||
/** @type {Initializer} */ | ||
@@ -45,12 +39,3 @@ | ||
const stack = [] | ||
/** @type {Construct} */ | ||
const inspectConstruct = { | ||
tokenize: tokenizeInspect, | ||
partial: true | ||
} | ||
let continued = 0 | ||
/** @type {Result|undefined} */ | ||
let inspectResult | ||
/** @type {TokenizeContext|undefined} */ | ||
@@ -62,2 +47,5 @@ | ||
let childToken | ||
/** @type {number} */ | ||
let lineStartOffset | ||
return start | ||
@@ -67,2 +55,12 @@ /** @type {State} */ | ||
function start(code) { | ||
// First we iterate through the open blocks, starting with the root | ||
// document, and descending through last children down to the last open | ||
// block. | ||
// Each block imposes a condition that the line must satisfy if the block is | ||
// to remain open. | ||
// For example, a block quote requires a `>` character. | ||
// A paragraph requires a non-blank line. | ||
// In this phase we may match all or just some of the open blocks. | ||
// But we cannot close unmatched blocks yet, because we may have a lazy | ||
// continuation line. | ||
if (continued < stack.length) { | ||
@@ -74,7 +72,7 @@ const item = stack[continued] | ||
documentContinue, | ||
documentContinued | ||
checkNewContainers | ||
)(code) | ||
} | ||
} // Done. | ||
return documentContinued(code) | ||
return checkNewContainers(code) | ||
} | ||
@@ -84,2 +82,3 @@ /** @type {State} */ | ||
function documentContinue(code) { | ||
if (self.containerState._closeFlow) closeFlow() | ||
continued++ | ||
@@ -90,14 +89,51 @@ return start(code) | ||
function documentContinued(code) { | ||
// If we’re in a concrete construct (such as when expecting another line of | ||
// HTML, or we resulted in lazy content), we can immediately start flow. | ||
if (inspectResult && inspectResult.flowContinue) { | ||
return flowStart(code) | ||
} | ||
function checkNewContainers(code) { | ||
// Next, after consuming the continuation markers for existing blocks, we | ||
// look for new block starts (e.g. `>` for a block quote). | ||
// If we encounter a new block start, we close any blocks unmatched in | ||
// step 1 before creating the new block as a child of the last matched | ||
// block. | ||
if (continued === stack.length) { | ||
// No need to `check` whether there’s a container, of `exitContainers` | ||
// would be moot. | ||
// We can instead immediately `attempt` to parse one. | ||
if (!childFlow) { | ||
return documentContinued(code) | ||
} // If we have concrete content, such as block HTML or fenced code, | ||
// we can’t have containers “pierce” into them, so we can immediately | ||
// start. | ||
self.interrupt = | ||
childFlow && | ||
childFlow.currentConstruct && | ||
childFlow.currentConstruct.interruptible | ||
if (childFlow.currentConstruct && childFlow.currentConstruct.concrete) { | ||
return flowStart(code) | ||
} // If we do have flow, we’d be interrupting it w/ a new container. | ||
self.interrupt = true | ||
} // Check if there is a new container. | ||
self.containerState = {} | ||
return effects.check( | ||
containerConstruct, | ||
thereIsANewContainer, | ||
thereIsNoNewContainer | ||
)(code) | ||
} | ||
/** @type {State} */ | ||
function thereIsANewContainer(code) { | ||
if (childFlow) closeFlow() | ||
exitContainers(continued) | ||
return documentContinued(code) | ||
} | ||
/** @type {State} */ | ||
function thereIsNoNewContainer(code) { | ||
self.parser.lazy[self.now().line] = continued !== stack.length | ||
lineStartOffset = self.now().offset | ||
return flowStart(code) | ||
} | ||
/** @type {State} */ | ||
function documentContinued(code) { | ||
// Try new containers. | ||
self.containerState = {} | ||
return effects.attempt( | ||
@@ -112,4 +148,5 @@ containerConstruct, | ||
function containerContinue(code) { | ||
stack.push([self.currentConstruct, self.containerState]) | ||
self.containerState = undefined | ||
continued++ | ||
stack.push([self.currentConstruct, self.containerState]) // Try another. | ||
return documentContinued(code) | ||
@@ -121,3 +158,4 @@ } | ||
if (code === null) { | ||
exitContainers(0, true) | ||
if (childFlow) closeFlow() | ||
exitContainers(0) | ||
effects.consume(code) | ||
@@ -139,4 +177,6 @@ return | ||
if (code === null) { | ||
continueFlow(effects.exit('chunkFlow')) | ||
return flowStart(code) | ||
writeToChild(effects.exit('chunkFlow'), true) | ||
exitContainers(0) | ||
effects.consume(code) | ||
return | ||
} | ||
@@ -146,4 +186,7 @@ | ||
effects.consume(code) | ||
continueFlow(effects.exit('chunkFlow')) | ||
return effects.check(inspectConstruct, documentAfterPeek) | ||
writeToChild(effects.exit('chunkFlow')) // Get ready for the next line. | ||
continued = 0 | ||
self.interrupt = undefined | ||
return start | ||
} | ||
@@ -154,136 +197,130 @@ | ||
} | ||
/** @type {State} */ | ||
function documentAfterPeek(code) { | ||
exitContainers(inspectResult.continued, inspectResult.flowEnd) | ||
continued = 0 | ||
return start(code) | ||
} | ||
/** | ||
* @param {Token} token | ||
* @param {boolean} [eof] | ||
* @returns {void} | ||
*/ | ||
function continueFlow(token) { | ||
function writeToChild(token, eof) { | ||
const stream = self.sliceStream(token) | ||
if (eof) stream.push(null) | ||
token.previous = childToken | ||
if (childToken) childToken.next = token | ||
childToken = token | ||
childFlow.lazy = inspectResult ? inspectResult.lazy : false | ||
childFlow.defineSkip(token.start) | ||
childFlow.write(self.sliceStream(token)) | ||
} | ||
/** | ||
* @param {number} size | ||
* @param {boolean} end | ||
* @returns {void} | ||
*/ | ||
childFlow.write(stream) // Alright, so we just added a lazy line: | ||
// | ||
// ```markdown | ||
// > a | ||
// b. | ||
// | ||
// Or: | ||
// | ||
// > ~~~c | ||
// d | ||
// | ||
// Or: | ||
// | ||
// > | e | | ||
// f | ||
// ``` | ||
// | ||
// The construct in the second example (fenced code) does not accept lazy | ||
// lines, so it marked itself as done at the end of its first line, and | ||
// then the content construct parses `d`. | ||
// Most constructs in markdown match on the first line: if the first line | ||
// forms a construct, a non-lazy line can’t “unmake” it. | ||
// | ||
// The construct in the third example is potentially a GFM table, and | ||
// those are *weird*. | ||
// It *could* be a table, from the first line, if the following line | ||
// matches a condition. | ||
// In this case, that second line is lazy, which “unmakes” the first line | ||
// and turns the whole into one content block. | ||
// | ||
// We’ve now parsed the non-lazy and the lazy line, and can figure out | ||
// whether the lazy line started a new flow block. | ||
// If it did, we exit the current containers between the two flow blocks. | ||
function exitContainers(size, end) { | ||
let index = stack.length // Close the flow. | ||
if (self.parser.lazy[token.start.line]) { | ||
let index = childFlow.events.length | ||
if (childFlow && end) { | ||
childFlow.write([null]) | ||
childToken = undefined | ||
childFlow = undefined | ||
} // Exit open containers. | ||
while (index--) { | ||
if ( | ||
// The token starts before the line ending… | ||
childFlow.events[index][1].start.offset < lineStartOffset && // …and either is not ended yet… | ||
(!childFlow.events[index][1].end || // …or ends after it. | ||
childFlow.events[index][1].end.offset > lineStartOffset) | ||
) { | ||
// Exit: there’s still something open, which means it’s a lazy line | ||
// part of something. | ||
return | ||
} | ||
} | ||
while (index-- > size) { | ||
self.containerState = stack[index][1] | ||
const entry = stack[index] | ||
entry[0].exit.call(self, effects) | ||
} | ||
const indexBeforeExits = self.events.length | ||
let indexBeforeFlow = indexBeforeExits | ||
/** @type {boolean|undefined} */ | ||
stack.length = size | ||
} | ||
/** @type {Tokenizer} */ | ||
let seen | ||
/** @type {Point|undefined} */ | ||
function tokenizeInspect(effects, ok) { | ||
let subcontinued = 0 | ||
inspectResult = { | ||
flowContinue: false, | ||
lazy: false, | ||
continued: 0, | ||
flowEnd: false | ||
} | ||
return inspectStart | ||
/** @type {State} */ | ||
let point // Find the previous chunk (the one before the lazy line). | ||
function inspectStart(code) { | ||
if (subcontinued < stack.length) { | ||
const entry = stack[subcontinued] | ||
self.containerState = entry[1] | ||
return effects.attempt( | ||
entry[0].continuation, | ||
inspectContinue, | ||
inspectLess | ||
)(code) | ||
} | ||
while (indexBeforeFlow--) { | ||
if ( | ||
self.events[indexBeforeFlow][0] === 'exit' && | ||
self.events[indexBeforeFlow][1].type === 'chunkFlow' | ||
) { | ||
if (seen) { | ||
point = self.events[indexBeforeFlow][1].end | ||
break | ||
} | ||
// If we’re continued but in a concrete flow, we can’t have more | ||
// containers. | ||
if (childFlow.currentConstruct && childFlow.currentConstruct.concrete) { | ||
inspectResult.flowContinue = true | ||
return inspectDone(code) | ||
seen = true | ||
} | ||
} | ||
self.interrupt = | ||
childFlow.currentConstruct && childFlow.currentConstruct.interruptible | ||
self.containerState = {} | ||
return effects.attempt( | ||
containerConstruct, | ||
inspectFlowEnd, | ||
inspectDone | ||
)(code) | ||
} | ||
/** @type {State} */ | ||
exitContainers(continued) // Fix positions. | ||
function inspectContinue(code) { | ||
subcontinued++ | ||
return self.containerState._closeFlow | ||
? inspectFlowEnd(code) | ||
: inspectStart(code) | ||
} | ||
/** @type {State} */ | ||
index = indexBeforeExits | ||
function inspectLess(code) { | ||
if (childFlow.currentConstruct && childFlow.currentConstruct.lazy) { | ||
// Maybe another container? | ||
self.containerState = {} | ||
return effects.attempt( | ||
containerConstruct, | ||
inspectFlowEnd, // Maybe flow, or a blank line? | ||
effects.attempt( | ||
lazyFlowConstruct, | ||
inspectFlowEnd, | ||
effects.check(blankLine, inspectFlowEnd, inspectLazy) | ||
) | ||
)(code) | ||
} // Otherwise we’re interrupting. | ||
while (index < self.events.length) { | ||
self.events[index][1].end = Object.assign({}, point) | ||
index++ | ||
} // Inject the exits earlier (they’re still also at the end). | ||
return inspectFlowEnd(code) | ||
splice( | ||
self.events, | ||
indexBeforeFlow + 1, | ||
0, | ||
self.events.slice(indexBeforeExits) | ||
) // Discard the duplicate exits. | ||
self.events.length = index | ||
} | ||
/** @type {State} */ | ||
} | ||
/** | ||
* @param {number} size | ||
* @returns {void} | ||
*/ | ||
function inspectLazy(code) { | ||
// Act as if all containers are continued. | ||
subcontinued = stack.length | ||
inspectResult.lazy = true | ||
inspectResult.flowContinue = true | ||
return inspectDone(code) | ||
} // We’re done with flow if we have more containers, or an interruption. | ||
function exitContainers(size) { | ||
let index = stack.length // Exit open containers. | ||
/** @type {State} */ | ||
function inspectFlowEnd(code) { | ||
inspectResult.flowEnd = true | ||
return inspectDone(code) | ||
while (index-- > size) { | ||
const entry = stack[index] | ||
self.containerState = entry[1] | ||
entry[0].exit.call(self, effects) | ||
} | ||
/** @type {State} */ | ||
function inspectDone(code) { | ||
inspectResult.continued = subcontinued | ||
self.interrupt = undefined | ||
self.containerState = undefined | ||
return ok(code) | ||
} | ||
stack.length = size | ||
} | ||
function closeFlow() { | ||
childFlow.write([null]) | ||
childToken = undefined | ||
childFlow = undefined | ||
self.containerState._closeFlow = undefined | ||
} | ||
} | ||
@@ -300,11 +337,1 @@ /** @type {Tokenizer} */ | ||
} | ||
/** @type {Tokenizer} */ | ||
function tokenizeLazyFlow(effects, ok, nok) { | ||
return factorySpace( | ||
effects, | ||
effects.lazy(this.parser.constructs.flow, ok, nok), | ||
'linePrefix', | ||
this.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4 | ||
) | ||
} |
@@ -31,2 +31,3 @@ /** | ||
defined: [], | ||
lazy: {}, | ||
constructs, | ||
@@ -33,0 +34,0 @@ content: create(content), |
{ | ||
"name": "micromark", | ||
"version": "3.0.0-alpha.2", | ||
"version": "3.0.0-alpha.3", | ||
"description": "small commonmark compliant markdown parser with positional info and concrete tokens", | ||
@@ -101,14 +101,14 @@ "license": "MIT", | ||
"debug": "^4.0.0", | ||
"micromark-core-commonmark": "1.0.0-alpha.2", | ||
"micromark-factory-space": "1.0.0-alpha.2", | ||
"micromark-util-character": "1.0.0-alpha.2", | ||
"micromark-util-chunked": "1.0.0-alpha.2", | ||
"micromark-util-combine-extensions": "1.0.0-alpha.2", | ||
"micromark-util-encode": "1.0.0-alpha.2", | ||
"micromark-util-normalize-identifier": "1.0.0-alpha.2", | ||
"micromark-util-resolve-all": "1.0.0-alpha.2", | ||
"micromark-util-sanitize-uri": "1.0.0-alpha.2", | ||
"micromark-util-subtokenize": "1.0.0-alpha.2", | ||
"micromark-util-symbol": "1.0.0-alpha.2", | ||
"micromark-util-types": "1.0.0-alpha.2", | ||
"micromark-core-commonmark": "1.0.0-alpha.3", | ||
"micromark-factory-space": "1.0.0-alpha.3", | ||
"micromark-util-character": "1.0.0-alpha.3", | ||
"micromark-util-chunked": "1.0.0-alpha.3", | ||
"micromark-util-combine-extensions": "1.0.0-alpha.3", | ||
"micromark-util-encode": "1.0.0-alpha.3", | ||
"micromark-util-normalize-identifier": "1.0.0-alpha.3", | ||
"micromark-util-resolve-all": "1.0.0-alpha.3", | ||
"micromark-util-sanitize-uri": "1.0.0-alpha.3", | ||
"micromark-util-subtokenize": "1.0.0-alpha.3", | ||
"micromark-util-symbol": "1.0.0-alpha.3", | ||
"micromark-util-types": "1.0.0-alpha.3", | ||
"parse-entities": "^3.0.0" | ||
@@ -115,0 +115,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
171235
49
5509
+ Addedmicromark-core-commonmark@1.0.0-alpha.3(transitive)
+ Addedmicromark-factory-space@1.0.0-alpha.3(transitive)
+ Addedmicromark-util-character@1.0.0-alpha.3(transitive)
+ Addedmicromark-util-chunked@1.0.0-alpha.3(transitive)
+ Addedmicromark-util-combine-extensions@1.0.0-alpha.3(transitive)
+ Addedmicromark-util-encode@1.0.0-alpha.3(transitive)
+ Addedmicromark-util-normalize-identifier@1.0.0-alpha.3(transitive)
+ Addedmicromark-util-resolve-all@1.0.0-alpha.3(transitive)
+ Addedmicromark-util-sanitize-uri@1.0.0-alpha.3(transitive)
+ Addedmicromark-util-subtokenize@1.0.0-alpha.3(transitive)
+ Addedmicromark-util-symbol@1.0.0-alpha.3(transitive)
+ Addedmicromark-util-types@1.0.0-alpha.3(transitive)
- Removedmicromark-core-commonmark@1.0.0-alpha.2(transitive)
- Removedmicromark-factory-space@1.0.0-alpha.2(transitive)
- Removedmicromark-util-character@1.0.0-alpha.2(transitive)
- Removedmicromark-util-chunked@1.0.0-alpha.2(transitive)
- Removedmicromark-util-combine-extensions@1.0.0-alpha.2(transitive)
- Removedmicromark-util-encode@1.0.0-alpha.2(transitive)
- Removedmicromark-util-normalize-identifier@1.0.0-alpha.2(transitive)
- Removedmicromark-util-resolve-all@1.0.0-alpha.2(transitive)
- Removedmicromark-util-sanitize-uri@1.0.0-alpha.2(transitive)
- Removedmicromark-util-subtokenize@1.0.0-alpha.2(transitive)
- Removedmicromark-util-symbol@1.0.0-alpha.2(transitive)
- Removedmicromark-util-types@1.0.0-alpha.2(transitive)