3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-12 11:48:29 +01:00

Answer architecture round 1: a * marker before a first line of * alone takes a backslash, the marker choice is a decision, the spec names which lists alternate, and the stale listBreak test goes

This commit is contained in:
2026-10-09 14:57:52 +02:00
committed by Lillem4n
parent 736750c120
commit 559ad4ec3c
8 changed files with 48 additions and 12 deletions
+1
View File
@@ -13,6 +13,7 @@ In `docs/decisions.md`:
- Markdown in is a canonical fixpoint
- Equality is deep
- `!adf:textBreak{}` parts text CommonMark would join
- A list beside one of its kind takes the other marker
- An empty key spells `empty`
- `-0` is spelled `-0`
- Empty markdown is a document of no blocks
@@ -175,6 +175,25 @@
}
],
"type": "bulletList"
},
{
"content": [
{
"content": [
{
"content": [
{
"text": "**",
"type": "text"
}
],
"type": "paragraph"
}
],
"type": "listItem"
}
],
"type": "bulletList"
}
],
"type": "doc",
@@ -14,3 +14,5 @@
- - Head
* Tail
* \**
+15 -3
View File
@@ -71,6 +71,16 @@ Two adjacent text nodes CommonMark would read back as one are parted by the rese
`!adf:textBreak{}`: a leaf building no node keeps both nodes and asks nothing of the text around
it. A code span holds no directive, so the spans close and reopen around the leaf. The grammar: `spec/flavour.md` §Inline nodes, **Adjacent text nodes**.
## A list beside one of its kind takes the other marker
2026-10-04, the maintainer. Goals 4 and 5. Valid while CommonMark starts a new list where the
bullet character or the ordered delimiter changes.
Every reader parts lists where the marker changes, as CommonMark does. Both writers give a bullet or
task list right after a bullet or task list `* `, and an ordered list right after an ordered one
`1)`; the list after that takes `- ` or `1.` again. CommonMark already spells two adjacent lists, so
no directive parts them. The grammar: `spec/flavour.md` §Canonical form.
## An empty key spells `empty`
2026-10-03, a writer panel and the maintainer. Goals 1 and 6. Valid while no attribute value
@@ -278,7 +288,9 @@ renderer confirming each shape. Reader panels settled `error` as an error panel,
(3 of 3) and a Han, Hangul, kana, Thai, Lao, Khmer or Myanmar character on either side bounding a
delimiter, so `は==日本語==で` (3 of 3), `==한국어==에서만` and `iPhone==専用==` (6 of 7) highlight,
the external image's two forms (6 of 7), a rule opening a list item dropping and the omission
notes (3 of 3), and a list's numbering overflowing into bullets (3 of 3, 5 of 7).
notes (3 of 3), and a list's numbering overflowing into bullets (3 of 3). A 5 of 7 verdict joining
adjacent ordered lists whose numbering breaks into one such list lapsed on 2026-10-09, when a
delimiter change began parting them.
An omission note reads as the converter's, never as the author's.
Reading takes other tools' spellings, since it reads their output and writes none of them.
Rejected: `~sub~` and `^sup^` (`~2~` is a strike on GitHub, so `subsup` drops), underline and colour
@@ -689,8 +701,8 @@ two cannot disagree — which is what makes the kept value a memo rather than a
per level above it otherwise.
The parse and the portable reduction keep each node's readable spelling in a memo, so the
`commonMarkSpelling` ask stops spelling a node once per level above it. `text` and `spelling` carry
no depth and `headroom` is affine in it, so a read at or above the depth that filled the entry
`commonMarkSpelling` ask stops spelling a node once per level above it. The spelled texts and
`spelling` carry no depth and `headroom` is affine in it, so a read at or above the depth that filled the entry
rebases; a read below re-spells, because a hit skips the depth guards the walk it replaces runs and
an ordered list past the marker cap gives way, spending two emitter levels where the parser spent
one. Only what succeeded is kept, so no path minted at another position is ever read.
+4 -3
View File
@@ -19,9 +19,10 @@ normalizes to it through the round-trip.
single tilde or a run of three or more is literal text — and block structure resolves before
inline, so a `~~~` line opens a CommonMark tilde code fence.
- Bullet lists `- `; ordered lists incrementing `1.` `2.` `3.`, the first number the node's
`order` attribute. A list right after a list of its kind takes the other marker, `* ` or `1)`,
since CommonMark starts a new list where the bullet character or the delimiter changes; the
list after that takes `- ` or `1.` again. Continuation lines align with the first character
`order` attribute. A bullet or task list right after a bullet or task list takes `* `, and an
ordered list right after an ordered one `1)`, since CommonMark starts a new list where the
bullet character or the delimiter changes; the list after that takes `- ` or `1.` again, and a
first line of `*` alone after `* ` opens with a backslash, `* \**`. Continuation lines align with the first character
after the marker (two spaces for `- `, three for `1. `); blank lines inside an item are empty
lines, none between a nested list and a CommonMark block above it — one wherever the nested
list's own marker cannot interrupt a paragraph (an ordered list whose first number is not 1, or
@@ -177,6 +177,9 @@ test('alternates the marker between adjacent lists of a kind, the marker change
assert.equal(markdown(adfToLosslessMarkdown(document(nested))), '- - x\n * x\n')
const stars: AdfNode = { content: [{ content: [paragraph({ text: '* *', type: 'text' })], type: 'listItem' }], type: 'bulletList' }
assert.equal(markdown(adfToLosslessMarkdown(document(list, stars))), '- x\n\n* \\* *\n')
const strong: AdfNode = { content: [{ content: [paragraph({ text: '**', type: 'text' }), paragraph({ text: 'y', type: 'text' })], type: 'listItem' }], type: 'bulletList' }
assert.equal(markdown(adfToLosslessMarkdown(document(strong))), '- **\n\n y\n')
assert.equal(markdown(adfToLosslessMarkdown(document(list, strong))), '- x\n\n* \\**\n\n y\n')
const carried: AdfNode = { ...list, attrs: { unknown: 'x' } }
assert.ok(markdown(adfToLosslessMarkdown(document(carried, carried))).includes('```\n\n```adf:bulletList\n'))
assert.ok(markdown(adfToLosslessMarkdown(document(carried, list))).endsWith('```\n\n- x\n'))
+4 -2
View File
@@ -364,8 +364,10 @@ function tryListItemLines(inner: string, marker: string): string | undefined {
if (body.some((line) => line !== '' && isBlankLine(line))) return undefined
const indent = ' '.repeat(marker.length)
const lines = body.map((line, index) => (index === 0 ? `${marker}${line}` : line === '' ? '' : `${indent}${line}`))
if (isThematicBreak(lines[0] ?? '')) return undefined
return lines.join('\n')
const first = lines[0] ?? ''
if (!isThematicBreak(first)) return lines.join('\n')
// A first line of `*` alone beside the `* ` marker is text, since a rule is spelled `---`: a backslash keeps it from reading as one.
return inner.startsWith('*') ? [`${marker}\\${first.slice(marker.length)}`, ...lines.slice(1)].join('\n') : undefined
}
function tryParagraph(node: AdfNode, path: ConvertErrorPath, flavour: WrittenFlavour): Result<EmittedBlock> | undefined {
@@ -284,10 +284,6 @@ test('reads a change of bullet character or ordered delimiter as a second list,
assert.deepEqual(path(losslessMarkdownToAdf('- a\n\n* b\n\n| x |\n')), ['content', 2])
})
test('reads the retired list separator as a directive no node spells', () => {
assert.equal(content(losslessMarkdownToAdf('- a\n\n!adf:listBreak\n\n- b\n')), 'unknown-directive-name: the directive name listBreak reads back to no node; \\!adf: keeps the prefix literal')
})
test('reads the image a pipe cell holds as its linked alt text', () => {
assert.deepEqual(content(losslessMarkdownToAdf('| a |\n| --- |\n| ![x](/u) |\n')), [
{ content: [{ content: [cell('tableHeader', text('a'))], type: 'tableRow' }, { content: [cell('tableCell', marked('x', link('/u')))], type: 'tableRow' }], type: 'table' },