From d69ffac722826b0b94eace4f7a97d20863ae7714 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 14 Oct 2026 12:57:15 +0200 Subject: [PATCH] =?UTF-8?q?Answer=20stability=20round=201:=201=20?= =?UTF-8?q?=F0=9F=94=B4,=202=20=F0=9F=9F=A1;=20spell=20list=20hints=20from?= =?UTF-8?q?=20the=20node=20and=20the=20marker=20alternation,=20pin=20both?= =?UTF-8?q?=20pair-fault=20sites?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/markdown/parse/markdown-to-adf.test.ts | 12 +++++++----- src/markdown/parse/markdown-to-adf.ts | 17 +++++++++++------ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/markdown/parse/markdown-to-adf.test.ts b/src/markdown/parse/markdown-to-adf.test.ts index c814898..769e7f1 100644 --- a/src/markdown/parse/markdown-to-adf.test.ts +++ b/src/markdown/parse/markdown-to-adf.test.ts @@ -333,15 +333,15 @@ test('names the CommonMark spelling to write for the directive form a node Commo assert.equal(content(losslessMarkdownToAdf('!adf:blockquote\nPart.\n!adf:/blockquote\n')), named('blockquote', 'each line behind `> `')) assert.equal(content(losslessMarkdownToAdf('!adf:heading {level=2}\nPart.\n!adf:/heading\n')), named('heading', 'one `#` per level before its text, `## Title` at level 2')) assert.equal(content(losslessMarkdownToAdf('!adf:paragraph\nPart.\n!adf:/paragraph\n')), named('paragraph', 'its text alone')) - assert.equal(content(losslessMarkdownToAdf('!adf:bulletList\n!adf:listItem\nPart.\n!adf:/listItem\n!adf:/bulletList\n')), named('bulletList', 'each item behind `- `')) + assert.equal(content(losslessMarkdownToAdf('!adf:bulletList\n!adf:listItem\nPart.\n!adf:/listItem\n!adf:/bulletList\n')), named('bulletList', 'each item behind `- `, or behind `* ` right after another bullet list')) assert.equal( - content(losslessMarkdownToAdf('!adf:orderedList {order=1}\n!adf:listItem\nPart.\n!adf:/listItem\n!adf:/orderedList\n')), - named('orderedList', 'each item behind `1. `'), + content(losslessMarkdownToAdf('!adf:orderedList {order=5}\n!adf:listItem\nPart.\n!adf:/listItem\n!adf:/orderedList\n')), + named('orderedList', 'each item behind its number, counting up from `5. `, or from `5) ` right after another ordered list'), ) - assert.equal(content(losslessMarkdownToAdf('!adf:codeBlock\n```\nx\n```\n!adf:/codeBlock\n')), named('codeBlock', 'its text in a ``` fence')) + assert.equal(content(losslessMarkdownToAdf('!adf:codeBlock\n```\nx\n```\n!adf:/codeBlock\n')), named('codeBlock', 'its text in a fence of more backticks than any run it holds, its language after the opening fence')) assert.equal( content(losslessMarkdownToAdf('!adf:mediaSingle {layout=center}\n!adf:media {alt=a type=external url=u}\n!adf:/mediaSingle\n')), - named('mediaSingle', '`![alt](url)`, or `[![alt](url)](href "title")` when linked'), + named('mediaSingle', '`![alt](url)`, or `[![alt](url)](href)` when linked'), ) assert.equal( content(losslessMarkdownToAdf('!adf:table\n!adf:tableRow\n!adf:tableHeader\nPart.\n!adf:/tableHeader\n!adf:/tableRow\n!adf:/table\n')), @@ -575,6 +575,8 @@ test('names the marks key no marks read back from', () => { const inBreakout = (cause: string): string => `unsupported-node-shape: the breakout mark in the marks attribute of rule: ${cause}` assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="breakout{width=1 mode=wide}"}\n')), inBreakout('the {attrs} keys read in alphabetical order: mode before width')) assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="breakout{mode}"}\n')), inBreakout('an attribute reads key=value, the value bare or double-quoted: this one does not')) + assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="breakout{mode=wide\\"x\\"}"}\n')), inBreakout('an attribute reads key=value, the value bare or double-quoted: this one does not')) + assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="breakout{mode=}"}\n')), inBreakout('an attribute reads key=value, the value bare or double-quoted: this one does not')) assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="breakout{mode=wide"}\n')), inBreakout('the {attrs} closing brace is missing')) assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="em{attrs=empty}strong"}\n')), named) assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="em{attrs=x}"}\n')), 'unsupported-node-shape: the reserved key attrs takes only the value empty: this one spells attrs=x') diff --git a/src/markdown/parse/markdown-to-adf.ts b/src/markdown/parse/markdown-to-adf.ts index 359fed3..6add4b6 100644 --- a/src/markdown/parse/markdown-to-adf.ts +++ b/src/markdown/parse/markdown-to-adf.ts @@ -266,18 +266,23 @@ function directiveNode(block: DirectiveBlock, reading: Reading, path: ConvertErr const readable = withinEngineLimits(() => commonMarkSpelling(built.value, path, depth, { flavour: 'lossless', memo: reading.memo })) if (!readable.ok) return readable if (!readable.value) return built - const spelling = commonMarkSpellings.get(built.value.type) + const spelling = spellingToWrite(built.value) return failure('unsupported-node-shape', `${built.value.type} takes the CommonMark spelling, not the directive form${spelling === undefined ? '' : `: write ${spelling}`}`, path) } -// The nodes `commonMarkSpelling` spells in the lossless flavour. +function spellingToWrite(node: AdfNode): string | undefined { + if (node.type !== 'orderedList') return commonMarkSpellings.get(node.type) + const order = String(nodeAttrs(node)['order']) + return `each item behind its number, counting up from \`${order}. \`, or from \`${order}) \` right after another ordered list` +} + +// One entry per node `commonMarkSpelling` spells in the lossless flavour, kept here because this refusal is its only reader; a node missing here is refused naming no spelling. const commonMarkSpellings: ReadonlyMap = new Map([ ['blockquote', 'each line behind `> `'], - ['bulletList', 'each item behind `- `'], - ['codeBlock', 'its text in a ``` fence'], + ['bulletList', 'each item behind `- `, or behind `* ` right after another bullet list'], + ['codeBlock', 'its text in a fence of more backticks than any run it holds, its language after the opening fence'], ['heading', 'one `#` per level before its text, `## Title` at level 2'], - ['mediaSingle', '`![alt](url)`, or `[![alt](url)](href "title")` when linked'], - ['orderedList', 'each item behind `1. `'], + ['mediaSingle', '`![alt](url)`, or `[![alt](url)](href)` when linked'], ['paragraph', 'its text alone'], ['rule', '`---`'], ['table', 'a pipe table'],