Answer the architecture pass: the empty {attrs}, the reserved fence and where the reader lives
CI / gate (push) Successful in 5s
CI / gate (push) Successful in 5s
This commit is contained in:
@@ -122,6 +122,14 @@ The corpus, all checked in: hand-built fixtures per node and combination; real s
|
|||||||
live Atlassian APIs; property-generated ADF trees; the CommonMark spec suite against
|
live Atlassian APIs; property-generated ADF trees; the CommonMark spec suite against
|
||||||
`markdownToAdf` and `markdownToHtml`.
|
`markdownToAdf` and `markdownToHtml`.
|
||||||
|
|
||||||
|
`spec/flavour.md` is read as a source too, so the node tables cannot drift from the prose they
|
||||||
|
copy: each `- ` bullet in `## Block nodes`, `## Inline nodes` and `## Marks` declares the nodes
|
||||||
|
named before its first em dash, with the attributes following `Attributes: ` — a parenthesized
|
||||||
|
value set reading `string` — and must equal the tables in `adf/`. Keep prose in those sections out
|
||||||
|
of a bullet; fenced examples are skipped. It guards the attributes alone: nodes that differ in
|
||||||
|
content model share a bullet, and the argument attribute is spelled ahead of `Attributes: `, so
|
||||||
|
both answer to the round-trip corpus and to nothing else where a node has no fixture.
|
||||||
|
|
||||||
## 11. Code rules
|
## 11. Code rules
|
||||||
|
|
||||||
- Two-space indent, strict TypeScript, English everywhere. Alphabetical order wherever order
|
- Two-space indent, strict TypeScript, English everywhere. Alphabetical order wherever order
|
||||||
@@ -165,6 +173,9 @@ live Atlassian APIs; property-generated ADF trees; the CommonMark spec suite aga
|
|||||||
there too, never a copy per direction, however conservative the copy would be.
|
there too, never a copy per direction, however conservative the copy would be.
|
||||||
- The attribute vocabulary is ADF's: `adf/` walks it and narrows each value to its kind, and a
|
- The attribute vocabulary is ADF's: `adf/` walks it and narrows each value to its kind, and a
|
||||||
format spells the narrowed value. A spelling that re-checks the type is the check's second copy.
|
format spells the narrowed value. A spelling that re-checks the type is the check's second copy.
|
||||||
|
Reading a spelling back is the format's own: the reader sits beside the spelling it inverts, so
|
||||||
|
decode-respell-compare cannot drift, and each format writes its own — canonical JSON for a
|
||||||
|
number is the markdown flavour's choice, not ADF's.
|
||||||
- Explicit over implicit; descriptive names; no catch-all files (`utils`, `helpers`, `misc`); a
|
- Explicit over implicit; descriptive names; no catch-all files (`utils`, `helpers`, `misc`); a
|
||||||
file does not repeat its directory in its name — `adf/document.ts`, never
|
file does not repeat its directory in its name — `adf/document.ts`, never
|
||||||
`adf/adf-document.ts`.
|
`adf/adf-document.ts`.
|
||||||
|
|||||||
+3
-2
@@ -113,8 +113,9 @@ assigns types.
|
|||||||
Canonical form orders keys alphabetically, spells values bare wherever allowed, escapes inside
|
Canonical form orders keys alphabetically, spells values bare wherever allowed, escapes inside
|
||||||
quotes in the shortest form each escape has, and omits empty `{attrs}` except where the `{` itself
|
quotes in the shortest form each escape has, and omits empty `{attrs}` except where the `{` itself
|
||||||
claims the directive (`:hardBreak{}`). Input reads that spelling alone: keys out of order, a value
|
claims the directive (`:hardBreak{}`). Input reads that spelling alone: keys out of order, a value
|
||||||
quoted where bare carries it, an escape longer than it need be, and a number or `json` value
|
quoted where bare carries it, an escape longer than it need be, an empty `{attrs}` the name or the
|
||||||
outside its canonical JSON spelling are each a named error naming the spelling to write instead.
|
`[content]` already claims, and a number or `json` value outside its canonical JSON spelling are
|
||||||
|
each a named error naming the spelling to write instead.
|
||||||
|
|
||||||
**Escaping**: the emitter backslash-escapes whatever literal text would otherwise parse as
|
**Escaping**: the emitter backslash-escapes whatever literal text would otherwise parse as
|
||||||
directive syntax — the leading `:` of a would-be directive, `]` inside content, a `{` right
|
directive syntax — the leading `:` of a would-be directive, `]` inside content, a `{` right
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ test('reads the leaf and container forms, their argument and their attributes',
|
|||||||
assert.deepEqual(readDirectiveLine('::rule'), header(2, 'rule'))
|
assert.deepEqual(readDirectiveLine('::rule'), header(2, 'rule'))
|
||||||
assert.deepEqual(readDirectiveLine('::rule '), header(2, 'rule'))
|
assert.deepEqual(readDirectiveLine('::rule '), header(2, 'rule'))
|
||||||
assert.deepEqual(readDirectiveLine('::taskItem TODO'), header(2, 'taskItem', 'TODO'))
|
assert.deepEqual(readDirectiveLine('::taskItem TODO'), header(2, 'taskItem', 'TODO'))
|
||||||
assert.deepEqual(readDirectiveLine('::hardBreak {}'), header(2, 'hardBreak'))
|
|
||||||
assert.deepEqual(readDirectiveLine('::media {id=a-1 type=file}'), header(2, 'media', undefined, ['id', 'a-1'], ['type', 'file']))
|
assert.deepEqual(readDirectiveLine('::media {id=a-1 type=file}'), header(2, 'media', undefined, ['id', 'a-1'], ['type', 'file']))
|
||||||
assert.deepEqual(readDirectiveLine('::panel info {panelColor="#ff0000"} '), header(2, 'panel', 'info', ['panelColor', '#ff0000', '"#ff0000"']))
|
assert.deepEqual(readDirectiveLine('::panel info {panelColor="#ff0000"} '), header(2, 'panel', 'info', ['panelColor', '#ff0000', '"#ff0000"']))
|
||||||
assert.deepEqual(readDirectiveLine(':::panel info'), header(3, 'panel', 'info'))
|
assert.deepEqual(readDirectiveLine(':::panel info'), header(3, 'panel', 'info'))
|
||||||
@@ -70,6 +69,14 @@ test('names the {attrs} keys read out of the alphabetical order canonical form s
|
|||||||
assert.deepEqual(readDirectiveLine('::media {id=a-1 type=file}'), header(2, 'media', undefined, ['id', 'a-1'], ['type', 'file']))
|
assert.deepEqual(readDirectiveLine('::media {id=a-1 type=file}'), header(2, 'media', undefined, ['id', 'a-1'], ['type', 'file']))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('spells an empty {attrs} only where the brace itself claims the directive', () => {
|
||||||
|
const omitted = 'an empty {attrs} is omitted unless the { itself claims the directive'
|
||||||
|
assert.equal(fault('::rule {}'), omitted)
|
||||||
|
assert.equal(fault(':::panel info {}'), omitted)
|
||||||
|
assert.equal(inline(':underline[a]{}'), omitted)
|
||||||
|
spans(':hardBreak{}', 'hardBreak', undefined)
|
||||||
|
})
|
||||||
|
|
||||||
test('names the directive line no spelling reads', () => {
|
test('names the directive line no spelling reads', () => {
|
||||||
assert.equal(fault('::Panel'), 'a directive name reads [a-z][A-Za-z0-9]*')
|
assert.equal(fault('::Panel'), 'a directive name reads [a-z][A-Za-z0-9]*')
|
||||||
assert.equal(fault('::1panel'), 'a directive name reads [a-z][A-Za-z0-9]*')
|
assert.equal(fault('::1panel'), 'a directive name reads [a-z][A-Za-z0-9]*')
|
||||||
@@ -77,7 +84,7 @@ test('names the directive line no spelling reads', () => {
|
|||||||
assert.equal(fault('::panel info extra'), 'a directive line reads a name, one bare argument and {attrs}, one space apart')
|
assert.equal(fault('::panel info extra'), 'a directive line reads a name, one bare argument and {attrs}, one space apart')
|
||||||
assert.equal(fault('::panel{}'), 'a directive line reads a name, one bare argument and {attrs}, one space apart')
|
assert.equal(fault('::panel{}'), 'a directive line reads a name, one bare argument and {attrs}, one space apart')
|
||||||
assert.equal(fault('::panel info{}'), 'a directive line reads a name, one bare argument and {attrs}, one space apart')
|
assert.equal(fault('::panel info{}'), 'a directive line reads a name, one bare argument and {attrs}, one space apart')
|
||||||
assert.equal(fault('::panel {} x'), 'a directive line reads a name, one bare argument and {attrs}, one space apart')
|
assert.equal(fault('::panel {a=1} x'), 'a directive line reads a name, one bare argument and {attrs}, one space apart')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('names the attributes no spelling reads', () => {
|
test('names the attributes no spelling reads', () => {
|
||||||
@@ -119,7 +126,6 @@ test('binds an inline directive as a unit, its content balancing brackets like l
|
|||||||
spans(':underline[a `b c]', 'underline', 'a `b c')
|
spans(':underline[a `b c]', 'underline', 'a `b c')
|
||||||
spans(':underline[:status[x]{color=red}]', 'underline', ':status[x]{color=red}')
|
spans(':underline[:status[x]{color=red}]', 'underline', ':status[x]{color=red}')
|
||||||
spans(':status[x]{color=red style="bold "}', 'status', 'x', ['color', 'red'], ['style', 'bold ', '"bold "'])
|
spans(':status[x]{color=red style="bold "}', 'status', 'x', ['color', 'red'], ['style', 'bold ', '"bold "'])
|
||||||
assert.deepEqual(inline(':underline[a]{}(b)'), { attributes: attributes(), content: 'a', length: 15, name: 'underline' })
|
|
||||||
assert.deepEqual(inline(':underline[a] {}'), { attributes: attributes(), content: 'a', length: 13, name: 'underline' })
|
assert.deepEqual(inline(':underline[a] {}'), { attributes: attributes(), content: 'a', length: 13, name: 'underline' })
|
||||||
assert.deepEqual(inline(':text{text=" "} and more'), { attributes: attributes(['text', ' ', '" "']), content: undefined, length: 15, name: 'text' })
|
assert.deepEqual(inline(':text{text=" "} and more'), { attributes: attributes(['text', ' ', '" "']), content: undefined, length: 15, name: 'text' })
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const quotedEscapes = new RegExp(reservedSource, 'g')
|
|||||||
const rawReserved = new RegExp(reservedSource)
|
const rawReserved = new RegExp(reservedSource)
|
||||||
const noAttributes: DirectiveAttributes = new Map()
|
const noAttributes: DirectiveAttributes = new Map()
|
||||||
|
|
||||||
|
const emptyFault = 'an empty {attrs} is omitted unless the { itself claims the directive'
|
||||||
const nameFault = 'a directive name reads [a-z][A-Za-z0-9]*'
|
const nameFault = 'a directive name reads [a-z][A-Za-z0-9]*'
|
||||||
const orderFault = 'the {attrs} keys read in alphabetical order'
|
const orderFault = 'the {attrs} keys read in alphabetical order'
|
||||||
const pairFault = 'an attribute reads key=value, the value bare or double-quoted'
|
const pairFault = 'an attribute reads key=value, the value bare or double-quoted'
|
||||||
@@ -136,6 +137,7 @@ function readDirectiveHeader(rest: string): Read<{ argument: string | undefined;
|
|||||||
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)
|
||||||
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) }
|
||||||
attributes = read.value.attributes
|
attributes = read.value.attributes
|
||||||
cursor += 1 + read.value.length
|
cursor += 1 + read.value.length
|
||||||
}
|
}
|
||||||
@@ -161,6 +163,7 @@ function readNestedDirective(text: string, index: number, depth: number): Read<D
|
|||||||
if (text.charAt(cursor) === '{') {
|
if (text.charAt(cursor) === '{') {
|
||||||
const read = readAttributes(text, cursor)
|
const read = readAttributes(text, cursor)
|
||||||
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) }
|
||||||
attributes = read.value.attributes
|
attributes = read.value.attributes
|
||||||
cursor += read.value.length
|
cursor += read.value.length
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { marksAttribute, readMarkValues } from '../block-directive-marks.ts'
|
|||||||
|
|
||||||
export type BlockDirectiveNode = { contentModel: BlockDirective['contentModel']; node: AdfNode }
|
export type BlockDirectiveNode = { contentModel: BlockDirective['contentModel']; node: AdfNode }
|
||||||
|
|
||||||
type Elsewhere = { key: string; place: string }
|
type Elsewhere = { key: string; slot: 'argument' | 'content' }
|
||||||
|
|
||||||
export function readBlockDirectiveNode(
|
export function readBlockDirectiveNode(
|
||||||
name: string,
|
name: string,
|
||||||
@@ -28,7 +28,7 @@ export function readBlockDirectiveNode(
|
|||||||
const argumentKey = blockArgument(name)
|
const argumentKey = blockArgument(name)
|
||||||
const rest = new Map(attributes)
|
const rest = new Map(attributes)
|
||||||
rest.delete(marksAttribute)
|
rest.delete(marksAttribute)
|
||||||
const elsewhere = argumentKey === undefined ? undefined : { key: argumentKey, place: 'as the directive argument' }
|
const elsewhere: Elsewhere | undefined = argumentKey === undefined ? undefined : { key: argumentKey, slot: 'argument' }
|
||||||
const attrs = readVocabulary(name, rest, directive.attributes, elsewhere, path)
|
const attrs = readVocabulary(name, rest, directive.attributes, elsewhere, path)
|
||||||
if (!attrs.ok) return attrs
|
if (!attrs.ok) return attrs
|
||||||
if (argument !== undefined) {
|
if (argument !== undefined) {
|
||||||
@@ -38,7 +38,7 @@ export function readBlockDirectiveNode(
|
|||||||
const spelled = attributes.get(marksAttribute)
|
const spelled = attributes.get(marksAttribute)
|
||||||
const marks: Result<AdfMark[] | undefined> = spelled === undefined ? success(undefined) : readMarks(name, spelled, path)
|
const marks: Result<AdfMark[] | undefined> = spelled === undefined ? success(undefined) : readMarks(name, spelled, path)
|
||||||
if (!marks.ok) return marks
|
if (!marks.ok) return marks
|
||||||
return success({ contentModel: directive.contentModel, node: directiveNode(name, attrs.value, marks.value) })
|
return success({ contentModel: directive.contentModel, node: namedNode(name, attrs.value, marks.value) })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readInlineDirectiveNode(span: DirectiveSpan, path: ConvertErrorPath): Result<AdfNode> {
|
export function readInlineDirectiveNode(span: DirectiveSpan, path: ConvertErrorPath): Result<AdfNode> {
|
||||||
@@ -49,10 +49,10 @@ export function readInlineDirectiveNode(span: DirectiveSpan, path: ConvertErrorP
|
|||||||
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 ? `a ${span.name} takes no content` : `the content slot a ${span.name} spells its ${slot} attribute in is unsupported`
|
||||||
return failure('unsupported-node-shape', message, path)
|
return failure('unsupported-node-shape', message, path)
|
||||||
}
|
}
|
||||||
const elsewhere = slot === undefined ? undefined : { key: slot, place: 'in the content slot' }
|
const elsewhere: Elsewhere | undefined = slot === undefined ? undefined : { key: slot, slot: 'content' }
|
||||||
const attrs = readVocabulary(span.name, span.attributes, directive.attributes, elsewhere, path)
|
const attrs = readVocabulary(span.name, span.attributes, directive.attributes, elsewhere, path)
|
||||||
if (!attrs.ok) return attrs
|
if (!attrs.ok) return attrs
|
||||||
return success(directiveNode(span.name, attrs.value, undefined))
|
return success(namedNode(span.name, attrs.value, undefined))
|
||||||
}
|
}
|
||||||
|
|
||||||
function readVocabulary(
|
function readVocabulary(
|
||||||
@@ -64,7 +64,10 @@ function readVocabulary(
|
|||||||
): Result<AdfAttributes> {
|
): Result<AdfAttributes> {
|
||||||
const attrs: AdfAttributes = {}
|
const attrs: AdfAttributes = {}
|
||||||
for (const [key, spelled] of attributes) {
|
for (const [key, spelled] of attributes) {
|
||||||
if (key === elsewhere?.key) return failure('unsupported-node-shape', `a ${type} spells its ${key} attribute ${elsewhere.place}`, path)
|
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)
|
||||||
|
}
|
||||||
const kind = Object.hasOwn(vocabulary, key) ? vocabulary[key] : undefined
|
const kind = Object.hasOwn(vocabulary, key) ? vocabulary[key] : undefined
|
||||||
if (kind === undefined) return failure('unsupported-node-shape', `a ${type} holds no ${key} attribute`, path)
|
if (kind === undefined) return failure('unsupported-node-shape', `a ${type} holds no ${key} attribute`, path)
|
||||||
const read = attributeValue(spelled.text, kind)
|
const read = attributeValue(spelled.text, kind)
|
||||||
@@ -85,7 +88,7 @@ function readMarks(type: string, spelled: DirectiveValue, path: ConvertErrorPath
|
|||||||
return success(marks)
|
return success(marks)
|
||||||
}
|
}
|
||||||
|
|
||||||
function directiveNode(type: string, attrs: AdfAttributes, marks: readonly AdfMark[] | undefined): AdfNode {
|
function namedNode(type: string, attrs: AdfAttributes, marks: readonly AdfMark[] | undefined): AdfNode {
|
||||||
const named = Object.keys(attrs).length === 0 ? { type } : { attrs, type }
|
const named = Object.keys(attrs).length === 0 ? { type } : { attrs, type }
|
||||||
return marks === undefined ? named : { ...named, marks: [...marks] }
|
return marks === undefined ? named : { ...named, marks: [...marks] }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ test('names the reserved carry name a block directive spells', () => {
|
|||||||
const reserved = 'malformed-directive: the name adf is reserved for the opaque carry, whose block form is the fence'
|
const reserved = 'malformed-directive: the name adf is reserved for the opaque carry, whose block form is the fence'
|
||||||
assert.equal(content(markdownToAdf('::adf\n')), reserved)
|
assert.equal(content(markdownToAdf('::adf\n')), reserved)
|
||||||
assert.equal(content(markdownToAdf(':::adf\nx\n:::\n')), reserved)
|
assert.equal(content(markdownToAdf(':::adf\nx\n:::\n')), reserved)
|
||||||
|
assert.equal(content(markdownToAdf('```adf\nx\n```\n')), 'malformed-directive: the info string adf is reserved for the opaque carry')
|
||||||
|
assert.deepEqual(content(markdownToAdf('```adfx\nx\n```\n')), [{ attrs: { language: 'adfx' }, content: [text('x')], type: 'codeBlock' }])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('reads each attribute value as the type its section assigns', () => {
|
test('reads each attribute value as the type its section assigns', () => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
||||||
import type { Block, DirectiveBlock } from './blocks.ts'
|
import type { Block, DirectiveBlock } from './blocks.ts'
|
||||||
import type { LinkDefinitions } from './inline-content.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'
|
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||||
import { largestNesting } from '../../nesting.ts'
|
import { largestNesting } from '../../nesting.ts'
|
||||||
import { parseBlocks } from './blocks.ts'
|
import { parseBlocks } from './blocks.ts'
|
||||||
@@ -32,7 +33,7 @@ function blockNode(block: Block, definitions: LinkDefinitions, path: ConvertErro
|
|||||||
case 'bulletList':
|
case 'bulletList':
|
||||||
return listNode({ type: 'bulletList' }, block.items, definitions, path, depth)
|
return listNode({ type: 'bulletList' }, block.items, definitions, path, depth)
|
||||||
case 'code':
|
case 'code':
|
||||||
return success(codeBlockNode(block.language, block.text))
|
return codeBlockNode(block.language, block.text, path)
|
||||||
case 'directive':
|
case 'directive':
|
||||||
return directiveNode(block, definitions, path, depth)
|
return directiveNode(block, definitions, path, depth)
|
||||||
case 'fault':
|
case 'fault':
|
||||||
@@ -92,9 +93,10 @@ function listNode(node: AdfNode, items: readonly Block[][], definitions: LinkDef
|
|||||||
return success({ ...node, content })
|
return success({ ...node, content })
|
||||||
}
|
}
|
||||||
|
|
||||||
function codeBlockNode(language: string, text: string): AdfNode {
|
function codeBlockNode(language: string, text: string, path: ConvertErrorPath): Result<AdfNode> {
|
||||||
|
if (language === carryName) return failure('malformed-directive', `the info string ${carryName} is reserved for the opaque carry`, path)
|
||||||
const node: AdfNode = language === '' ? { type: 'codeBlock' } : { attrs: { language }, type: 'codeBlock' }
|
const node: AdfNode = language === '' ? { type: 'codeBlock' } : { attrs: { language }, type: 'codeBlock' }
|
||||||
return text === '' ? node : { ...node, content: [{ text, type: 'text' }] }
|
return success(text === '' ? node : { ...node, content: [{ text, type: 'text' }] })
|
||||||
}
|
}
|
||||||
|
|
||||||
// spec/flavour.md, The CommonMark image: only a plain paragraph gives an image the block it needs.
|
// spec/flavour.md, The CommonMark image: only a plain paragraph gives an image the block it needs.
|
||||||
|
|||||||
@@ -318,7 +318,8 @@ detail is settled at its own milestone.
|
|||||||
- [ ] **3h — The block nodes.** `block-nodes/` reads back: the plain-versus-directive choice 2f
|
- [ ] **3h — The block nodes.** `block-nodes/` reads back: the plain-versus-directive choice 2f
|
||||||
settles, read from the other side; the `codeBlock` directive's fenced body and the
|
settles, read from the other side; the `codeBlock` directive's fenced body and the
|
||||||
`language` attribute a bare fence leaves it; the media family's composition; and both
|
`language` attribute a bare fence leaves it; the media family's composition; and both
|
||||||
table forms, the pipe table's cell split and its named errors.
|
table forms, the pipe table's cell split and its named errors. `fenceInfo` is a rule both
|
||||||
|
directions answer alike and moves to the `markdown/` root with the language attribute.
|
||||||
**Settled** (the maintainer, 2026-08-27): 1d's last pick, the one
|
**Settled** (the maintainer, 2026-08-27): 1d's last pick, the one
|
||||||
`container-block-separation` holds — a CommonMark block and a directive block sit adjacent
|
`container-block-separation` holds — a CommonMark block and a directive block sit adjacent
|
||||||
in a container body with no blank line between them. That reduces the three cases to one
|
in a container body with no blank line between them. That reduces the three cases to one
|
||||||
@@ -335,10 +336,19 @@ detail is settled at its own milestone.
|
|||||||
outermost first; and `:em[x]` as the error `spec/flavour.md` promises. Editor-normal's
|
outermost first; and `:em[x]` as the error `spec/flavour.md` promises. Editor-normal's
|
||||||
merging half lands here, `text-whitespace` being the first fixture that forces it, and 4's
|
merging half lands here, `text-whitespace` being the first fixture that forces it, and 4's
|
||||||
`toEditorNormal` is built on it.
|
`toEditorNormal` is built on it.
|
||||||
|
3g's shape leaves three: `readInlineDirectiveNode` takes the name, the attributes and the
|
||||||
|
slot's parsed text rather than the span, since `inline-content.ts` already imports it and
|
||||||
|
parsing the slot inside it is a cycle; the four directive marks get `parse/directive-marks.ts`
|
||||||
|
that `inline-content.ts` tries ahead of the node reader, as `mark-spellings.ts` sits apart
|
||||||
|
from `emit/inline-directive-spelling.ts`; and the five markdown-spelled mark names in inline
|
||||||
|
directive position want a claim code — `:em[x]` is an error forever, so
|
||||||
|
`unknown-directive-name`'s "a later MINOR may give the name meaning" is the wrong signal,
|
||||||
|
as it was for `adf`. `corpus/errors/directive-content-slot` goes when the slot opens.
|
||||||
- [ ] **3j — The carry and the combinations.** `opaque-carry/` and `combinations/` read back:
|
- [ ] **3j — The carry and the combinations.** `opaque-carry/` and `combinations/` read back:
|
||||||
the `adf` fence and `:adf{json="…"}` restoring a deep-equal node, invalid JSON in either a
|
the `adf` fence and `:adf{json="…"}` restoring a deep-equal node, invalid JSON in either a
|
||||||
named error, a carry inside a mark spelling another, and the three carve-outs' escapes
|
named error, a carry inside a mark spelling another, and the three carve-outs' escapes
|
||||||
reading as the literal text they hold.
|
reading as the literal text they hold. 3g refuses the `adf` fence rather than reading a
|
||||||
|
`codeBlock` from it; the refusal goes when the carry reads it.
|
||||||
- [ ] **3k — The CommonMark spec suite.** Checked in at `corpus/commonmark-spec/`, pinned to
|
- [ ] **3k — The CommonMark spec suite.** Checked in at `corpus/commonmark-spec/`, pinned to
|
||||||
the version it ships — the one `html-blocks.ts` names for its start conditions —
|
the version it ships — the one `html-blocks.ts` names for its start conditions —
|
||||||
`corpus/README.md` gaining the kind.
|
`corpus/README.md` gaining the kind.
|
||||||
|
|||||||
Reference in New Issue
Block a user