This commit is contained in:
@@ -93,7 +93,7 @@ code from a closed list a consumer may switch exhaustively, the message free tex
|
|||||||
node's place from the document root, the position where a parse read the refusal in its input.
|
node's place from the document root, the position where a parse read the refusal in its input.
|
||||||
A message reads `rule: violation` — a rule alone states a truth the reader must invert before it
|
A message reads `rule: violation` — a rule alone states a truth the reader must invert before it
|
||||||
reads as a failure — and where the flavour's claim refuses ordinary prose it names the escape that
|
reads as a failure — and where the flavour's claim refuses ordinary prose it names the escape that
|
||||||
unclaims the line.
|
unclaims the form claimed: `\:::` or `\|` for a line, `\:` for an inline directive.
|
||||||
Adding, removing or renaming a code is breaking, so a milestone meeting a new failure cause
|
Adding, removing or renaming a code is breaking, so a milestone meeting a new failure cause
|
||||||
reuses a code where one fits; the list is complete at `0.1.0`. A code names the
|
reuses a code where one fits; the list is complete at `0.1.0`. A code names the
|
||||||
cause; where one cause recurs across node types or across directions, one code covers them all and
|
cause; where one cause recurs across node types or across directions, one code covers them all and
|
||||||
|
|||||||
@@ -91,13 +91,14 @@ test('names the attributes no spelling reads', () => {
|
|||||||
assert.equal(fault('::panel {a=1'), 'the {attrs} closing brace is missing')
|
assert.equal(fault('::panel {a=1'), 'the {attrs} closing brace is missing')
|
||||||
assert.equal(fault('::panel {a="x}'), 'the {attrs} quoted value is unclosed')
|
assert.equal(fault('::panel {a="x}'), 'the {attrs} quoted value is unclosed')
|
||||||
assert.equal(fault('::panel {a="\\uzzzz"}'), 'the {attrs} quoted value is not a JSON string')
|
assert.equal(fault('::panel {a="\\uzzzz"}'), 'the {attrs} quoted value is not a JSON string')
|
||||||
assert.equal(fault('::panel {a}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not')
|
assert.equal(fault('::panel {a}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\::: keeps the line literal text')
|
||||||
assert.equal(fault('::panel {a=}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not')
|
assert.equal(fault('::panel {a=}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\::: keeps the line literal text')
|
||||||
assert.equal(fault('::panel {=1}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not')
|
assert.equal(fault('::panel {=1}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\::: keeps the line literal text')
|
||||||
assert.equal(fault('::panel {a=1 b=2}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not')
|
assert.equal(fault('::panel {a=1 b=2}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\::: keeps the line literal text')
|
||||||
assert.equal(fault('::panel { a=1}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not')
|
assert.equal(fault('::panel { a=1}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\::: keeps the line literal text')
|
||||||
assert.equal(fault('::panel {a=1 }'), 'an attribute reads key=value, the value bare or double-quoted: this one does not')
|
assert.equal(fault('::panel {a=1 }'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\::: keeps the line literal text')
|
||||||
assert.equal(fault('::panel {a=1 a=2}'), 'the attribute key a is spelled twice')
|
assert.equal(fault('::panel {a=1 a=2}'), 'the attribute key a is spelled twice')
|
||||||
|
assert.equal(inline(':mention[@A]{id}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\: keeps the colon literal')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('breaks the directive on the raw characters a quoted value spells as escapes', () => {
|
test('breaks the directive on the raw characters a quoted value spells as escapes', () => {
|
||||||
@@ -131,10 +132,10 @@ test('binds an inline directive as a unit, its content balancing brackets like l
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('names the inline directive left unclosed at the end of its line', () => {
|
test('names the inline directive left unclosed at the end of its line', () => {
|
||||||
assert.equal(inline(':mention[@A'), 'an inline directive [content] is unclosed')
|
assert.equal(inline(':mention[@A'), 'an inline directive [content] is unclosed; \\: keeps the colon literal')
|
||||||
assert.equal(inline(':mention[@A\nB]'), 'an inline directive [content] is unclosed')
|
assert.equal(inline(':mention[@A\nB]'), 'an inline directive [content] is unclosed; \\: keeps the colon literal')
|
||||||
assert.equal(inline(':mention[a `b\nc` d]'), 'an inline directive [content] is unclosed')
|
assert.equal(inline(':mention[a `b\nc` d]'), 'an inline directive [content] is unclosed; \\: keeps the colon literal')
|
||||||
assert.equal(inline(':underline[:status[x'), 'an inline directive [content] is unclosed')
|
assert.equal(inline(':underline[:status[x'), 'an inline directive [content] is unclosed; \\: keeps the colon literal')
|
||||||
assert.equal(inline(':mention[@A]{id=1'), 'the {attrs} closing brace is missing')
|
assert.equal(inline(':mention[@A]{id=1'), 'the {attrs} closing brace is missing')
|
||||||
assert.equal(inline(':mention{id=1'), 'the {attrs} closing brace is missing')
|
assert.equal(inline(':mention{id=1'), 'the {attrs} closing brace is missing')
|
||||||
assert.equal(inline(':text{text="a\nb"}'), 'the {attrs} quoted value is not a JSON string')
|
assert.equal(inline(':text{text="a\nb"}'), 'the {attrs} quoted value is not a JSON string')
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const rawReserved = new RegExp(reservedSource)
|
|||||||
const noAttributes: DirectiveAttributes = new Map()
|
const noAttributes: DirectiveAttributes = new Map()
|
||||||
|
|
||||||
export const directiveLineEscape = '\\::: keeps the line literal text'
|
export const directiveLineEscape = '\\::: keeps the line literal text'
|
||||||
|
export const inlineDirectiveEscape = '\\: keeps the colon literal'
|
||||||
|
|
||||||
const emptyFault = 'an empty {attrs} is omitted unless the { itself claims the directive: this one spells {}'
|
const emptyFault = 'an empty {attrs} is omitted unless the { itself claims the directive: this one spells {}'
|
||||||
const nameFault = `a directive name reads [a-z][A-Za-z0-9]*: this one does not; ${directiveLineEscape}`
|
const nameFault = `a directive name reads [a-z][A-Za-z0-9]*: this one does not; ${directiveLineEscape}`
|
||||||
@@ -123,14 +124,18 @@ export function spellVocabulary(pairs: readonly VocabularyPair[]): [string, stri
|
|||||||
return pairs.map((pair): [string, string] => [pair.key, spellAttributeValue(pair)])
|
return pairs.map((pair): [string, string] => [pair.key, spellAttributeValue(pair)])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unknownDirectiveFault(name: string): ConvertFault {
|
export function unknownDirectiveFault(name: string, escape: string): ConvertFault {
|
||||||
return { code: 'unknown-directive-name', message: `the directive name ${name} reads back to no node` }
|
return { code: 'unknown-directive-name', message: `the directive name ${name} reads back to no node; ${escape}` }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unsupportedNodeShape(message: string): ConvertFault {
|
export function unsupportedNodeShape(message: string): ConvertFault {
|
||||||
return { code: 'unsupported-node-shape', message }
|
return { code: 'unsupported-node-shape', message }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function attributePairFault(escape: string): ConvertFault {
|
||||||
|
return malformedDirective(`${pairFault}; ${escape}`)
|
||||||
|
}
|
||||||
|
|
||||||
function keyOrder(left: string, right: string): number {
|
function keyOrder(left: string, right: string): number {
|
||||||
if (left < right) return -1
|
if (left < right) return -1
|
||||||
return left > right ? 1 : 0
|
return left > right ? 1 : 0
|
||||||
@@ -164,7 +169,7 @@ function readDirectiveHeader(rest: string): Read<{ argument: string | undefined;
|
|||||||
cursor += 1 + argument.length
|
cursor += 1 + argument.length
|
||||||
}
|
}
|
||||||
if (rest.charAt(cursor) === ' ' && rest.charAt(cursor + 1) === '{') {
|
if (rest.charAt(cursor) === ' ' && rest.charAt(cursor + 1) === '{') {
|
||||||
const read = readAttributes(rest, cursor + 1)
|
const read = readAttributes(rest, cursor + 1, directiveLineEscape)
|
||||||
if (read.fault !== undefined) return { fault: read.fault }
|
if (read.fault !== undefined) return { fault: read.fault }
|
||||||
if (read.value.attributes.size === 0) return { fault: malformedDirective(emptyFault) }
|
if (read.value.attributes.size === 0) return { fault: malformedDirective(emptyFault) }
|
||||||
attributes = read.value.attributes
|
attributes = read.value.attributes
|
||||||
@@ -190,7 +195,7 @@ function readNestedDirective(text: string, index: number, depth: number): Read<D
|
|||||||
}
|
}
|
||||||
let attributes = noAttributes
|
let attributes = noAttributes
|
||||||
if (text.charAt(cursor) === '{') {
|
if (text.charAt(cursor) === '{') {
|
||||||
const read = readAttributes(text, cursor)
|
const read = readAttributes(text, cursor, inlineDirectiveEscape)
|
||||||
if (read.fault !== undefined) return { fault: read.fault }
|
if (read.fault !== undefined) return { fault: read.fault }
|
||||||
if (read.value.attributes.size === 0 && content !== undefined) return { fault: malformedDirective(emptyFault) }
|
if (read.value.attributes.size === 0 && content !== undefined) return { fault: malformedDirective(emptyFault) }
|
||||||
attributes = read.value.attributes
|
attributes = read.value.attributes
|
||||||
@@ -226,7 +231,7 @@ function readDirectiveContent(text: string, start: number, depth: number): Read<
|
|||||||
if (character === ']') brackets -= 1
|
if (character === ']') brackets -= 1
|
||||||
cursor += 1
|
cursor += 1
|
||||||
}
|
}
|
||||||
return { fault: malformedDirective('an inline directive [content] is unclosed') }
|
return { fault: malformedDirective(`an inline directive [content] is unclosed; ${inlineDirectiveEscape}`) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// `undefined` where the span crosses the line ending an inline directive may not cross.
|
// `undefined` where the span crosses the line ending an inline directive may not cross.
|
||||||
@@ -237,16 +242,16 @@ function readCodeSpanEnd(text: string, index: number): number | undefined {
|
|||||||
return text.slice(index, closer + opener).includes('\n') ? undefined : closer + opener
|
return text.slice(index, closer + opener).includes('\n') ? undefined : closer + opener
|
||||||
}
|
}
|
||||||
|
|
||||||
function readAttributes(text: string, index: number): Read<Attributes> {
|
function readAttributes(text: string, index: number, escape: string): Read<Attributes> {
|
||||||
const attributes = new Map<string, DirectiveValue>()
|
const attributes = new Map<string, DirectiveValue>()
|
||||||
let cursor = index + 1
|
let cursor = index + 1
|
||||||
let previous = ''
|
let previous = ''
|
||||||
while (cursor < text.length && text.charAt(cursor) !== '}') {
|
while (cursor < text.length && text.charAt(cursor) !== '}') {
|
||||||
if (attributes.size > 0) {
|
if (attributes.size > 0) {
|
||||||
if (text.charAt(cursor) !== ' ') return { fault: malformedDirective(pairFault) }
|
if (text.charAt(cursor) !== ' ') return { fault: attributePairFault(escape) }
|
||||||
cursor += 1
|
cursor += 1
|
||||||
}
|
}
|
||||||
const pair = readAttributePair(text, cursor)
|
const pair = readAttributePair(text, cursor, escape)
|
||||||
if (pair.fault !== undefined) return { fault: pair.fault }
|
if (pair.fault !== undefined) return { fault: pair.fault }
|
||||||
const key = pair.value.key
|
const key = pair.value.key
|
||||||
if (attributes.has(key)) return { fault: malformedDirective(`the attribute key ${key} is spelled twice`) }
|
if (attributes.has(key)) return { fault: malformedDirective(`the attribute key ${key} is spelled twice`) }
|
||||||
@@ -259,10 +264,10 @@ function readAttributes(text: string, index: number): Read<Attributes> {
|
|||||||
return { value: { attributes, length: cursor + 1 - index } }
|
return { value: { attributes, length: cursor + 1 - index } }
|
||||||
}
|
}
|
||||||
|
|
||||||
function readAttributePair(text: string, index: number): Read<AttributePair> {
|
function readAttributePair(text: string, index: number, escape: string): Read<AttributePair> {
|
||||||
bareRun.lastIndex = index
|
bareRun.lastIndex = index
|
||||||
const key = bareRun.exec(text)?.[0]
|
const key = bareRun.exec(text)?.[0]
|
||||||
if (key === undefined || text.charAt(index + key.length) !== '=') return { fault: malformedDirective(pairFault) }
|
if (key === undefined || text.charAt(index + key.length) !== '=') return { fault: attributePairFault(escape) }
|
||||||
const start = index + key.length + 1
|
const start = index + key.length + 1
|
||||||
if (text.charAt(start) === '"') {
|
if (text.charAt(start) === '"') {
|
||||||
const quoted = readQuotedValue(text, start)
|
const quoted = readQuotedValue(text, start)
|
||||||
@@ -271,7 +276,7 @@ function readAttributePair(text: string, index: number): Read<AttributePair> {
|
|||||||
}
|
}
|
||||||
bareRun.lastIndex = start
|
bareRun.lastIndex = start
|
||||||
const bare = bareRun.exec(text)?.[0]
|
const bare = bareRun.exec(text)?.[0]
|
||||||
if (bare === undefined) return { fault: malformedDirective(pairFault) }
|
if (bare === undefined) return { fault: attributePairFault(escape) }
|
||||||
return { value: { end: start + bare.length, key, value: { decoded: bare, spelling: bare } } }
|
return { value: { end: start + bare.length, key, value: { decoded: bare, spelling: bare } } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { BlockDirective } from '../../adf/block-directives.ts'
|
|||||||
import type { ConvertFault } from '../../result.ts'
|
import type { ConvertFault } from '../../result.ts'
|
||||||
import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts'
|
import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts'
|
||||||
import type { Elsewhere } from './directive-attributes.ts'
|
import type { Elsewhere } from './directive-attributes.ts'
|
||||||
import { attributeValue, spellAttributeValue, unknownDirectiveFault } from '../directive-syntax.ts'
|
import { attributeValue, directiveLineEscape, inlineDirectiveEscape, spellAttributeValue, unknownDirectiveFault } from '../directive-syntax.ts'
|
||||||
import { blockArgument } from '../block-directive-arguments.ts'
|
import { blockArgument } from '../block-directive-arguments.ts'
|
||||||
import { blockDirective } from '../../adf/block-directives.ts'
|
import { blockDirective } from '../../adf/block-directives.ts'
|
||||||
import { carryName } from '../opaque-carry.ts'
|
import { carryName } from '../opaque-carry.ts'
|
||||||
@@ -27,7 +27,7 @@ export function readBlockDirectiveNode(
|
|||||||
return failure('malformed-directive', `the name ${carryName} is reserved for the opaque carry, whose block form is the fence`, path)
|
return failure('malformed-directive', `the name ${carryName} is reserved for the opaque carry, whose block form is the fence`, path)
|
||||||
}
|
}
|
||||||
const directive = blockDirective(name)
|
const directive = blockDirective(name)
|
||||||
if (directive === undefined) return faulted(inlineSpellingFault(name) ?? unknownDirectiveFault(name), path)
|
if (directive === undefined) return faulted(inlineSpellingFault(name) ?? unknownDirectiveFault(name, directiveLineEscape), path)
|
||||||
const argumentKey = blockArgument(name)
|
const argumentKey = blockArgument(name)
|
||||||
const rest = new Map(attributes)
|
const rest = new Map(attributes)
|
||||||
rest.delete(marksAttribute)
|
rest.delete(marksAttribute)
|
||||||
@@ -51,7 +51,7 @@ export function readInlineDirectiveNode(
|
|||||||
path: ConvertErrorPath,
|
path: ConvertErrorPath,
|
||||||
): Result<AdfNode> {
|
): Result<AdfNode> {
|
||||||
const directive = inlineDirective(name)
|
const directive = inlineDirective(name)
|
||||||
if (directive === undefined) return faulted(blockSpellingFault(name) ?? unknownDirectiveFault(name), path)
|
if (directive === undefined) return faulted(blockSpellingFault(name) ?? unknownDirectiveFault(name, inlineDirectiveEscape), path)
|
||||||
const slot = directive.textAttribute
|
const slot = directive.textAttribute
|
||||||
if (slot === undefined && content !== undefined) return failure('unsupported-node-shape', `${name} takes no content: this one holds some`, path)
|
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 elsewhere: Elsewhere | undefined = slot === undefined ? undefined : { key: slot, slot: 'content' }
|
||||||
|
|||||||
@@ -248,8 +248,9 @@ test('gives back the refusal the CommonMark spelling itself raises', () => {
|
|||||||
|
|
||||||
test('names the directive name no node reads back to', () => {
|
test('names the directive name no node reads back to', () => {
|
||||||
assert.equal(code(markdownToAdf(':::widget info\nx\n:::\n')), 'unknown-directive-name')
|
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')
|
assert.equal(content(markdownToAdf('::widget\n')), 'unknown-directive-name: the directive name widget reads back to no node; \\::: keeps the line literal text')
|
||||||
assert.equal(code(markdownToAdf(':widget[x]\n')), 'unknown-directive-name')
|
assert.equal(content(markdownToAdf(':widget[x]\n')), 'unknown-directive-name: the directive name widget reads back to no node; \\: keeps the colon literal')
|
||||||
|
assert.equal(content(markdownToAdf('ratio a:b[c]{d}\n')), 'malformed-directive: an attribute reads key=value, the value bare or double-quoted: this one does not; \\: keeps the colon literal')
|
||||||
assert.deepEqual(path(markdownToAdf('Part.\n\n::widget\n')), ['content', 1])
|
assert.deepEqual(path(markdownToAdf('Part.\n\n::widget\n')), ['content', 1])
|
||||||
assert.equal(content(markdownToAdf('Part.\n:::x\n')), 'malformed-directive: a container fenced with 3 colons is unclosed')
|
assert.equal(content(markdownToAdf('Part.\n:::x\n')), 'malformed-directive: a container fenced with 3 colons is unclosed')
|
||||||
assert.deepEqual(path(markdownToAdf('Part.\n:::x\n')), ['content', 1])
|
assert.deepEqual(path(markdownToAdf('Part.\n:::x\n')), ['content', 1])
|
||||||
@@ -417,7 +418,7 @@ test('leaves the colon that opens no directive the text it is', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('names the inline directive left unclosed at the end of its line', () => {
|
test('names the inline directive left unclosed at the end of its line', () => {
|
||||||
assert.equal(code(markdownToAdf('Part :mention[@A\n')), 'malformed-directive')
|
assert.equal(content(markdownToAdf('Part :mention[@A\n')), 'malformed-directive: an inline directive [content] is unclosed; \\: keeps the colon literal')
|
||||||
assert.equal(code(markdownToAdf('Part :mention[@A]{id=\n')), 'malformed-directive')
|
assert.equal(code(markdownToAdf('Part :mention[@A]{id=\n')), 'malformed-directive')
|
||||||
assert.deepEqual(path(markdownToAdf('> Part :mention[@A\n')), ['content', 0, 'content', 0])
|
assert.deepEqual(path(markdownToAdf('> Part :mention[@A\n')), ['content', 0, 'content', 0])
|
||||||
})
|
})
|
||||||
|
|||||||
+4
-2
@@ -425,5 +425,7 @@ Under **3 — `markdownToAdf` (`0.1.0`)**:
|
|||||||
parts into `adfDocumentFault`, the guard reading it, so the first failing check is the
|
parts into `adfDocumentFault`, the guard reading it, so the first failing check is the
|
||||||
message — the wrapper mistake names the key it found. Two carve-outs claim a line, not
|
message — the wrapper mistake names the key it found. Two carve-outs claim a line, not
|
||||||
three: a matched `~~` pair spells `strike` silently, so nothing refuses it and no message
|
three: a matched `~~` pair spells `strike` silently, so nothing refuses it and no message
|
||||||
names `\~~`; the escape lands on the directive-line and pipe-table refusals a prose line
|
names `\~~`. The escape lands on the refusals a prose line hits, in the form that was
|
||||||
hits. `a pipe table row holds 1 cells` gained its plural.
|
claimed — `\:::` on the directive line's, `\|` on the pipe table's, `\:` on the inline
|
||||||
|
directive's, the attribute-pair and unknown-name faults taking whichever form read them.
|
||||||
|
`a pipe table row holds 1 cells` gained its plural.
|
||||||
|
|||||||
Reference in New Issue
Block a user