21 - the table's row is the node's model, never the node
CI / gate (push) Successful in 29s
CI / publish (push) Successful in 5s

This commit was merged in pull request #119.
This commit is contained in:
2026-09-21 21:45:11 +02:00
parent 7f290d220d
commit 67c3345fd2
14 changed files with 96 additions and 84 deletions
+4 -4
View File
@@ -1,9 +1,9 @@
import { blockNodeNamed } from '../adf/block-nodes.ts'
import { blockNodeModel } from '../adf/block-nodes.ts'
import { listBreakName } from './list-break.ts'
export function blockDirectiveForm(name: string): 'container' | 'leaf' | undefined {
if (name === listBreakName) return 'leaf'
const blockNode = blockNodeNamed(name)
if (blockNode === undefined) return undefined
return blockNode.contentModel === 'none' ? 'leaf' : 'container'
const model = blockNodeModel(name)
if (model === undefined) return undefined
return model.contentModel === 'none' ? 'leaf' : 'container'
}
+13 -13
View File
@@ -1,8 +1,8 @@
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
import type { BlockNode } from '../../adf/block-nodes.ts'
import type { BlockNodeModel } from '../../adf/block-nodes.ts'
import { adfDocumentFault, carriesOnly, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
import { blockDirectiveForm } from '../block-directive-forms.ts'
import { blockNodeNamed, blockNodes } from '../../adf/block-nodes.ts'
import { blockNodeModel, blockNodes } from '../../adf/block-nodes.ts'
import { carriedBlock } from '../opaque-carry.ts'
import { emitInlineLine } from './inline-line.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
@@ -87,11 +87,11 @@ function interruptsParagraph(node: AdfNode): boolean {
}
function emitBlock(node: AdfNode, path: ConvertErrorPath, depth: number, memo: SpellingMemo | undefined): Result<EmittedBlock> {
const blockNode = blockNodeNamed(node.type)
if (blockNode === undefined) return commonMarkLine(carriedBlock(node, path, depth))
const model = blockNodeModel(node.type)
if (model === undefined) return commonMarkLine(carriedBlock(node, path, depth))
const readable = readableBlock(node, path, depth, memo)
if (readable !== undefined) return readable
return emitDirectiveBlock(node, blockNode, path, depth, () => walkBlocks(nodeContent(node), path, depth + 1, memo))
return emitDirectiveBlock(node, model, path, depth, () => walkBlocks(nodeContent(node), path, depth + 1, memo))
}
export function commonMarkSpelling(node: AdfNode, path: ConvertErrorPath, depth: number, memo: SpellingMemo): Result<null> | undefined {
@@ -143,18 +143,18 @@ function directivePair(node: AdfNode, opener: string, body: string, headroom: nu
return { headroom, spelling: 'directive', text: `${opener}\n${body === '' ? '' : `${body}\n`}${spellDirectiveCloser(node.type)}` }
}
function emitDirectiveBlock(node: AdfNode, blockNode: BlockNode, path: ConvertErrorPath, depth: number, walkBody: () => Result<Walk>): Result<EmittedBlock> {
function emitDirectiveBlock(node: AdfNode, model: BlockNodeModel, path: ConvertErrorPath, depth: number, walkBody: () => Result<Walk>): Result<EmittedBlock> {
if (node.text !== undefined) return failure('unsupported-node-shape', `a ${node.type} carries no text: this one holds text`, path)
if (blockDirectiveForm(node.type) === 'leaf' && nodeContent(node).length > 0) return failure('unsupported-node-shape', `a ${node.type} holds no content: this one holds some`, path)
if (blockNode.contentModel === 'code') return emitCodeDirective(node, blockNode, path, depth)
const opener = spellBlockDirectiveOpener(node, blockNode)
if (model.contentModel === 'code') return emitCodeDirective(node, model, path, depth)
const opener = spellBlockDirectiveOpener(node, model)
if (opener === undefined) return commonMarkLine(carriedBlock(node, path, depth))
return emitDirectiveBody(node, blockNode, opener, path, walkBody)
return emitDirectiveBody(node, model, opener, path, walkBody)
}
function emitDirectiveBody(node: AdfNode, blockNode: BlockNode, opener: string, path: ConvertErrorPath, walkBody: () => Result<Walk>): Result<EmittedBlock> {
function emitDirectiveBody(node: AdfNode, model: BlockNodeModel, opener: string, path: ConvertErrorPath, walkBody: () => Result<Walk>): Result<EmittedBlock> {
if (blockDirectiveForm(node.type) === 'leaf') return success({ headroom: Number.POSITIVE_INFINITY, spelling: 'directive', text: opener })
if (blockNode.contentModel === 'inline') {
if (model.contentModel === 'inline') {
const line = emitInlineLine(nodeContent(node), 'paragraph', path)
if (!line.ok) return line
return success(directivePair(node, opener, line.value))
@@ -184,9 +184,9 @@ function tryCodeBlock(node: AdfNode, path: ConvertErrorPath): Result<EmittedBloc
return success(commonMarkText(fencedCodeBlock(slot.kind === 'fence' ? slot.info : '', text.value)))
}
function emitCodeDirective(node: AdfNode, blockNode: BlockNode, path: ConvertErrorPath, depth: number): Result<EmittedBlock> {
function emitCodeDirective(node: AdfNode, model: BlockNodeModel, path: ConvertErrorPath, depth: number): Result<EmittedBlock> {
const slot = languageSlot(nodeAttrs(node)['language'])
const opener = spellBlockDirectiveOpener(node, blockNode, slot.kind === 'attribute' ? [] : ['language'])
const opener = spellBlockDirectiveOpener(node, model, slot.kind === 'attribute' ? [] : ['language'])
if (opener === undefined) return commonMarkLine(carriedBlock(node, path, depth))
const text = codeBlockText(node, path)
if (!text.ok) return text
@@ -1,17 +1,17 @@
import type { AdfNode } from '../../adf/document.ts'
import type { BlockNode } from '../../adf/block-nodes.ts'
import type { BlockNodeModel } from '../../adf/block-nodes.ts'
import { blockArgument } from '../block-directive-arguments.ts'
import { isBareToken, spellAttributes, spellDirectiveOpener, spellJsonAttribute, spellVocabulary } from '../directive-syntax.ts'
import { markValues, marksAttribute } from '../block-directive-marks.ts'
import { nodeAttrs, nodeMarks } from '../../adf/document.ts'
import { vocabularyPairs } from '../../adf/attribute-vocabulary.ts'
export function spellBlockDirectiveOpener(node: AdfNode, blockNode: BlockNode, spelledByBody: readonly string[] = []): string | undefined {
export function spellBlockDirectiveOpener(node: AdfNode, model: BlockNodeModel, spelledByBody: readonly string[] = []): string | undefined {
const argumentAttribute = blockArgument(node.type)
const slot = bareArgument(node, argumentAttribute)
if (slot === undefined) return undefined
const spelled = argumentAttribute === undefined ? spelledByBody : [argumentAttribute, ...spelledByBody]
const pairs = vocabularyPairs(nodeAttrs(node), blockNode.attributes, spelled)
const pairs = vocabularyPairs(nodeAttrs(node), model.attributes, spelled)
if (pairs === undefined) return undefined
const spelledPairs = spellVocabulary(pairs)
const marks = nodeMarks(node)
@@ -1,10 +1,10 @@
import type { AdfNode } from '../../adf/document.ts'
import type { InlineNode } from '../../adf/inline-nodes.ts'
import type { InlineNodeModel } from '../../adf/inline-nodes.ts'
import { nodeAttrs } from '../../adf/document.ts'
import { spellAttributes, spellVocabulary } from '../directive-syntax.ts'
import { vocabularyPairs } from '../../adf/attribute-vocabulary.ts'
export function spellInlineNodeAttributes(node: AdfNode, inlineNode: InlineNode): string | undefined {
const pairs = vocabularyPairs(nodeAttrs(node), inlineNode.attributes, inlineNode.textAttribute === undefined ? [] : [inlineNode.textAttribute])
export function spellInlineNodeAttributes(node: AdfNode, model: InlineNodeModel): string | undefined {
const pairs = vocabularyPairs(nodeAttrs(node), model.attributes, model.textAttribute === undefined ? [] : [model.textAttribute])
return pairs === undefined ? undefined : spellAttributes(spellVocabulary(pairs))
}
+12 -12
View File
@@ -1,12 +1,12 @@
import type { AdfMark, AdfNode } from '../../adf/document.ts'
import type { InlineNode } from '../../adf/inline-nodes.ts'
import type { InlineNodeModel } from '../../adf/inline-nodes.ts'
import { assembleInlineLine, isSyntax, type InlineEscaping, type InlineSegment, type LineContainer, type NodeRange } from './line-escaping.ts'
import { carriedInline } from '../opaque-carry.ts'
import { claimsLine, holdsNullCharacter, trimTrailingSpace } from '../commonmark/grammar.ts'
import { commonMarkLink, linkHref, markSpelling, spellMarkAttributes } from '../mark-spellings.ts'
import { escapeUnbalanced, spellDestination } from '../commonmark/link-syntax.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { inlineNodeNamed } from '../../adf/inline-nodes.ts'
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
import { largestNesting } from '../../nesting.ts'
import { longestBacktickRun } from '../commonmark/backtick-runs.ts'
import { nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
@@ -196,7 +196,7 @@ function nodePath(context: InlineContext, index: number): ConvertErrorPath {
function carries(node: AdfNode, carried: ReadonlySet<number>, index: number): boolean {
if (carried.has(index)) return true
return node.type !== 'text' && inlineNodeNamed(node.type) === undefined
return node.type !== 'text' && inlineNodeModel(node.type) === undefined
}
function emitLeaf(node: AdfNode, context: InlineContext, index: number): Result<Emission> {
@@ -208,27 +208,27 @@ function emitLeaf(node: AdfNode, context: InlineContext, index: number): Result<
}
const types = nodeMarks(node).map((mark) => mark.type)
if (new Set(types).size !== types.length) return failure('unsupported-node-shape', `a ${node.type} node carries one mark type twice`, path)
const inlineNode = inlineNodeNamed(node.type)
if (inlineNode === undefined) return emitText(node, context, index, path)
if (node.type === 'hardBreak') return emitHardBreak(node, inlineNode, context, index, path)
return emitInlineDirective(node, inlineNode, index, path)
const model = inlineNodeModel(node.type)
if (model === undefined) return emitText(node, context, index, path)
if (node.type === 'hardBreak') return emitHardBreak(node, model, context, index, path)
return emitInlineDirective(node, model, index, path)
}
function emitHardBreak(node: AdfNode, inlineNode: InlineNode, context: InlineContext, index: number, path: ConvertErrorPath): Result<Emission> {
function emitHardBreak(node: AdfNode, model: InlineNodeModel, context: InlineContext, index: number, path: ConvertErrorPath): Result<Emission> {
const empty = refuseContentAndText(node, path)
if (!empty.ok) return empty
const attributes = spellInlineNodeAttributes(node, inlineNode)
const attributes = spellInlineNodeAttributes(node, model)
if (attributes === undefined) return success({ carry: { first: index, last: index } })
if (attributes === '' && context.spansLines && !context.atBlockEnd) return success({ segments: [syntax('\\\n')] })
return success({ segments: [syntax(spellInlineLeafDirective('hardBreak', attributes))] })
}
function emitInlineDirective(node: AdfNode, inlineNode: InlineNode, index: number, path: ConvertErrorPath): Result<Emission> {
function emitInlineDirective(node: AdfNode, model: InlineNodeModel, index: number, path: ConvertErrorPath): Result<Emission> {
const empty = refuseContentAndText(node, path)
if (!empty.ok) return empty
const attributes = spellInlineNodeAttributes(node, inlineNode)
const attributes = spellInlineNodeAttributes(node, model)
if (attributes === undefined) return success({ carry: { first: index, last: index } })
const slot = inlineNode.textAttribute === undefined ? undefined : nodeAttrs(node)[inlineNode.textAttribute]
const slot = model.textAttribute === undefined ? undefined : nodeAttrs(node)[model.textAttribute]
if (slot === undefined) return success({ segments: [syntax(spellInlineLeafDirective(node.type, attributes))] })
if (typeof slot !== 'string') return success({ carry: { first: index, last: index } })
const spans = slotLineEndingFault(node.type, slot)
+13 -13
View File
@@ -1,5 +1,5 @@
import type { AdfAttributes, AdfMark, AdfNode } from '../../adf/document.ts'
import type { BlockNode } from '../../adf/block-nodes.ts'
import type { BlockNodeModel } from '../../adf/block-nodes.ts'
import type { ConvertFault } from '../../result.ts'
import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts'
import type { Elsewhere } from './directive-attributes.ts'
@@ -7,17 +7,17 @@ import { attributeNestingMessage, nodeAttrs, nodeContent, nodeMarks } from '../.
import { attributeValue, directivePrefix, spellAttributeValue, unknownDirectiveFault } from '../directive-syntax.ts'
import { blockArgument } from '../block-directive-arguments.ts'
import { blockDirectiveForm } from '../block-directive-forms.ts'
import { blockNodeNamed } from '../../adf/block-nodes.ts'
import { blockNodeModel } from '../../adf/block-nodes.ts'
import { carryName } from '../opaque-carry.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { inlineMarkSpellingFault } from './directive-marks.ts'
import { inlineNodeNamed } from '../../adf/inline-nodes.ts'
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
import { marksAttribute, readMarkValues } from '../block-directive-marks.ts'
import { readVocabulary } from './directive-attributes.ts'
import { slotLineEndingFault } from '../directive-syntax.ts'
import { textDirectiveName } from '../text-directive.ts'
export type BlockDirectiveNode = { contentModel: BlockNode['contentModel']; node: AdfNode }
export type BlockDirectiveNode = { contentModel: BlockNodeModel['contentModel']; node: AdfNode }
export function readBlockDirectiveNode(
name: string,
@@ -28,13 +28,13 @@ export function readBlockDirectiveNode(
if (name === carryName) {
return failure('malformed-directive', `the name ${carryName} is reserved for the opaque carry, whose block form is the ${carryName} fence`, path)
}
const blockNode = blockNodeNamed(name)
if (blockNode === undefined) return faulted(inlineSpellingFault(name) ?? unknownDirectiveFault(name), path)
const model = blockNodeModel(name)
if (model === undefined) return faulted(inlineSpellingFault(name) ?? unknownDirectiveFault(name), path)
const argumentKey = blockArgument(name)
const rest = new Map(attributes)
rest.delete(marksAttribute)
const elsewhere: Elsewhere | undefined = argumentKey === undefined ? undefined : { key: argumentKey, slot: 'argument' }
const attrs = readVocabulary(name, rest, blockNode.attributes, elsewhere, path)
const attrs = readVocabulary(name, rest, model.attributes, elsewhere, path)
if (!attrs.ok) return attrs
if (argument !== undefined) {
if (argumentKey === undefined) return failure('unsupported-node-shape', `${name} takes no argument: this one spells one`, path)
@@ -43,7 +43,7 @@ export function readBlockDirectiveNode(
const spelled = attributes.get(marksAttribute)
const marks: Result<AdfMark[] | undefined> = spelled === undefined ? success(undefined) : readMarks(name, spelled, path)
if (!marks.ok) return marks
return success({ contentModel: blockNode.contentModel, node: namedNode(name, attrs.value, marks.value) })
return success({ contentModel: model.contentModel, node: namedNode(name, attrs.value, marks.value) })
}
export function readInlineDirectiveNode(
@@ -52,12 +52,12 @@ export function readInlineDirectiveNode(
content: readonly AdfNode[] | undefined,
path: ConvertErrorPath,
): Result<AdfNode> {
const inlineNode = inlineNodeNamed(name)
if (inlineNode === undefined) return faulted(blockSpellingFault(name) ?? unknownDirectiveFault(name), path)
const slot = inlineNode.textAttribute
const model = inlineNodeModel(name)
if (model === undefined) return faulted(blockSpellingFault(name) ?? unknownDirectiveFault(name), path)
const slot = model.textAttribute
if (slot === undefined && content !== undefined) return failure('unsupported-node-shape', `${name} takes no content: this one holds some`, path)
const elsewhere: Elsewhere | undefined = slot === undefined ? undefined : { key: slot, slot: 'content' }
const attrs = readVocabulary(name, attributes, inlineNode.attributes, elsewhere, path)
const attrs = readVocabulary(name, attributes, model.attributes, elsewhere, path)
if (!attrs.ok) return attrs
if (slot !== undefined && content !== undefined) {
const text = slotText(content)
@@ -75,7 +75,7 @@ export function readInlineDirectiveNode(
function inlineSpellingFault(name: string): ConvertFault | undefined {
const mark = inlineMarkSpellingFault(name)
if (mark !== undefined) return mark
if (inlineNodeNamed(name) === undefined && name !== textDirectiveName) return undefined
if (inlineNodeModel(name) === undefined && name !== textDirectiveName) return undefined
return { code: 'unsupported-node-shape', message: `${name} takes the inline form, ${directivePrefix}${name}{…}, never the block form` }
}
+2 -2
View File
@@ -8,7 +8,7 @@ import { backtickRun, closingBacktickRun } from '../commonmark/backtick-runs.ts'
import { commonMarkLink, linkHref } from '../mark-spellings.ts'
import { delimiterFlags, matchEmphasis, runLength } from '../commonmark/emphasis-matching.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { inlineNodeNamed } from '../../adf/inline-nodes.ts'
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
import { mergeAdjacentText } from '../../adf/editor-normal.ts'
import { noSpans, readInlineDirective } from '../directive-syntax.ts'
import { nodeAttrs, nodeMarks } from '../../adf/document.ts'
@@ -407,7 +407,7 @@ function imageAlt(inner: readonly Piece[], path: ConvertErrorPath): Result<strin
// spec/flavour.md, The CommonMark image: the description's plain text, the content slot included.
function altText(node: AdfNode): string {
if (node.type === 'hardBreak') return ' '
const slot = inlineNodeNamed(node.type)?.textAttribute
const slot = inlineNodeModel(node.type)?.textAttribute
const spelled = slot === undefined ? undefined : nodeAttrs(node)[slot]
return typeof spelled === 'string' ? spelled : (node.text ?? '')
}