diff --git a/corpus/round-trip/commonmark-subset/code-block.json b/corpus/round-trip/commonmark-subset/code-block.json index ccc8f3d..8008692 100644 --- a/corpus/round-trip/commonmark-subset/code-block.json +++ b/corpus/round-trip/commonmark-subset/code-block.json @@ -6,7 +6,7 @@ }, "content": [ { - "text": "SELECT id\nFROM part\nWHERE qty > 0;\n-- 1. the marker a container start would claim", + "text": "SELECT id\nFROM part\nWHERE qty > 0;\n- 1. the markers a container start would claim", "type": "text" } ], diff --git a/corpus/round-trip/commonmark-subset/code-block.md b/corpus/round-trip/commonmark-subset/code-block.md index 554645b..527bb73 100644 --- a/corpus/round-trip/commonmark-subset/code-block.md +++ b/corpus/round-trip/commonmark-subset/code-block.md @@ -2,5 +2,5 @@ SELECT id FROM part WHERE qty > 0; --- 1. the marker a container start would claim +- 1. the markers a container start would claim ``` diff --git a/src/markdown/emit/adf-to-markdown.test.ts b/src/markdown/emit/adf-to-markdown.test.ts index 6647675..ba97844 100644 --- a/src/markdown/emit/adf-to-markdown.test.ts +++ b/src/markdown/emit/adf-to-markdown.test.ts @@ -130,9 +130,12 @@ test('refuses a line whose start block parsing would claim', () => { assert.equal(code(adfToMarkdown(document(paragraph({ marks: [{ type: 'code' }], text: '```', type: 'text' })))), 'unspellable-line-start') }) -test('refuses two adjacent lists of the same kind', () => { +test('refuses two adjacent lists of the same kind, the marker spelling being what merges', () => { const list: AdfNode = { content: [{ content: [paragraph({ text: 'x', type: 'text' })], type: 'listItem' }], type: 'bulletList' } assert.equal(code(adfToMarkdown(document(list, list))), 'unspellable-adjacent-lists') + const carried: AdfNode = { ...list, attrs: { unknown: 'x' } } + assert.ok(markdown(adfToMarkdown(document(carried, carried))).startsWith('```adf\n')) + assert.ok(markdown(adfToMarkdown(document(carried, list))).endsWith('```\n\n- x\n')) }) test('carries a node type no section spells', () => { diff --git a/src/markdown/emit/adf-to-markdown.ts b/src/markdown/emit/adf-to-markdown.ts index 1975ed2..c03f15b 100644 --- a/src/markdown/emit/adf-to-markdown.ts +++ b/src/markdown/emit/adf-to-markdown.ts @@ -14,13 +14,12 @@ import { tryImage } from './image.ts' import { tryPipeTable } from './pipe-table.ts' type BlockContainer = 'directive' | 'document' | 'list-item' -type BlockSpelling = 'commonmark' | 'directive' +type BlockSpelling = 'commonmark' | 'directive' | 'list' type EmittedBody = { fenceColons: number; text: string } type EmittedBlock = EmittedBody & { spelling: BlockSpelling } type PlacedBlock = EmittedBlock & { node: AdfNode; path: ConvertErrorPath } const largestListMarker = 999999999 -const listTypes = ['bulletList', 'orderedList'] export function adfToMarkdown(document: AdfDocument): Result { if (!isAdfDocument(document)) return failure('not-an-adf-document', 'the value is not an ADF document', []) @@ -55,9 +54,9 @@ function emitBlocks(nodes: readonly AdfNode[], container: BlockContainer, path: } function separationBetween(previous: PlacedBlock, next: PlacedBlock, container: BlockContainer): Result { - const plainPair = previous.spelling === 'commonmark' && next.spelling === 'commonmark' - if (plainPair && listTypes.includes(next.node.type)) { - if (previous.node.type === next.node.type) { + const plainPair = previous.spelling !== 'directive' && next.spelling !== 'directive' + if (next.spelling === 'list') { + if (previous.spelling === 'list' && previous.node.type === next.node.type) { return failure('unspellable-adjacent-lists', `two adjacent ${next.node.type} nodes read back as one list`, next.path) } if (container === 'list-item') return success(interruptsParagraph(next.node) ? '\n' : '\n\n') @@ -221,7 +220,7 @@ function emitList(node: AdfNode, path: ConvertErrorPath, depth: number): Result< fenceColons = Math.max(fenceColons, emitted.value.fenceColons) lines.push(emitted.value.text) } - return success({ fenceColons, spelling: 'commonmark', text: lines.join('\n') }) + return success({ fenceColons, spelling: 'list', text: lines.join('\n') }) } function listStart(node: AdfNode, items: number): number | undefined { diff --git a/src/markdown/parse/blocks.ts b/src/markdown/parse/blocks.ts index cf9d3bf..616bc1e 100644 --- a/src/markdown/parse/blocks.ts +++ b/src/markdown/parse/blocks.ts @@ -59,7 +59,7 @@ export function parseBlocks(markdown: string): ParsedBlocks { function readLine(walk: Walk, line: Line): void { const matched = matchContainers(walk, line) - // A leaf that swallows whole lines takes the marker too: no container opens inside a code or HTML block. + // CommonMark: no container opens inside an open code or HTML block. if (matched.depth === walk.stack.length && swallowsLines(walk.leaf)) { readBlockLine(walk, matched.rest) return diff --git a/src/markdown/parse/markdown-to-adf.test.ts b/src/markdown/parse/markdown-to-adf.test.ts index e879ba7..4e3ab18 100644 --- a/src/markdown/parse/markdown-to-adf.test.ts +++ b/src/markdown/parse/markdown-to-adf.test.ts @@ -199,6 +199,7 @@ test('measures a tab from the column the containers cut it to', () => { test('drops the tightness ADF does not record', () => { assert.deepEqual(content(markdownToAdf('- a\n\n- b\n')), [bulletList(item(paragraph('a')), item(paragraph('b')))]) assert.deepEqual(content(markdownToAdf('- a\n\n 2. b\n')), [bulletList(item(paragraph('a'), orderedList(2, item(paragraph('b')))))]) + assert.deepEqual(content(markdownToAdf('- a\n\n -\n')), [bulletList(item(paragraph('a'), bulletList(item())))]) }) test('opens a list beside a paragraph only where the marker interrupts it', () => {