4b - review nits: the list's headroom folds into its item loop, emitBlocks inlined, panel-wrapped boundary pinned, 4c note says cubic
CI / gate (push) Successful in 34s
CI / publish (push) Has been skipped

This commit is contained in:
2026-09-18 00:43:56 +02:00
parent 98ff478fe9
commit c7a7a4333b
3 changed files with 20 additions and 22 deletions
@@ -520,6 +520,7 @@ test('refuses a document nested deeper than the emitter carries', () => {
const deep = document(listed(directiveLists, [{ type: 'rule' }]))
assert.deepEqual(markdownToAdf(markdown(adfToMarkdown(deep))), { ok: true, value: deep })
assert.equal(code(adfToMarkdown(document(listed(directiveLists + 1, [{ type: 'rule' }])))), 'unsupported-nesting-depth')
assert.equal(code(adfToMarkdown(document({ content: [listed(directiveLists, [{ type: 'rule' }])], type: 'panel' }))), 'unsupported-nesting-depth')
const chain = (levels: number): AdfNode => {
let card: AdfNode = { type: 'blockCard' }
for (let level = 0; level < levels; level += 1) card = { content: [card], type: 'blockCard' }
+12 -14
View File
@@ -31,18 +31,13 @@ export function adfToMarkdown(document: AdfDocument): Result<string> {
const fault = adfDocumentFault(document)
if (fault !== undefined) return faulted(fault, [])
if (document.version !== 1) return failure('unsupported-document-version', `no markdown spelling carries ADF version ${document.version}`, [])
const blocks = emitBlocks(nodeContent(document), 'document', [], 0)
if (!blocks.ok) return blocks
return success(blocks.value === '' ? '' : `${blocks.value}\n`)
}
function emitBlocks(nodes: readonly AdfNode[], container: BlockContainer, path: ConvertErrorPath, depth: number): Result<string> {
const walk = walkBlocks(nodes, path, depth)
const walk = walkBlocks(nodeContent(document), [], 0)
if (!walk.ok) return walk
return success(joinBlocks(walk.value.blocks, container))
const text = joinBlocks(walk.value.blocks, 'document')
return success(text === '' ? '' : `${text}\n`)
}
// The walk's headroom is the least slack any depth guard below it has, so a spelling that sinks the walked blocks a level can refuse rather than walk again.
// headroom: the least slack any depth guard below the walk has.
function walkBlocks(nodes: readonly AdfNode[], path: ConvertErrorPath, depth: number): Result<Walk> {
let headroom = largestNesting - depth
if (headroom < 0) return tooDeep(path)
@@ -224,25 +219,28 @@ function emitList(node: AdfNode, path: ConvertErrorPath, depth: number): Result<
if (start === undefined || items.length === 0) return undefined
if (items.some((item) => item.type !== 'listItem' || !carriesOnly(item, []))) return undefined
const walked: WalkedItem[] = []
let headroom = Number.POSITIVE_INFINITY
for (const [offset, item] of items.entries()) {
const walk = walkBlocks(nodeContent(item), [...path, 'content', offset], depth + 1)
if (!walk.ok) return walk
headroom = Math.min(headroom, walk.value.headroom)
walked.push({ node: item, walk: walk.value })
}
const headroom = Math.min(...walked.map((item) => item.walk.headroom))
const lines: string[] = []
for (const [offset, item] of walked.entries()) {
const line = listItemLines(item.walk.blocks, ordered ? `${start + offset}. ` : '- ')
if (line === undefined) return headroom < 1 ? tooDeep(path) : emitDirectiveBlock(node, ordered ? blockDirectives.orderedList : blockDirectives.bulletList, path, depth, () => success(directiveItems(walked)))
if (line === undefined) {
if (headroom < 1) return tooDeep(path)
return emitDirectiveBlock(node, ordered ? blockDirectives.orderedList : blockDirectives.bulletList, path, depth, () => success({ blocks: directiveItems(walked), headroom: headroom - 1 }))
}
lines.push(line)
}
return success({ headroom, spelling: 'list', text: lines.join('\n') })
}
// The directive form sinks each item's blocks a level below where the walk read them.
function directiveItems(items: readonly WalkedItem[]): Walk {
const blocks = items.map((item) => ({ ...directivePair(item.node, listItemOpener, joinBlocks(item.walk.blocks, 'directive'), item.walk.headroom - 1), node: item.node }))
return { blocks, headroom: Math.min(...blocks.map((block) => block.headroom)) }
function directiveItems(items: readonly WalkedItem[]): PlacedBlock[] {
return items.map((item) => ({ ...directivePair(item.node, listItemOpener, joinBlocks(item.walk.blocks, 'directive'), item.walk.headroom - 1), node: item.node }))
}
function listStart(node: AdfNode, items: number): number | undefined {