Read the container blocks, and part a nested list the tight spelling would swallow #32

Merged
lilleman merged 6 commits from tick-3c into main 2026-08-30 21:02:45 +02:00
6 changed files with 13 additions and 10 deletions
Showing only changes of commit 7fc0cdb827 - Show all commits
@@ -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"
}
],
@@ -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
```
+4 -1
View File
@@ -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', () => {
+5 -6
View File
@@ -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<string> {
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<string> {
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 {
+1 -1
View File
@@ -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
@@ -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', () => {