Answer the stability pass: the misleading spellings, and only the form the emitter picks
CI / gate (push) Successful in 5s
CI / gate (push) Successful in 5s
This commit is contained in:
@@ -5,11 +5,11 @@ import type { DirectiveAttributes, DirectiveLine } from './directive-syntax.ts'
|
||||
import { largestNesting } from '../nesting.ts'
|
||||
import { readDirectiveLine, readInlineDirective } from './directive-syntax.ts'
|
||||
|
||||
// A pair the input spells bare reads its own text back; a quoted one names the spelling beside it.
|
||||
// A pair the input spells bare decodes to itself; a quoted one names its spelling beside the decoding.
|
||||
type Pair = [string, string, string?]
|
||||
|
||||
function attributes(...pairs: Pair[]): DirectiveAttributes {
|
||||
return new Map(pairs.map(([key, text, spelling]) => [key, { spelling: spelling ?? text, text }]))
|
||||
return new Map(pairs.map(([key, decoded, spelling]) => [key, { decoded, spelling: spelling ?? decoded }]))
|
||||
}
|
||||
|
||||
function header(colons: number, name: string, argument?: string, ...pairs: Pair[]): { value: DirectiveLine } {
|
||||
|
||||
@@ -8,8 +8,7 @@ import { largestNesting } from '../nesting.ts'
|
||||
import { runLength } from './emphasis-matching.ts'
|
||||
import { serializeCanonicalJson } from '../canonical-json.ts'
|
||||
|
||||
// The value as the input spells it, beside the string the grammar decodes it to.
|
||||
export type DirectiveValue = { spelling: string; text: string }
|
||||
export type DirectiveValue = { decoded: string; spelling: string }
|
||||
|
||||
export type DirectiveAttributes = ReadonlyMap<string, DirectiveValue>
|
||||
|
||||
@@ -80,7 +79,7 @@ export function readInlineDirective(text: string, index: number): Read<Directive
|
||||
|
||||
export function spellAttributes(pairs: readonly (readonly [string, string])[]): string {
|
||||
if (pairs.length === 0) return ''
|
||||
const spelled = [...pairs].sort(([left], [right]) => (left < right ? -1 : 1)).map(([key, value]) => `${key}=${value}`)
|
||||
const spelled = [...pairs].sort(([left], [right]) => keyOrder(left, right)).map(([key, value]) => `${key}=${value}`)
|
||||
return `{${spelled.join(' ')}}`
|
||||
}
|
||||
|
||||
@@ -107,6 +106,10 @@ export function unknownDirectiveFault(name: string): ConvertFault {
|
||||
return { code: 'unknown-directive-name', message: `the directive name ${name} reads back to no node` }
|
||||
}
|
||||
|
||||
function keyOrder(left: string, right: string): number {
|
||||
return left < right ? -1 : 1
|
||||
}
|
||||
|
||||
function quote(text: string): string {
|
||||
return JSON.stringify(text).replace(quotedEscapes, (character) => `\\u${escapeDigits(character)}`)
|
||||
}
|
||||
@@ -221,7 +224,7 @@ function readAttributes(text: string, index: number): Read<Attributes> {
|
||||
if (pair.fault !== undefined) return { fault: pair.fault }
|
||||
const key = pair.value.key
|
||||
if (attributes.has(key)) return { fault: malformedDirective(`the attribute key ${key} is spelled twice`) }
|
||||
if (key < previous) return { fault: malformedDirective(`${orderFault}: ${key} before ${previous}`) }
|
||||
if (keyOrder(previous, key) > 0) return { fault: malformedDirective(`${orderFault}: ${key} before ${previous}`) }
|
||||
previous = key
|
||||
attributes.set(key, pair.value.value)
|
||||
cursor = pair.value.end
|
||||
@@ -243,7 +246,7 @@ function readAttributePair(text: string, index: number): Read<AttributePair> {
|
||||
bareRun.lastIndex = start
|
||||
const bare = bareRun.exec(text)?.[0]
|
||||
if (bare === undefined) return { fault: malformedDirective(pairFault) }
|
||||
return { value: { end: start + bare.length, key, value: { spelling: bare, text: bare } } }
|
||||
return { value: { end: start + bare.length, key, value: { decoded: bare, spelling: bare } } }
|
||||
}
|
||||
|
||||
function readQuotedValue(text: string, index: number): Read<{ end: number; value: DirectiveValue }> {
|
||||
@@ -257,7 +260,7 @@ function readQuotedValue(text: string, index: number): Read<{ end: number; value
|
||||
}
|
||||
const parsed = parseJson(spelling)
|
||||
if (typeof parsed !== 'string') return { fault: malformedDirective('the {attrs} quoted value is not a JSON string') }
|
||||
return { value: { end: cursor + 1, value: { spelling, text: parsed } } }
|
||||
return { value: { end: cursor + 1, value: { decoded: parsed, spelling } } }
|
||||
}
|
||||
|
||||
function parseJson(raw: string): JsonValue | undefined {
|
||||
|
||||
@@ -86,6 +86,11 @@ function emitBlock(node: AdfNode, path: ConvertErrorPath, depth: number): Result
|
||||
return emitDirectiveBlock(node, directive, path, depth)
|
||||
}
|
||||
|
||||
// The plain-versus-directive choice is the emitter's; input reads it back rather than restating it (AGENTS.md §11).
|
||||
export function spellsCommonMark(node: AdfNode, path: ConvertErrorPath, depth: number): boolean {
|
||||
return readableBlock(node, path, depth) !== undefined
|
||||
}
|
||||
|
||||
function readableBlock(node: AdfNode, path: ConvertErrorPath, depth: number): Result<EmittedBlock> | undefined {
|
||||
if (node.type === 'blockquote') return emitBlockquote(node, path, depth)
|
||||
if (node.type === 'bulletList' || node.type === 'orderedList') return emitList(node, path, depth)
|
||||
|
||||
@@ -90,7 +90,7 @@ test('holds a directive container open until the fence that closes it', () => {
|
||||
assert.deepEqual(parseBlocks(':::panel info {panelColor="#ff0000"}\nPart.\n:::\n').blocks, [
|
||||
{
|
||||
argument: 'info',
|
||||
attributes: new Map([['panelColor', { spelling: '"#ff0000"', text: '#ff0000' }]]),
|
||||
attributes: new Map([['panelColor', { decoded: '#ff0000', spelling: '"#ff0000"' }]]),
|
||||
blocks: [{ kind: 'paragraph', text: 'Part.' }],
|
||||
kind: 'directive',
|
||||
name: 'panel',
|
||||
|
||||
@@ -32,7 +32,7 @@ export function readBlockDirectiveNode(
|
||||
const attrs = readVocabulary(name, rest, directive.attributes, elsewhere, path)
|
||||
if (!attrs.ok) return attrs
|
||||
if (argument !== undefined) {
|
||||
if (argumentKey === undefined) return failure('unsupported-node-shape', `a ${name} takes no argument`, path)
|
||||
if (argumentKey === undefined) return failure('unsupported-node-shape', `${name} takes no argument`, path)
|
||||
attrs.value[argumentKey] = argument
|
||||
}
|
||||
const spelled = attributes.get(marksAttribute)
|
||||
@@ -46,7 +46,7 @@ export function readInlineDirectiveNode(span: DirectiveSpan, path: ConvertErrorP
|
||||
if (directive === undefined) return faulted(unknownDirectiveFault(span.name), path)
|
||||
const slot = directive.textAttribute
|
||||
if (span.content !== undefined) {
|
||||
const message = slot === undefined ? `a ${span.name} takes no content` : `the content slot a ${span.name} spells its ${slot} attribute in is unsupported`
|
||||
const message = slot === undefined ? `${span.name} takes no content` : `the content slot ${span.name} spells its ${slot} attribute in is unsupported`
|
||||
return failure('unsupported-node-shape', message, path)
|
||||
}
|
||||
const elsewhere: Elsewhere | undefined = slot === undefined ? undefined : { key: slot, slot: 'content' }
|
||||
@@ -66,24 +66,24 @@ function readVocabulary(
|
||||
for (const [key, spelled] of attributes) {
|
||||
if (key === elsewhere?.key) {
|
||||
const place = elsewhere.slot === 'argument' ? 'as the directive argument' : 'in the content slot'
|
||||
return failure('unsupported-node-shape', `a ${type} spells its ${key} attribute ${place}`, path)
|
||||
return failure('unsupported-node-shape', `${type} spells its ${key} attribute ${place}`, path)
|
||||
}
|
||||
const kind = Object.hasOwn(vocabulary, key) ? vocabulary[key] : undefined
|
||||
if (kind === undefined) return failure('unsupported-node-shape', `a ${type} holds no ${key} attribute`, path)
|
||||
const read = attributeValue(spelled.text, kind)
|
||||
if (read === undefined) return failure('unsupported-node-shape', `the ${key} attribute of a ${type} is a ${kind}`, path)
|
||||
if (kind === undefined) return failure('unsupported-node-shape', `${type} holds no ${key} attribute`, path)
|
||||
const read = attributeValue(spelled.decoded, kind)
|
||||
if (read === undefined) return failure('unsupported-node-shape', `the ${key} attribute of ${type} is no ${kind}`, path)
|
||||
const spelling = spellAttributeValue(read)
|
||||
if (spelling !== spelled.spelling) return failure('unsupported-node-shape', `a ${type} spells its ${key} attribute as ${key}=${spelling}`, path)
|
||||
if (spelling !== spelled.spelling) return failure('unsupported-node-shape', `${type} spells its ${key} attribute as ${key}=${spelling}`, path)
|
||||
attrs[key] = read.value
|
||||
}
|
||||
return success(attrs)
|
||||
}
|
||||
|
||||
function readMarks(type: string, spelled: DirectiveValue, path: ConvertErrorPath): Result<AdfMark[]> {
|
||||
const read = attributeValue(spelled.text, 'json')
|
||||
const read = attributeValue(spelled.decoded, 'json')
|
||||
const marks = read === undefined || spellAttributeValue(read) !== spelled.spelling ? undefined : readMarkValues(read.value)
|
||||
if (marks === undefined) {
|
||||
return failure('unsupported-node-shape', `the ${marksAttribute} attribute of a ${type} is its marks array in canonical JSON`, path)
|
||||
return failure('unsupported-node-shape', `the ${marksAttribute} attribute of ${type} is its marks array in canonical JSON`, path)
|
||||
}
|
||||
return success(marks)
|
||||
}
|
||||
|
||||
@@ -136,16 +136,30 @@ test('claims a block-level colon run with no directive to parse it', () => {
|
||||
})
|
||||
|
||||
test('reads the three directive forms into the nodes the tables name', () => {
|
||||
assert.deepEqual(content(markdownToAdf('::rule\n')), [{ type: 'rule' }])
|
||||
assert.deepEqual(content(markdownToAdf('::rule {localId=a-1}\n')), [{ attrs: { localId: 'a-1' }, type: 'rule' }])
|
||||
assert.deepEqual(content(markdownToAdf('::paragraph\n')), [{ type: 'paragraph' }])
|
||||
assert.deepEqual(content(markdownToAdf(' :::panel info\nPart.\n:::\n')), [
|
||||
{ attrs: { panelType: 'info' }, content: [paragraph('Part.')], type: 'panel' },
|
||||
])
|
||||
assert.deepEqual(content(markdownToAdf(':::blockquote\n:::\n')), [{ type: 'blockquote' }])
|
||||
assert.deepEqual(content(markdownToAdf(':::heading {level=2}\nPart.\n:::\n')), [{ attrs: { level: 2 }, content: [text('Part.')], type: 'heading' }])
|
||||
assert.deepEqual(content(markdownToAdf(':::blockquote {localId=a-1}\n:::\n')), [{ attrs: { localId: 'a-1' }, type: 'blockquote' }])
|
||||
assert.deepEqual(content(markdownToAdf(':::heading {level=2 localId=a-1}\nPart.\n:::\n')), [
|
||||
{ attrs: { level: 2, localId: 'a-1' }, content: [text('Part.')], type: 'heading' },
|
||||
])
|
||||
assert.deepEqual(content(markdownToAdf('Part:hardBreak{}.\n')), [{ content: [text('Part'), hardBreak(), text('.')], type: 'paragraph' }])
|
||||
})
|
||||
|
||||
// The emitter's plain-versus-directive choice, read backwards: only the form it picks parses.
|
||||
test('names the directive form a node CommonMark spells refuses', () => {
|
||||
const named = (type: string): string => `unsupported-node-shape: ${type} takes the CommonMark spelling, not the directive form`
|
||||
assert.equal(content(markdownToAdf('::rule\n')), named('rule'))
|
||||
assert.equal(content(markdownToAdf(':::blockquote\nPart.\n:::\n')), named('blockquote'))
|
||||
assert.equal(content(markdownToAdf(':::heading {level=2}\nPart.\n:::\n')), named('heading'))
|
||||
assert.equal(content(markdownToAdf(':::paragraph\nPart.\n:::\n')), named('paragraph'))
|
||||
assert.equal(content(markdownToAdf('::::bulletList\n:::listItem\nPart.\n:::\n::::\n')), named('bulletList'))
|
||||
// The item whose first line reads back as a thematic break keeps the directive form the emitter falls back to.
|
||||
assert.deepEqual(content(markdownToAdf('::::bulletList\n:::listItem\n---\n:::\n::::\n')), [bulletList(item({ type: 'rule' }))])
|
||||
})
|
||||
|
||||
test('names the directive name no node reads back to', () => {
|
||||
assert.equal(code(markdownToAdf(':::widget info\nx\n:::\n')), 'unknown-directive-name')
|
||||
assert.equal(content(markdownToAdf('::widget\n')), 'unknown-directive-name: the directive name widget reads back to no node')
|
||||
@@ -180,7 +194,7 @@ test('reads the reserved marks key as the node array it spells', () => {
|
||||
})
|
||||
|
||||
test('names the marks key no marks array reads back from', () => {
|
||||
const named = 'unsupported-node-shape: the marks attribute of a rule is its marks array in canonical JSON'
|
||||
const named = 'unsupported-node-shape: the marks attribute of rule is its marks array in canonical JSON'
|
||||
assert.equal(content(markdownToAdf('::rule {marks="[]"}\n')), named)
|
||||
assert.equal(content(markdownToAdf('::rule {marks="[1]"}\n')), named)
|
||||
assert.equal(content(markdownToAdf('::rule {marks="{}"}\n')), named)
|
||||
@@ -189,36 +203,36 @@ test('names the marks key no marks array reads back from', () => {
|
||||
})
|
||||
|
||||
test('names the attribute a node holds no reading for', () => {
|
||||
assert.equal(content(markdownToAdf('::rule {bogus=1}\n')), 'unsupported-node-shape: a rule holds no bogus attribute')
|
||||
assert.equal(content(markdownToAdf('::media {width=wide}\n')), 'unsupported-node-shape: the width attribute of a media is a number')
|
||||
assert.equal(content(markdownToAdf(':::table {isNumberColumnEnabled=yes}\n:::\n')), 'unsupported-node-shape: the isNumberColumnEnabled attribute of a table is a boolean')
|
||||
assert.equal(content(markdownToAdf('::media {width=true}\n')), 'unsupported-node-shape: the width attribute of a media is a number')
|
||||
assert.equal(content(markdownToAdf(':::tableCell {colwidth="[340,"}\n:::\n')), 'unsupported-node-shape: the colwidth attribute of a tableCell is a json')
|
||||
assert.equal(content(markdownToAdf('::rule {bogus=1}\n')), 'unsupported-node-shape: rule holds no bogus attribute')
|
||||
assert.equal(content(markdownToAdf('::media {width=wide}\n')), 'unsupported-node-shape: the width attribute of media is no number')
|
||||
assert.equal(content(markdownToAdf(':::table {isNumberColumnEnabled=yes}\n:::\n')), 'unsupported-node-shape: the isNumberColumnEnabled attribute of table is no boolean')
|
||||
assert.equal(content(markdownToAdf('::media {width=true}\n')), 'unsupported-node-shape: the width attribute of media is no number')
|
||||
assert.equal(content(markdownToAdf(':::tableCell {colwidth="[340,"}\n:::\n')), 'unsupported-node-shape: the colwidth attribute of tableCell is no json')
|
||||
const deep = `${'['.repeat(largestNesting + 2)}${']'.repeat(largestNesting + 2)}`
|
||||
assert.equal(content(markdownToAdf(`:::tableCell {colwidth="${deep}"}\n:::\n`)), 'unsupported-node-shape: the colwidth attribute of a tableCell is a json')
|
||||
assert.equal(content(markdownToAdf(':::panel info {panelType=note}\nx\n:::\n')), 'unsupported-node-shape: a panel spells its panelType attribute as the directive argument')
|
||||
assert.equal(content(markdownToAdf('Part :mention{id=b1c2 text=A}.\n')), 'unsupported-node-shape: a mention spells its text attribute in the content slot')
|
||||
assert.equal(content(markdownToAdf(`:::tableCell {colwidth="${deep}"}\n:::\n`)), 'unsupported-node-shape: the colwidth attribute of tableCell is no json')
|
||||
assert.equal(content(markdownToAdf(':::panel info {panelType=note}\nx\n:::\n')), 'unsupported-node-shape: panel spells its panelType attribute as the directive argument')
|
||||
assert.equal(content(markdownToAdf('Part :mention{id=b1c2 text=A}.\n')), 'unsupported-node-shape: mention spells its text attribute in the content slot')
|
||||
})
|
||||
|
||||
test('names the attribute value spelled outside the canonical form', () => {
|
||||
assert.equal(content(markdownToAdf('::rule {localId="a-1"}\n')), 'unsupported-node-shape: a rule spells its localId attribute as localId=a-1')
|
||||
assert.equal(content(markdownToAdf('::media {width="20.0"}\n')), 'unsupported-node-shape: a media spells its width attribute as width=20')
|
||||
assert.equal(content(markdownToAdf(':::tableCell {colwidth="[340, 420]"}\n:::\n')), 'unsupported-node-shape: a tableCell spells its colwidth attribute as colwidth="[340,420]"')
|
||||
assert.equal(content(markdownToAdf('::rule {localId="a-1"}\n')), 'unsupported-node-shape: rule spells its localId attribute as localId=a-1')
|
||||
assert.equal(content(markdownToAdf('::media {width="20.0"}\n')), 'unsupported-node-shape: media spells its width attribute as width=20')
|
||||
assert.equal(content(markdownToAdf(':::tableCell {colwidth="[340, 420]"}\n:::\n')), 'unsupported-node-shape: tableCell spells its colwidth attribute as colwidth="[340,420]"')
|
||||
})
|
||||
|
||||
test('names the argument and the body a node takes no reading for', () => {
|
||||
assert.equal(content(markdownToAdf('::rule x\n')), 'unsupported-node-shape: a rule takes no argument')
|
||||
assert.equal(content(markdownToAdf(':::rule\nPart.\n:::\n')), 'unsupported-node-shape: a rule holds no content')
|
||||
assert.equal(content(markdownToAdf('::bulletList\n')), 'unsupported-node-shape: a bulletList spells its body in the container form :::bulletList')
|
||||
assert.equal(content(markdownToAdf(':::paragraph\n:::\n')), 'unsupported-node-shape: an empty paragraph is the leaf form ::paragraph')
|
||||
assert.equal(content(markdownToAdf(':::paragraph\nOne.\n\nTwo.\n:::\n')), 'unsupported-node-shape: a paragraph takes one paragraph as its body')
|
||||
assert.equal(content(markdownToAdf(':::paragraph\n---\n:::\n')), 'unsupported-node-shape: a paragraph takes one paragraph as its body')
|
||||
assert.equal(content(markdownToAdf(':::codeBlock\n```\nx\n```\n:::\n')), 'unsupported-node-shape: the fenced body of a codeBlock is unsupported')
|
||||
assert.equal(content(markdownToAdf('::rule x\n')), 'unsupported-node-shape: rule takes no argument')
|
||||
assert.equal(content(markdownToAdf(':::rule\nPart.\n:::\n')), 'unsupported-node-shape: rule holds no content')
|
||||
assert.equal(content(markdownToAdf('::bulletList\n')), 'unsupported-node-shape: bulletList spells its body in the container form, :::')
|
||||
assert.equal(content(markdownToAdf(':::paragraph\n:::\n')), 'unsupported-node-shape: an empty paragraph takes the leaf form, ::')
|
||||
assert.equal(content(markdownToAdf(':::paragraph\nOne.\n\nTwo.\n:::\n')), 'unsupported-node-shape: paragraph takes one paragraph as its body')
|
||||
assert.equal(content(markdownToAdf(':::paragraph\n---\n:::\n')), 'unsupported-node-shape: paragraph takes one paragraph as its body')
|
||||
assert.equal(content(markdownToAdf(':::codeBlock\n```\nx\n```\n:::\n')), 'unsupported-node-shape: the fenced body of codeBlock is unsupported')
|
||||
assert.equal(content(markdownToAdf(':::paragraph\n\n:::\n')), 'unmappable-image: no ADF node carries an image inside a paragraph')
|
||||
assert.equal(content(markdownToAdf('Part :date[now]{timestamp=1}.\n')), 'unsupported-node-shape: a date takes no content')
|
||||
assert.equal(content(markdownToAdf('Part :date[now]{timestamp=1}.\n')), 'unsupported-node-shape: date takes no content')
|
||||
assert.equal(
|
||||
content(markdownToAdf('Part :emoji[x]{shortName=":x:"}.\n')),
|
||||
'unsupported-node-shape: the content slot a emoji spells its text attribute in is unsupported',
|
||||
'unsupported-node-shape: the content slot emoji spells its text attribute in is unsupported',
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
||||
import type { Block, DirectiveBlock } from './blocks.ts'
|
||||
import type { BlockDirectiveNode } from './directive-nodes.ts'
|
||||
import type { LinkDefinitions } from './inline-content.ts'
|
||||
import { carryName } from '../opaque-carry.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
@@ -7,6 +8,7 @@ import { largestNesting } from '../../nesting.ts'
|
||||
import { parseBlocks } from './blocks.ts'
|
||||
import { parseInlineContent } from './inline-content.ts'
|
||||
import { readBlockDirectiveNode } from './directive-nodes.ts'
|
||||
import { spellsCommonMark } from '../emit/adf-to-markdown.ts'
|
||||
|
||||
export function markdownToAdf(markdown: string): Result<AdfDocument> {
|
||||
const parsed = parseBlocks(markdown)
|
||||
@@ -54,22 +56,30 @@ function blockNode(block: Block, definitions: LinkDefinitions, path: ConvertErro
|
||||
function directiveNode(block: DirectiveBlock, definitions: LinkDefinitions, path: ConvertErrorPath, depth: number): Result<AdfNode> {
|
||||
const read = readBlockDirectiveNode(block.name, block.argument, block.attributes, path)
|
||||
if (!read.ok) return read
|
||||
const { contentModel, node } = read.value
|
||||
const blocks = block.blocks
|
||||
const built = directiveBody(read.value, block.blocks, definitions, path, depth)
|
||||
if (!built.ok) return built
|
||||
if (spellsCommonMark(built.value, path, depth)) {
|
||||
return failure('unsupported-node-shape', `${built.value.type} takes the CommonMark spelling, not the directive form`, path)
|
||||
}
|
||||
return built
|
||||
}
|
||||
|
||||
function directiveBody(read: BlockDirectiveNode, blocks: Block[] | undefined, definitions: LinkDefinitions, path: ConvertErrorPath, depth: number): Result<AdfNode> {
|
||||
const { contentModel, node } = read
|
||||
if (blocks === undefined) {
|
||||
if (contentModel === 'none' || contentModel === 'inline') return success(node)
|
||||
return failure('unsupported-node-shape', `a ${node.type} spells its body in the container form :::${node.type}`, path)
|
||||
return failure('unsupported-node-shape', `${node.type} spells its body in the container form, :::`, path)
|
||||
}
|
||||
if (contentModel === 'none') return failure('unsupported-node-shape', `a ${node.type} holds no content`, path)
|
||||
if (contentModel === 'code') return failure('unsupported-node-shape', `the fenced body of a ${node.type} is unsupported`, path)
|
||||
if (contentModel === 'none') return failure('unsupported-node-shape', `${node.type} holds no content`, path)
|
||||
if (contentModel === 'code') return failure('unsupported-node-shape', `the fenced body of ${node.type} is unsupported`, path)
|
||||
if (contentModel === 'block') return containerNode(node, blocks, definitions, path, depth)
|
||||
return inlineBodyNode(node, blocks, definitions, path)
|
||||
}
|
||||
|
||||
function inlineBodyNode(node: AdfNode, blocks: readonly Block[], definitions: LinkDefinitions, path: ConvertErrorPath): Result<AdfNode> {
|
||||
if (blocks.length === 0) return failure('unsupported-node-shape', `an empty ${node.type} is the leaf form ::${node.type}`, path)
|
||||
if (blocks.length === 0) return failure('unsupported-node-shape', `an empty ${node.type} takes the leaf form, ::`, path)
|
||||
const only = blocks.length === 1 ? blocks[0] : undefined
|
||||
if (only?.kind !== 'paragraph') return failure('unsupported-node-shape', `a ${node.type} takes one paragraph as its body`, path)
|
||||
if (only?.kind !== 'paragraph') return failure('unsupported-node-shape', `${node.type} takes one paragraph as its body`, path)
|
||||
return contentNode(node, only.text, definitions, path)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user