Answer the architecture pass: one fault vocabulary, and the guards pinned where they compose
CI / gate (push) Successful in 5s

This commit is contained in:
2026-09-01 10:01:35 +02:00
parent f328ed4cab
commit f231f652ca
11 changed files with 59 additions and 38 deletions
+10
View File
@@ -87,6 +87,16 @@ test('holds a directive container open until the fence that closes it', () => {
assert.deepEqual(faults(':::panel info\n> Part.\n> :::\n'), [])
assert.deepEqual(faults('::::panel info\n- :::expand\n Part.\n :::\n::::\n'), [])
assert.deepEqual(faults(':::panel info\n```\n:::\n```\n:::\n'), [])
assert.deepEqual(parseBlocks(':::panel info {panelColor="#ff0000"}\nPart.\n:::\n').blocks, [
{
argument: 'info',
attributes: new Map([['panelColor', '#ff0000']]),
blocks: [{ kind: 'paragraph', text: 'Part.' }],
kind: 'directive',
name: 'panel',
},
])
assert.deepEqual(parseBlocks('::rule\n').blocks, [{ argument: undefined, attributes: new Map(), blocks: undefined, kind: 'directive', name: 'rule' }])
})
test('names the directive fence a container does not sit longer than', () => {
+11 -9
View File
@@ -181,10 +181,15 @@ function openContainer(walk: Walk, start: ContainerStart): void {
function closeContainers(walk: Walk, depth: number): void {
closeLeaf(walk)
for (const container of walk.stack.splice(depth)) {
for (const container of walk.stack.slice(depth)) {
if (container.kind !== 'directive') continue
container.parent[container.index] = { fault: malformedDirective(`a container fenced with ${container.colons} colons is unclosed`), kind: 'fault' }
}
dropContainers(walk, depth)
}
function dropContainers(walk: Walk, depth: number): void {
walk.stack.length = depth
}
function openDirective(walk: Walk, directive: Extract<DirectiveLine, { kind: 'header' }>): void {
@@ -200,12 +205,7 @@ function openDirective(walk: Walk, directive: Extract<DirectiveLine, { kind: 'he
if (block.blocks !== undefined) walk.stack.push({ blocks: block.blocks, colons: directive.colons, index: parent.length - 1, kind: 'directive', parent })
}
function readDirective(walk: Walk, directive: DirectiveLine): void {
closeLeaf(walk)
if (directive.kind === 'fault') {
pushFault(walk, directive.fault)
return
}
function applyDirectiveLine(walk: Walk, directive: DirectiveLine): void {
const enclosing = innermostDirective(walk)
if (directive.kind === 'closing') {
closeDirective(walk, directive.colons, enclosing)
@@ -227,7 +227,7 @@ function closeDirective(walk: Walk, colons: number, enclosing: { container: Open
pushFault(walk, malformedDirective(`a closing fence is shorter than the ${enclosing.container.colons} colons it would close`))
return
}
walk.stack.length = enclosing.depth
dropContainers(walk, enclosing.depth)
}
function innermostDirective(walk: Walk): { container: OpenDirective; depth: number } | undefined {
@@ -292,7 +292,9 @@ function openLeaf(walk: Walk, line: Line): void {
const opener = removeColumns(line, largestOpenerIndentation).text
const directive = readDirectiveLine(opener)
if (directive !== undefined) {
readDirective(walk, directive)
closeLeaf(walk)
if (directive.fault === undefined) applyDirectiveLine(walk, directive.value)
else pushFault(walk, directive.fault)
return
}
if (claimsPipeLine(opener)) {
+2 -3
View File
@@ -4,7 +4,7 @@ import type { LinkDefinition } from '../link-syntax.ts'
import { backslashEscape, decodeTextEscapes, inlineHtmlConstruct, readBracketedAutolink, readEmailAutolink } from '../commonmark-grammar.ts'
import { backtickRun, closingBacktickRun } from '../backtick-runs.ts'
import { delimiterFlags, matchEmphasis, runLength } from '../emphasis-matching.ts'
import { failure, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { mergeAdjacentText } from '../../adf/editor-normal.ts'
import { normalizeLabel, readInlineTarget, readLabel } from '../link-syntax.ts'
import { readInlineDirective, unknownDirectiveFault } from '../directive-syntax.ts'
@@ -144,8 +144,7 @@ function readDirective(scan: Scan, index: number): Result<number> {
scan.pending += ':'
return success(index + 1)
}
const fault = directive.fault ?? unknownDirectiveFault(directive.value.name)
return failure(fault.code, fault.message, scan.path)
return faulted(directive.fault ?? unknownDirectiveFault(directive.value.name), scan.path)
}
function flush(scan: Scan, strip: boolean): void {
+4 -6
View File
@@ -1,7 +1,7 @@
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
import type { Block } from './blocks.ts'
import type { LinkDefinitions } from './inline-content.ts'
import { failure, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { largestNesting } from '../../nesting.ts'
import { parseBlocks } from './blocks.ts'
import { parseInlineContent } from './inline-content.ts'
@@ -33,12 +33,10 @@ function blockNode(block: Block, definitions: LinkDefinitions, path: ConvertErro
return listNode({ type: 'bulletList' }, block.items, definitions, path, depth)
case 'code':
return success(codeBlockNode(block.language, block.text))
case 'directive': {
const fault = unknownDirectiveFault(block.name)
return failure(fault.code, fault.message, path)
}
case 'directive':
return faulted(unknownDirectiveFault(block.name), path)
case 'fault':
return failure(block.fault.code, block.fault.message, path)
return faulted(block.fault, path)
case 'heading':
return contentNode({ attrs: { level: block.level }, type: 'heading' }, block.text, definitions, path)
case 'html':