Fix the false claims round one found, and refuse the content a code span dropped
CI / gate (push) Successful in 5s
CI / gate (push) Successful in 5s
This commit is contained in:
@@ -82,8 +82,8 @@ closed list a consumer may switch exhaustively, the message free text, the path
|
|||||||
from the document root. Adding, removing or renaming a code is breaking, so a milestone meeting a
|
from the document root. 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
|
new failure cause 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, one code covers them all and `path` and `message`
|
cause; where one cause recurs across node types, one code covers them all and `path` and `message`
|
||||||
say which. A cause the carry answers gets no code: no mark refuses, since a mark no spelling writes
|
say which. A cause the carry answers gets no code: a mark no spelling writes rides the carry with
|
||||||
rides the carry with its node.
|
its node.
|
||||||
|
|
||||||
## 9. Release automation
|
## 9. Release automation
|
||||||
|
|
||||||
@@ -154,9 +154,8 @@ One-line commit messages and PR titles; short PR summaries. No AI-attribution ma
|
|||||||
No wiki markup (§1), no network or filesystem I/O, no name→id resolution (§3), no ADF schema
|
No wiki markup (§1), no network or filesystem I/O, no name→id resolution (§3), no ADF schema
|
||||||
validation or exported validator — a refusal that keeps the round-trip is not schema validation,
|
validation or exported validator — a refusal that keeps the round-trip is not schema validation,
|
||||||
so the one a spelled node carrying the same mark type twice earns stays, no shipped CSS (§4), no
|
so the one a spelled node carrying the same mark type twice earns stays, no shipped CSS (§4), no
|
||||||
streaming APIs, no performance budget —
|
streaming APIs, no performance budget — real documents are kilobytes. A CLI is a later goal
|
||||||
conversions are O(n), real documents are kilobytes. A CLI is a later goal (`todo.md`), not a
|
(`todo.md`), not a non-goal.
|
||||||
non-goal.
|
|
||||||
|
|
||||||
## 15. The working loop
|
## 15. The working loop
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,41 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "paragraph"
|
"type": "paragraph"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"text": "a",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"marks": [
|
||||||
|
{
|
||||||
|
"type": "em"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"text": ".",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "b",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"marks": [
|
||||||
|
{
|
||||||
|
"type": "strong"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"text": ",",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "c",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "paragraph"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "doc",
|
"type": "doc",
|
||||||
|
|||||||
@@ -7,3 +7,5 @@ Reviewed :adf{json="{\"marks\":[{\"attrs\":{\"annotationType\":\"inlineComment\"
|
|||||||
:adf{json="{\"marks\":[{\"type\":\"code\"}],\"text\":\"a\\nb\",\"type\":\"text\"}"}
|
:adf{json="{\"marks\":[{\"type\":\"code\"}],\"text\":\"a\\nb\",\"type\":\"text\"}"}
|
||||||
|
|
||||||
:adf{json="{\"marks\":[{\"type\":\"link\"}],\"text\":\"Release notes\",\"type\":\"text\"}"}
|
:adf{json="{\"marks\":[{\"type\":\"link\"}],\"text\":\"Release notes\",\"type\":\"text\"}"}
|
||||||
|
|
||||||
|
a:adf{json="{\"marks\":[{\"type\":\"em\"}],\"text\":\".\",\"type\":\"text\"}"}b:adf{json="{\"marks\":[{\"type\":\"strong\"}],\"text\":\",\",\"type\":\"text\"}"}c
|
||||||
|
|||||||
+8
-6
@@ -386,12 +386,14 @@ restores the array, not a set — and opens each spelling once over the longest
|
|||||||
inline nodes carrying an identical mark, attributes included, at that depth. A run breaks at every
|
inline nodes carrying an identical mark, attributes included, at that depth. A run breaks at every
|
||||||
node the emitter carries, so no emitted carry sits inside a mark spelling.
|
node the emitter carries, so no emitted carry sits inside a mark spelling.
|
||||||
|
|
||||||
An inline node whose marks no nesting spells — a mark type not listed here, an attribute no
|
An inline node whose marks no nesting spells — a mark type not listed here, an attrs key its
|
||||||
spelling holds or one a spelling needs and the mark lacks, an order putting a code span outside
|
spelling does not list, a value that is not the spelling's type, an attribute the spelling needs
|
||||||
another mark, `code` over anything but a text node or over text holding a newline, or a spelling
|
and the mark lacks, an order putting a code span outside another mark, `code` over anything but a
|
||||||
CommonMark's flanking rules cannot open or close where the run sits (`un**-real**istic`) — rides
|
text node or over text holding a newline, or a spelling CommonMark's flanking rules cannot open or
|
||||||
the inline carry whole. An opaque carry inside a mark spelling is a
|
close where the run sits (`un**-real**istic`) — rides the inline carry whole. A value the spelling
|
||||||
named error in input: the carry restores its node exactly, marks included (AGENTS.md §3).
|
holds but CommonMark cannot write — a link destination or title — is a named error instead. An
|
||||||
|
opaque carry inside a mark spelling is a named error in input: the carry restores its node
|
||||||
|
exactly, marks included (AGENTS.md §3).
|
||||||
|
|
||||||
```
|
```
|
||||||
:textColor[**Overdue**]{color="#ae2e24"}, H:subsup[2]{type=sub}O, :underline[signed].
|
:textColor[**Overdue**]{color="#ae2e24"}, H:subsup[2]{type=sub}O, :underline[signed].
|
||||||
|
|||||||
@@ -317,9 +317,12 @@ test('refuses the characters CommonMark rewrites', () => {
|
|||||||
assert.equal(code(adfToMarkdown(document({ content: [{ text: '', type: 'text' }], type: 'codeBlock' }))), 'unsupported-node-shape')
|
assert.equal(code(adfToMarkdown(document({ content: [{ text: '', type: 'text' }], type: 'codeBlock' }))), 'unsupported-node-shape')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('refuses a text node carrying no text at all', () => {
|
test('refuses a text node the spelling would empty out', () => {
|
||||||
|
const nested: AdfNode[] = [{ text: 'lost', type: 'text' }]
|
||||||
assert.equal(code(adfToMarkdown(document(paragraph({ text: '', type: 'text' })))), 'unsupported-node-shape')
|
assert.equal(code(adfToMarkdown(document(paragraph({ text: '', type: 'text' })))), 'unsupported-node-shape')
|
||||||
assert.equal(code(adfToMarkdown(document(paragraph({ marks: [{ type: 'code' }], text: '', type: 'text' })))), 'unsupported-node-shape')
|
assert.equal(code(adfToMarkdown(document(paragraph({ marks: [{ type: 'code' }], text: '', type: 'text' })))), 'unsupported-node-shape')
|
||||||
|
assert.equal(code(adfToMarkdown(document(paragraph({ content: nested, text: 'x', type: 'text' })))), 'unsupported-node-shape')
|
||||||
|
assert.equal(code(adfToMarkdown(document(paragraph({ content: nested, marks: [{ type: 'code' }], text: 'x', type: 'text' })))), 'unsupported-node-shape')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('carries a mark run whose edge holds whitespace CommonMark flanking counts', () => {
|
test('carries a mark run whose edge holds whitespace CommonMark flanking counts', () => {
|
||||||
|
|||||||
@@ -110,13 +110,11 @@ function commonMarkContainer(body: Result<EmittedBody>): Result<EmittedBlock> {
|
|||||||
|
|
||||||
function emitDirectiveBlock(node: AdfNode, directive: BlockDirective, path: ConvertErrorPath, depth: number): Result<EmittedBlock> {
|
function emitDirectiveBlock(node: AdfNode, directive: BlockDirective, path: ConvertErrorPath, depth: number): Result<EmittedBlock> {
|
||||||
if (node.text !== undefined) return failure('unsupported-node-shape', `a ${node.type} carries no text`, path)
|
if (node.text !== undefined) return failure('unsupported-node-shape', `a ${node.type} carries no text`, path)
|
||||||
|
const content = node.content ?? []
|
||||||
|
if (directive.body === 'none' && content.length > 0) return failure('unsupported-node-shape', `a ${node.type} holds no content`, path)
|
||||||
const header = spellDirectiveHeader(node, directive)
|
const header = spellDirectiveHeader(node, directive)
|
||||||
if (header === undefined) return commonMarkLine(carriedBlock(node, path))
|
if (header === undefined) return commonMarkLine(carriedBlock(node, path))
|
||||||
const content = node.content ?? []
|
if (directive.body === 'none') return success({ fenceColons: 2, spelling: 'directive', text: `::${header}` })
|
||||||
if (directive.body === 'none') {
|
|
||||||
if (content.length > 0) return failure('unsupported-node-shape', `a ${node.type} holds no content`, path)
|
|
||||||
return success({ fenceColons: 2, spelling: 'directive', text: `::${header}` })
|
|
||||||
}
|
|
||||||
const body = directive.body === 'inline' ? emitInlineBody(content, path) : emitBlocks(content, 'directive', path, depth + 1)
|
const body = directive.body === 'inline' ? emitInlineBody(content, path) : emitBlocks(content, 'directive', path, depth + 1)
|
||||||
if (!body.ok) return body
|
if (!body.ok) return body
|
||||||
const fenceColons = Math.max(3, body.value.fenceColons + 1)
|
const fenceColons = Math.max(3, body.value.fenceColons + 1)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function tryImageLine(alt: string | undefined, href: string, path: Conver
|
|||||||
return attempt.ok ? attempt.value.line : undefined
|
return attempt.ok ? attempt.value.line : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// A demand names a run no spelling holds, and a carried node joins no run, so every pass carries one more node.
|
// A demand names a run no spelling holds, and a carried node joins no run, so every pass carries at least one more node.
|
||||||
function emitLine(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath): Result<EmittedLine> {
|
function emitLine(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath): Result<EmittedLine> {
|
||||||
const carried = new Set<number>()
|
const carried = new Set<number>()
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -262,6 +262,7 @@ function emitCodeSpan(nodes: readonly AdfNode[], depth: number, range: NodeRange
|
|||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
if (node.type !== 'text' || (node.marks ?? []).length !== depth + 1) return success({ carry: range })
|
if (node.type !== 'text' || (node.marks ?? []).length !== depth + 1) return success({ carry: range })
|
||||||
if (typeof node.text !== 'string' || node.text === '') return failure('unsupported-node-shape', 'a text node holds text', path)
|
if (typeof node.text !== 'string' || node.text === '') return failure('unsupported-node-shape', 'a text node holds text', path)
|
||||||
|
if ((node.content ?? []).length > 0) return failure('unsupported-node-shape', 'a text node holds no content', path)
|
||||||
text += node.text
|
text += node.text
|
||||||
}
|
}
|
||||||
if (/[\n\r]/.test(text)) return success({ carry: range })
|
if (/[\n\r]/.test(text)) return success({ carry: range })
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ detail is settled at its own milestone.
|
|||||||
which leaves the refusals a container's own spelling owns. The flanking trigger 2e2
|
which leaves the refusals a container's own spelling owns. The flanking trigger 2e2
|
||||||
added to that list is the odd one out: `unspellableMark` finds it after assembly and
|
added to that list is the odd one out: `unspellableMark` finds it after assembly and
|
||||||
names a mark type against the line's path, so the failing run needs identifying before
|
names a mark type against the line's path, so the failing run needs identifying before
|
||||||
the carry can replace the refusal `corpus/unspellable/mark-inside-word` pins.
|
the carry can replace the refusal `mark-inside-word` pinned.
|
||||||
- [ ] **2e5 — Combined documents and the collision property.** Documents combining nodes rather
|
- [ ] **2e5 — Combined documents and the collision property.** Documents combining nodes rather
|
||||||
than isolating one, and the gate's collision property: no two corpus documents may emit
|
than isolating one, and the gate's collision property: no two corpus documents may emit
|
||||||
the same bytes — one spelling for two documents is a round-trip break no parser can undo,
|
the same bytes — one spelling for two documents is a round-trip break no parser can undo,
|
||||||
|
|||||||
Reference in New Issue
Block a user