diff --git a/corpus/errors/list-break-body.error b/corpus/errors/list-break-body.error new file mode 100644 index 0000000..4f35411 --- /dev/null +++ b/corpus/errors/list-break-body.error @@ -0,0 +1 @@ +malformed-directive diff --git a/corpus/errors/list-break-body.md b/corpus/errors/list-break-body.md new file mode 100644 index 0000000..8011a94 --- /dev/null +++ b/corpus/errors/list-break-body.md @@ -0,0 +1,7 @@ +- Bolt M8 + +!adf:listBreak +Packed. +!adf:/listBreak + +- Nut M8 diff --git a/src/markdown/parse/blocks.test.ts b/src/markdown/parse/blocks.test.ts index 3802dfc..b99f357 100644 --- a/src/markdown/parse/blocks.test.ts +++ b/src/markdown/parse/blocks.test.ts @@ -115,5 +115,6 @@ test('closes the containers a closer names past as unclosed, and crosses no list assert.deepEqual(faults('> !adf:panel info\n> Part.\n!adf:/panel\n'), [unclosed('panel'), unopened('panel')]) assert.deepEqual(faults('!adf:panel info\n> !adf:/panel\n!adf:/panel\n'), [unopened('panel')]) assert.deepEqual(faults('Part.\n\n!adf:/panel\n'), [unopened('panel')]) - assert.deepEqual(faults('!adf:rule {localId=a-1}\nPart.\n!adf:/rule\n'), ['rule takes no body, so no !adf:/rule closes it']) + assert.deepEqual(kinds('!adf:rule {localId=a-1}\nPart.\n!adf:/rule\n'), ['fault', 'paragraph']) + assert.deepEqual(kinds('!adf:rule {localId=a-1}\n!adf:/rule\n!adf:/rule\n'), ['fault', 'fault']) }) diff --git a/src/markdown/parse/blocks.ts b/src/markdown/parse/blocks.ts index 82281c8..05f3791 100644 --- a/src/markdown/parse/blocks.ts +++ b/src/markdown/parse/blocks.ts @@ -60,14 +60,16 @@ type ContainerStart = { kind: 'blockquote'; rest: Line } | { fresh: boolean; ind // The line from an absolute column on: a tab a cut splits keeps the stop it is measured against. type Line = { column: number; text: string } -type Walk = ParsedBlocks & { leaf: OpenLeaf | undefined; position: SourcePosition; stack: OpenContainer[] } +type LeafOpener = { index: number; position: SourcePosition } + +type Walk = ParsedBlocks & { leaf: OpenLeaf | undefined; leafOpeners: Map>; position: SourcePosition; stack: OpenContainer[] } const indentedCodeColumns = 4 const largestOpenerIndentation = 3 const tabStop = 4 export function parseBlocks(markdown: string): ParsedBlocks { - const walk: Walk = { blocks: [], definitions: new Map(), leaf: undefined, position: { line: 1, offset: 0 }, stack: [] } + const walk: Walk = { blocks: [], definitions: new Map(), leaf: undefined, leafOpeners: new Map(), position: { line: 1, offset: 0 }, stack: [] } for (const line of sourceLines(markdown)) { walk.position = line.position readLine(walk, { column: 0, text: line.text }) @@ -229,15 +231,16 @@ function openDirective(walk: Walk, directive: Extract { + const known = walk.leafOpeners.get(blocks) + if (known !== undefined) return known + const openers = new Map() + walk.leafOpeners.set(blocks, openers) + return openers +} + function pushFault(walk: Walk, fault: ConvertFault): void { currentBlocks(walk).push({ fault, kind: 'fault', position: walk.position }) } diff --git a/src/markdown/parse/markdown-to-adf.test.ts b/src/markdown/parse/markdown-to-adf.test.ts index 645eaee..960d8a0 100644 --- a/src/markdown/parse/markdown-to-adf.test.ts +++ b/src/markdown/parse/markdown-to-adf.test.ts @@ -315,7 +315,18 @@ test('names the closer that finds no container open where it stands', () => { assert.equal(content(markdownToAdf('!adf:panel info\n> !adf:/panel\n!adf:/panel\n')), unopened('panel')) assert.equal(content(markdownToAdf('!adf:panel info\n- !adf:/panel\n!adf:/panel\n')), unopened('panel')) assert.equal(content(markdownToAdf('!adf:panel info\nPart.\n!adf:/expand\n!adf:/panel\n')), unopened('expand')) - assert.equal(content(markdownToAdf('!adf:rule {localId=a-1}\nPart.\n!adf:/rule\n')), 'malformed-directive: rule takes no body, so no !adf:/rule closes it') +}) + +test('names the leaf given a body at its opener, ahead of any refusal the leaf holds itself', () => { + const body = (name: string): string => `malformed-directive: ${name} takes no body, so no !adf:/${name} closes it` + assert.equal(content(markdownToAdf('!adf:rule\nPart.\n!adf:/rule\n')), body('rule')) + assert.deepEqual(position(markdownToAdf('Part.\n\n!adf:rule\nPart.\n!adf:/rule\n')), { line: 3, offset: 7 }) + assert.equal(content(markdownToAdf('- a\n\n!adf:listBreak\n!adf:/listBreak\n\n- b\n')), body('listBreak')) + assert.equal(content(markdownToAdf('- a\n\n!adf:listBreak\nPart.\n!adf:/listBreak\n\n- b\n')), body('listBreak')) + assert.deepEqual(path(markdownToAdf('!adf:rule {localId=a-1}\n!adf:rule {localId=a-2}\nPart.\n!adf:/rule\n')), ['content', 1]) + assert.deepEqual(path(markdownToAdf('!adf:rule {localId=a-1}\n!adf:/rule\n!adf:/rule\n')), ['content', 0]) + assert.deepEqual(path(markdownToAdf('Part.\n\n!adf:/rule\n')), ['content', 1]) + assert.deepEqual(path(markdownToAdf('!adf:panel info\n!adf:rule {localId=a-1}\n!adf:/panel\n!adf:/rule\n')), ['content', 1]) }) test('names the position a directive name the other one spells belongs to', () => {