5c: report the depth cause from the guards, and make the publish converge
This commit is contained in:
@@ -6,7 +6,11 @@ import { adfDocumentFault, isAdfDocument } from './document.ts'
|
||||
import { largestNesting } from '../nesting.ts'
|
||||
|
||||
function fault(value: unknown): string {
|
||||
return adfDocumentFault(value) ?? 'accepted'
|
||||
return adfDocumentFault(value)?.message ?? 'accepted'
|
||||
}
|
||||
|
||||
function faultCode(value: unknown): string {
|
||||
return adfDocumentFault(value)?.code ?? 'accepted'
|
||||
}
|
||||
|
||||
function nested(levels: number): JsonValue {
|
||||
@@ -56,13 +60,15 @@ test('rejects a node whose shape ProseMirror JSON cannot hold', () => {
|
||||
assert.equal(isAdfDocument({ content: [{ attrs: [], type: 'paragraph' }], type: 'doc', version: 1 }), false)
|
||||
})
|
||||
|
||||
test('holds an attribute value to the levels the parser reads one at, the attrs object costing none', () => {
|
||||
assert.equal(isAdfDocument(withAttribute(nested(largestNesting))), true)
|
||||
assert.equal(isAdfDocument(withAttribute(nested(largestNesting + 1))), false)
|
||||
assert.equal(adfDocumentFault(withAttribute(nested(largestNesting + 1)), Number.POSITIVE_INFINITY), undefined)
|
||||
test('names the attribute nesting past the levels the parser reads one at, and still calls the value a document', () => {
|
||||
const deeper = (key: string, type: string): string => `the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries`
|
||||
assert.equal(fault(withAttribute(nested(largestNesting))), 'accepted')
|
||||
assert.equal(fault(withAttribute(nested(largestNesting + 1))), deeper('a', 'paragraph'))
|
||||
assert.equal(faultCode(withAttribute(nested(largestNesting + 1))), 'unsupported-nesting-depth')
|
||||
assert.equal(isAdfDocument(withAttribute(nested(largestNesting + 1))), true)
|
||||
const marked = { content: [{ marks: [{ attrs: { a: nested(largestNesting + 1) }, type: 'link' }], text: 'x', type: 'text' }], type: 'doc', version: 1 }
|
||||
assert.equal(isAdfDocument(marked), false)
|
||||
assert.equal(adfDocumentFault(marked, Number.POSITIVE_INFINITY), undefined)
|
||||
assert.equal(fault(marked), deeper('a', 'link'))
|
||||
assert.equal(isAdfDocument(marked), true)
|
||||
})
|
||||
|
||||
test('accepts the JSON values an attribute may hold', () => {
|
||||
|
||||
+60
-21
@@ -1,4 +1,5 @@
|
||||
import { isJsonValue, type JsonValue } from '../json-value.ts'
|
||||
import type { ConvertFault } from '../result.ts'
|
||||
import { isJsonValue, overNested, type JsonValue } from '../json-value.ts'
|
||||
import { largestNesting } from '../nesting.ts'
|
||||
|
||||
export type AdfAttributes = { [key: string]: JsonValue }
|
||||
@@ -26,19 +27,25 @@ const documentKeys = ['content', 'type', 'version']
|
||||
const markKeys = ['attrs', 'type']
|
||||
const nodeKeys = ['attrs', 'content', 'marks', 'text', 'type']
|
||||
|
||||
export function adfDocumentFault(value: unknown, levels: number = largestNesting): string | undefined {
|
||||
if (!isRecord(value)) return `an ADF document is an object: found ${describe(value)}`
|
||||
export function adfDocumentFault(value: unknown): ConvertFault | undefined {
|
||||
if (!isRecord(value)) return notADocument(`an ADF document is an object: found ${describe(value)}`)
|
||||
const extra = extraKey(value, documentKeys)
|
||||
if (extra !== undefined) return `an ADF document holds content, type and version alone: found the key ${extra}`
|
||||
if (!('type' in value)) return 'an ADF document holds type "doc": found no type field'
|
||||
if (value['type'] !== 'doc') return `an ADF document holds type "doc": found ${describe(value['type'])}`
|
||||
if (!('version' in value)) return 'an ADF document holds a version number: found no version field'
|
||||
if (extra !== undefined) return notADocument(`an ADF document holds content, type and version alone: found the key ${extra}`)
|
||||
if (!('type' in value)) return notADocument('an ADF document holds type "doc": found no type field')
|
||||
if (value['type'] !== 'doc') return notADocument(`an ADF document holds type "doc": found ${describe(value['type'])}`)
|
||||
if (!('version' in value)) return notADocument('an ADF document holds a version number: found no version field')
|
||||
const version = value['version']
|
||||
if (typeof version !== 'number' || !Number.isFinite(version)) return `an ADF document holds a version number: found ${describe(version)}`
|
||||
if (typeof version !== 'number' || !Number.isFinite(version)) return notADocument(`an ADF document holds a version number: found ${describe(version)}`)
|
||||
if (!('content' in value)) return undefined
|
||||
const content = value['content']
|
||||
if (!Array.isArray(content)) return `an ADF document's content is an array: found ${describe(content)}`
|
||||
return isNodeArray(content, levels) ? undefined : "an ADF document's content holds ADF nodes: one of them is not"
|
||||
const held: unknown = value['content']
|
||||
if (!Array.isArray(held)) return notADocument(`an ADF document's content is an array: found ${describe(held)}`)
|
||||
const content: readonly unknown[] = held
|
||||
if (!isNodeArray(content)) return notADocument("an ADF document's content holds ADF nodes: one of them is not")
|
||||
return nestingFault(content)
|
||||
}
|
||||
|
||||
export function attributeNestingMessage(key: string, type: string): string {
|
||||
return `the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries`
|
||||
}
|
||||
|
||||
export function carriesOnly(node: AdfNode, attributes: readonly string[]): boolean {
|
||||
@@ -46,28 +53,30 @@ export function carriesOnly(node: AdfNode, attributes: readonly string[]): boole
|
||||
return holdsOnly(node.attrs ?? {}, attributes)
|
||||
}
|
||||
|
||||
// Depth is the walks' business, not the shape's: the guard waves a deep document through as blocks and marks do.
|
||||
export function isAdfDocument(value: unknown): value is AdfDocument {
|
||||
return adfDocumentFault(value) === undefined
|
||||
const fault = adfDocumentFault(value)
|
||||
return fault === undefined || fault.code === 'unsupported-nesting-depth'
|
||||
}
|
||||
|
||||
export function isAdfNode(value: unknown): value is AdfNode {
|
||||
return isNodeArray([value], largestNesting)
|
||||
return isNodeArray([value])
|
||||
}
|
||||
|
||||
export function isAdfMark(value: unknown, levels: number = largestNesting): value is AdfMark {
|
||||
export function isAdfMark(value: unknown): value is AdfMark {
|
||||
if (!isRecord(value) || !holdsOnly(value, markKeys)) return false
|
||||
if (typeof value['type'] !== 'string') return false
|
||||
return !('attrs' in value) || isAttributes(value['attrs'], levels)
|
||||
return !('attrs' in value) || isAttributes(value['attrs'])
|
||||
}
|
||||
|
||||
function isNodeArray(value: readonly unknown[], levels: number): boolean {
|
||||
function isNodeArray(value: readonly unknown[]): value is readonly AdfNode[] {
|
||||
const pending: unknown[] = [...value]
|
||||
while (pending.length > 0) {
|
||||
const node = pending.pop()
|
||||
if (!isRecord(node) || !holdsOnly(node, nodeKeys)) return false
|
||||
if (typeof node['type'] !== 'string') return false
|
||||
if ('attrs' in node && !isAttributes(node['attrs'], levels)) return false
|
||||
if ('marks' in node && !isArrayOf(node['marks'], (mark): mark is AdfMark => isAdfMark(mark, levels))) return false
|
||||
if ('attrs' in node && !isAttributes(node['attrs'])) return false
|
||||
if ('marks' in node && !isArrayOf(node['marks'], isAdfMark)) return false
|
||||
if ('text' in node && typeof node['text'] !== 'string') return false
|
||||
if ('content' in node) {
|
||||
const content = node['content']
|
||||
@@ -78,19 +87,49 @@ function isNodeArray(value: readonly unknown[], levels: number): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
function nestingFault(nodes: readonly AdfNode[]): ConvertFault | undefined {
|
||||
const pending: AdfNode[] = [...nodes]
|
||||
while (pending.length > 0) {
|
||||
const node = pending.pop()
|
||||
if (node === undefined) continue
|
||||
const fault = attributesFault(node.attrs, node.type) ?? marksFault(node.marks)
|
||||
if (fault !== undefined) return fault
|
||||
pending.push(...(node.content ?? []))
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function marksFault(marks: readonly AdfMark[] | undefined): ConvertFault | undefined {
|
||||
for (const mark of marks ?? []) {
|
||||
const fault = attributesFault(mark.attrs, mark.type)
|
||||
if (fault !== undefined) return fault
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function attributesFault(attrs: AdfAttributes | undefined, type: string): ConvertFault | undefined {
|
||||
for (const [key, value] of Object.entries(attrs ?? {})) {
|
||||
if (overNested(value)) return { code: 'unsupported-nesting-depth', message: attributeNestingMessage(key, type) }
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function isArrayOf<T>(value: unknown, guard: (item: unknown) => item is T): value is T[] {
|
||||
return Array.isArray(value) && [...value].every(guard)
|
||||
}
|
||||
|
||||
// Per value, so an attribute reaches the same 500 levels the parser reads one at (AGENTS.md §11).
|
||||
function isAttributes(value: unknown, levels: number): value is AdfAttributes {
|
||||
return isRecord(value) && Object.values(value).every((held) => isJsonValue(held, levels))
|
||||
function isAttributes(value: unknown): value is AdfAttributes {
|
||||
return isRecord(value) && isJsonValue(value)
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function notADocument(message: string): ConvertFault {
|
||||
return { code: 'not-an-adf-document', message }
|
||||
}
|
||||
|
||||
function describe(value: unknown): string {
|
||||
if (typeof value === 'string') return JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}…` : value)
|
||||
if (typeof value === 'function') return 'a function'
|
||||
|
||||
+18
-8
@@ -2,22 +2,32 @@ import { largestNesting } from './nesting.ts'
|
||||
|
||||
export type JsonValue = JsonValue[] | boolean | null | number | string | { [key: string]: JsonValue }
|
||||
|
||||
export function isJsonValue(value: unknown, levels: number = largestNesting): value is JsonValue {
|
||||
const pending: { depth: number; item: unknown }[] = [{ depth: 0, item: value }]
|
||||
export function isJsonValue(value: unknown): value is JsonValue {
|
||||
const pending: unknown[] = [value]
|
||||
while (pending.length > 0) {
|
||||
const entry = pending.pop()
|
||||
if (entry === undefined) continue
|
||||
const { depth, item } = entry
|
||||
if (depth > levels) return false
|
||||
const item = pending.pop()
|
||||
if (item === null || typeof item === 'boolean' || typeof item === 'string') continue
|
||||
if (typeof item === 'number') {
|
||||
if (!Number.isFinite(item)) return false
|
||||
continue
|
||||
}
|
||||
// A hole is not a JSON value, and Array.prototype methods skip holes — spreading materialises them.
|
||||
if (Array.isArray(item)) for (const child of [...item]) pending.push({ depth: depth + 1, item: child })
|
||||
else if (typeof item === 'object') for (const child of Object.values(item)) pending.push({ depth: depth + 1, item: child })
|
||||
if (Array.isArray(item)) for (const child of [...item]) pending.push(child)
|
||||
else if (typeof item === 'object') for (const child of Object.values(item)) pending.push(child)
|
||||
else return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function overNested(value: JsonValue, levels: number = largestNesting): boolean {
|
||||
const pending: { depth: number; item: JsonValue }[] = [{ depth: 0, item: value }]
|
||||
while (pending.length > 0) {
|
||||
const entry = pending.pop()
|
||||
if (entry === undefined) continue
|
||||
const { depth, item } = entry
|
||||
if (depth > levels) return true
|
||||
if (Array.isArray(item)) for (const child of item) pending.push({ depth: depth + 1, item: child })
|
||||
else if (item !== null && typeof item === 'object') for (const child of Object.values(item)) pending.push({ depth: depth + 1, item: child })
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ import type { ConvertFault } from '../result.ts'
|
||||
import type { JsonValue } from '../json-value.ts'
|
||||
import { backslashEscape, claimsDirectiveLine } from './commonmark-grammar.ts'
|
||||
import { backtickRun, closingBacktickRun } from './backtick-runs.ts'
|
||||
import { isJsonValue } from '../json-value.ts'
|
||||
import { isJsonValue, overNested } from '../json-value.ts'
|
||||
import { largestNesting } from '../nesting.ts'
|
||||
import { runLength } from './emphasis-matching.ts'
|
||||
import { serializeCanonicalJson } from '../canonical-json.ts'
|
||||
|
||||
export type AttributeReading = { refusal: 'kind' | 'nesting'; value?: undefined } | { refusal?: undefined; value: VocabularyValue }
|
||||
|
||||
export type DirectiveValue = { decoded: string; spelling: string }
|
||||
|
||||
export type DirectiveAttributes = ReadonlyMap<string, DirectiveValue>
|
||||
@@ -45,18 +47,13 @@ const orderFault = 'the {attrs} keys read in alphabetical order'
|
||||
const pairFault = 'an attribute reads key=value, the value bare or double-quoted: this one does not'
|
||||
const shapeFault = `a directive line reads a name, one bare argument and {attrs}, one space apart: this one does not; ${directiveLineEscape}`
|
||||
|
||||
export function attributeNestingFault(text: string, kind: AttributeKind, key: string, type: string): ConvertFault | undefined {
|
||||
if (kind !== 'json' || parseJson(text, Number.POSITIVE_INFINITY) === undefined) return undefined
|
||||
return { code: 'unsupported-nesting-depth', message: `the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels the parser carries` }
|
||||
}
|
||||
|
||||
export function attributeValue(text: string, kind: AttributeKind): VocabularyValue | undefined {
|
||||
if (kind === 'string') return { kind, value: text }
|
||||
if (kind === 'boolean') return text === 'true' || text === 'false' ? { kind, value: text === 'true' } : undefined
|
||||
export function attributeValue(text: string, kind: AttributeKind): AttributeReading {
|
||||
if (kind === 'string') return { value: { kind, value: text } }
|
||||
if (kind === 'boolean') return text === 'true' || text === 'false' ? { value: { kind, value: text === 'true' } } : { refusal: 'kind' }
|
||||
const parsed = parseJson(text)
|
||||
if (parsed === undefined) return undefined
|
||||
if (kind === 'json') return { kind, value: parsed }
|
||||
return typeof parsed === 'number' ? { kind, value: parsed } : undefined
|
||||
if (parsed === undefined) return { refusal: 'kind' }
|
||||
if (kind === 'number') return typeof parsed === 'number' ? { value: { kind, value: parsed } } : { refusal: 'kind' }
|
||||
return overNested(parsed) ? { refusal: 'nesting' } : { value: { kind, value: parsed } }
|
||||
}
|
||||
|
||||
export function isBareToken(text: string): boolean {
|
||||
@@ -299,10 +296,10 @@ function readQuotedValue(text: string, index: number): Read<{ end: number; value
|
||||
return { value: { end: cursor + 1, value: { decoded: parsed, spelling } } }
|
||||
}
|
||||
|
||||
function parseJson(raw: string, levels: number = largestNesting): JsonValue | undefined {
|
||||
function parseJson(raw: string): JsonValue | undefined {
|
||||
try {
|
||||
const value: unknown = JSON.parse(raw)
|
||||
return isJsonValue(value, levels) ? value : undefined
|
||||
return isJsonValue(value) ? value : undefined
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -336,14 +336,15 @@ test('refuses marks and attributes nested deeper than the emitter carries', () =
|
||||
assert.equal(code(adfToMarkdown(document(paragraph({ marks, text: 'x', type: 'text' })))), 'unsupported-nesting-depth')
|
||||
let attrs: AdfMark['attrs'] = { depth: 'x' }
|
||||
for (let depth = 0; depth < 600; depth += 1) attrs = { depth: attrs }
|
||||
const deeper = `unsupported-nesting-depth: an attribute value nests deeper than the ${largestNesting} levels the emitter carries`
|
||||
assert.equal(markdown(adfToMarkdown(document(paragraph({ marks: [{ attrs, type: 'em' }], text: 'x', type: 'text' })))), deeper)
|
||||
const deeper = (key: string, type: string): string =>
|
||||
`unsupported-nesting-depth: the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries`
|
||||
assert.equal(markdown(adfToMarkdown(document(paragraph({ marks: [{ attrs, type: 'em' }], text: 'x', type: 'text' })))), deeper('depth', 'em'))
|
||||
const card = (levels: number): AdfNode => {
|
||||
let data: JsonValue = 1
|
||||
for (let level = 0; level < levels; level += 1) data = [data]
|
||||
return { attrs: { data, url: 'https://example.com/a' }, type: 'inlineCard' }
|
||||
}
|
||||
assert.equal(markdown(adfToMarkdown(document(paragraph(card(largestNesting + 1))))), deeper)
|
||||
assert.equal(markdown(adfToMarkdown(document(paragraph(card(largestNesting + 1))))), deeper('data', 'inlineCard'))
|
||||
assert.deepEqual(path(adfToMarkdown(document(paragraph(card(largestNesting + 1))))), [])
|
||||
const spelled = adfToMarkdown(document(paragraph(card(largestNesting))))
|
||||
assert.ok(spelled.ok, spelled.ok ? '' : spelled.error.message)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { adfDocumentFault, carriesOnly } from '../../adf/document.ts'
|
||||
import { blockDirective } from '../../adf/block-directives.ts'
|
||||
import { carriedBlock } from '../opaque-carry.ts'
|
||||
import { emitInlineLine } from './inline-line.ts'
|
||||
import { failure, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { fencedCodeBlock } from '../backtick-runs.ts'
|
||||
import { holdsNullCharacter, isThematicBreak, markerInterruptsParagraph } from '../commonmark-grammar.ts'
|
||||
import { languageSlot } from '../code-language.ts'
|
||||
@@ -24,10 +24,7 @@ const largestListMarker = 999999999
|
||||
|
||||
export function adfToMarkdown(document: AdfDocument): Result<string> {
|
||||
const fault = adfDocumentFault(document)
|
||||
if (fault !== undefined && adfDocumentFault(document, Number.POSITIVE_INFINITY) === undefined) {
|
||||
return failure('unsupported-nesting-depth', `an attribute value nests deeper than the ${largestNesting} levels the emitter carries`, [])
|
||||
}
|
||||
if (fault !== undefined) return failure('not-an-adf-document', fault, [])
|
||||
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(document.content ?? [], 'document', [], 0)
|
||||
if (!blocks.ok) return blocks
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { DirectiveSpan, Read } from './directive-syntax.ts'
|
||||
import type { JsonSpelling } from '../canonical-json.ts'
|
||||
import { failure, success, type ConvertErrorPath, type Result } from '../result.ts'
|
||||
import { isAdfNode } from '../adf/document.ts'
|
||||
import { isJsonValue } from '../json-value.ts'
|
||||
import { isJsonValue, overNested } from '../json-value.ts'
|
||||
import { fencedCodeBlock } from './backtick-runs.ts'
|
||||
import { largestNesting } from '../nesting.ts'
|
||||
import { malformedDirective, readSoleStringAttribute, spellAttributes, spellStringAttribute, unsupportedNodeShape } from './directive-syntax.ts'
|
||||
@@ -37,7 +37,7 @@ export function readCarriedInline(span: DirectiveSpan): Read<AdfNode> | undefine
|
||||
}
|
||||
|
||||
function carriedJson(node: AdfNode, spelling: JsonSpelling, path: ConvertErrorPath, levels: number): Result<string> {
|
||||
if (!isJsonValue(node, levels)) {
|
||||
if (!isJsonValue(node) || overNested(node, levels)) {
|
||||
return failure('unsupported-nesting-depth', `a carried node's JSON nests deeper than the ${levels} levels its position leaves`, path)
|
||||
}
|
||||
return success(serializeCanonicalJson(node, spelling))
|
||||
@@ -47,9 +47,8 @@ function readCarriedJson(raw: string, spelling: JsonSpelling, levels: number): R
|
||||
const parsed = parseJsonText(raw)
|
||||
if (parsed === undefined) return { fault: malformedDirective('the opaque carry holds invalid JSON') }
|
||||
const { value } = parsed
|
||||
if (!isJsonValue(value, levels)) {
|
||||
// Unbounded, the same walk parts the two causes one `false` holds (AGENTS.md §8).
|
||||
if (!isJsonValue(value, Number.POSITIVE_INFINITY)) return { fault: unsupportedNodeShape('the opaque carry holds a number JSON cannot spell') }
|
||||
if (!isJsonValue(value)) return { fault: unsupportedNodeShape('the opaque carry holds a number JSON cannot spell') }
|
||||
if (overNested(value, levels)) {
|
||||
return { fault: { code: 'unsupported-nesting-depth', message: `a carried node's JSON nests deeper than the ${levels} levels its position leaves` } }
|
||||
}
|
||||
if (serializeCanonicalJson(value, spelling) !== raw) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { AdfAttributes } from '../../adf/document.ts'
|
||||
import type { AttributeVocabulary } from '../../adf/attribute-vocabulary.ts'
|
||||
import type { DirectiveAttributes } from '../directive-syntax.ts'
|
||||
import { attributeNestingFault, attributeValue, spellAttributeValue } from '../directive-syntax.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { attributeNestingMessage } from '../../adf/document.ts'
|
||||
import { attributeValue, spellAttributeValue } from '../directive-syntax.ts'
|
||||
import { failure, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
|
||||
export type Elsewhere = { key: string; slot: 'argument' | 'content' }
|
||||
|
||||
@@ -22,14 +23,11 @@ export function readVocabulary(
|
||||
const kind = Object.hasOwn(vocabulary, key) ? vocabulary[key] : undefined
|
||||
if (kind === undefined) return failure('unsupported-node-shape', `${type} holds no ${key} attribute: this one spells it`, path)
|
||||
const read = attributeValue(spelled.decoded, kind)
|
||||
if (read === undefined) {
|
||||
const deep = attributeNestingFault(spelled.decoded, kind, key, type)
|
||||
if (deep !== undefined) return faulted(deep, path)
|
||||
return failure('unsupported-node-shape', `the ${key} attribute of ${type} is no ${kind}`, path)
|
||||
}
|
||||
const spelling = spellAttributeValue(read)
|
||||
if (read.refusal === 'nesting') return failure('unsupported-nesting-depth', attributeNestingMessage(key, type), path)
|
||||
if (read.value === undefined) return failure('unsupported-node-shape', `the ${key} attribute of ${type} is no ${kind}`, path)
|
||||
const spelling = spellAttributeValue(read.value)
|
||||
if (spelling !== spelled.spelling) return failure('unsupported-node-shape', `${type} spells its ${key} attribute as ${key}=${spelling}`, path)
|
||||
attrs[key] = read.value
|
||||
attrs[key] = read.value.value
|
||||
}
|
||||
return success(attrs)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import type { BlockDirective } from '../../adf/block-directives.ts'
|
||||
import type { ConvertFault } from '../../result.ts'
|
||||
import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts'
|
||||
import type { Elsewhere } from './directive-attributes.ts'
|
||||
import { attributeNestingFault, attributeValue, directiveLineEscape, inlineDirectiveEscape, spellAttributeValue, unknownDirectiveFault } from '../directive-syntax.ts'
|
||||
import { attributeNestingMessage } from '../../adf/document.ts'
|
||||
import { attributeValue, directiveLineEscape, inlineDirectiveEscape, spellAttributeValue, unknownDirectiveFault } from '../directive-syntax.ts'
|
||||
import { blockArgument } from '../block-directive-arguments.ts'
|
||||
import { blockDirective } from '../../adf/block-directives.ts'
|
||||
import { carryName } from '../opaque-carry.ts'
|
||||
@@ -94,9 +95,8 @@ function slotText(content: readonly AdfNode[]): string | undefined {
|
||||
|
||||
function readMarks(type: string, spelled: DirectiveValue, path: ConvertErrorPath): Result<AdfMark[]> {
|
||||
const read = attributeValue(spelled.decoded, 'json')
|
||||
const deep = read === undefined ? attributeNestingFault(spelled.decoded, 'json', marksAttribute, type) : undefined
|
||||
if (deep !== undefined) return faulted(deep, path)
|
||||
const marks = read === undefined || spellAttributeValue(read) !== spelled.spelling ? undefined : readMarkValues(read.value)
|
||||
if (read.refusal === 'nesting') return failure('unsupported-nesting-depth', attributeNestingMessage(marksAttribute, type), path)
|
||||
const marks = read.value === undefined || spellAttributeValue(read.value) !== spelled.spelling ? undefined : readMarkValues(read.value.value)
|
||||
if (marks === undefined) {
|
||||
return failure('unsupported-node-shape', `the ${marksAttribute} attribute of ${type} is its marks array in canonical JSON: this one is not`, path)
|
||||
}
|
||||
|
||||
@@ -437,7 +437,8 @@ test('names the attribute a node holds no reading for', () => {
|
||||
|
||||
test('names the depth an attribute value nests past, never the kind the JSON reads as', () => {
|
||||
const nested = (levels: number): string => `${'['.repeat(levels)}1${']'.repeat(levels)}`
|
||||
const deeper = (key: string, type: string): string => `unsupported-nesting-depth: the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels the parser carries`
|
||||
const deeper = (key: string, type: string): string =>
|
||||
`unsupported-nesting-depth: the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries`
|
||||
assert.equal(content(markdownToAdf(`:::tableCell {colwidth="${nested(largestNesting + 1)}"}\n:::\n`)), deeper('colwidth', 'tableCell'))
|
||||
assert.equal(content(markdownToAdf(`::rule {marks="${nested(largestNesting + 1)}"}\n`)), deeper('marks', 'rule'))
|
||||
assert.equal(content(markdownToAdf(`::media {width="${nested(largestNesting + 1)}"}\n`)), 'unsupported-node-shape: the width attribute of media is no number')
|
||||
|
||||
Reference in New Issue
Block a user