From 6182ba6b633bf5b37b6c3fe8709567e6ff89618d Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 10 Oct 2026 11:11:06 +0200 Subject: [PATCH] Answer architecture round 1: closeLeaf records the block definitions end beside the blocks it pushes, openedList's comment names the exception, the README drops its counts, the changelog puts other changes last, and item 98 files the blank-line re-scan --- CHANGELOG.md | 12 ++++++------ README.md | 15 ++++++++------- src/markdown/parse/blocks.ts | 18 ++++++++---------- todo.md | 15 +++++++++++++-- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec3350d..8873feb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,6 @@ CommonMark reads it: `- a` then `* b`, `1. a` then `1) b`. `adfToLosslessMarkdown` gives a list right after one of its kind the other marker, `* ` or `1)`, in place of the `::listBreak` separator. Convert stored markdown per `MIGRATION.md`. -- **Breaking:** two lists a link reference definition parts read as two lists, as CommonMark - reads them, where they read as one; see `MIGRATION.md`. - **Breaking:** each call names the markdown it reads or writes. `adfToMarkdown` and `markdownToAdf` are now `adfToLosslessMarkdown` and `losslessMarkdownToAdf`. A bot's or a model's markdown reads in `portableMarkdownToAdf`, and markdown known to be strict CommonMark in the new @@ -58,6 +56,8 @@ `losslessMarkdownToAdf` refuses a directive mark wrapping only hard breaks as `unsupported-node-shape`. `adfToLosslessMarkdown` writes a hard break holding marks as `!adf:carry{json="…"}`. See `MIGRATION.md`. +- **Breaking:** two lists a link reference definition parts read as two lists, as CommonMark + reads them, where `0.1.0` read one; see `MIGRATION.md`. - `adfToLosslessMarkdown` and `adfToPortableMarkdown` write text holding a long run of `*`, `_`, `~` or backticks, or a heading ending in a long run of `#`, in time linear in the run: a run of 20000 `*` took seconds before, and now takes milliseconds. @@ -79,16 +79,16 @@ - Fix a cyclic document, in which an object holds itself: `adfToLosslessMarkdown` and `adfToPortableMarkdown` refuse it as `not-an-adf-document` and `isAdfDocument` returns `false`, where all three hung. -- The README opens with what the package does and for whom, and `package.json` carries search - keywords. -- The source moves to [github.com/larvit/adf-codec](https://github.com/larvit/adf-codec), where - issues and pull requests go. - Add `adfToPortableMarkdown` and `portableMarkdownToAdf`, a lossy pair converting ADF to and from markdown GitHub, GitLab and Obsidian render: alerts, callouts, task lists, `==highlights==` and pipe tables. A task item keeps its paragraphs, then its nested task lists; the rest of the item, such as an image or a code block, stands after the task list. - Spell `rule`'s `color`, `style` and `weight`, `layoutSection`'s `columnRuleStyle` and a link's `collection`, `id` and `occurrenceKey` directly where they rode the opaque carry. +- The README opens with what the package does and for whom, and `package.json` carries search + keywords. +- The source moves to [github.com/larvit/adf-codec](https://github.com/larvit/adf-codec), where + issues and pull requests go. ## 0.1.0 diff --git a/README.md b/README.md index d6a73cc..a847a8b 100644 --- a/README.md +++ b/README.md @@ -239,8 +239,8 @@ Serves Goals 1, 4 and 5. without a delimiter row, is an error result; each can be kept literal (`spec/flavour.md`). - A document any reader built, written back with `adfToLosslessMarkdown`, takes the library's canonical spelling, which round-trips byte-identically. -- Six CommonMark spellings parse without an error and build a document the reference - implementation renders differently. Four because ADF holds no node for what it renders: +- These CommonMark spellings parse without an error and build a document the reference + implementation renders differently, where ADF holds no node for what it renders: - An empty link text reads as its destination, `[](/url)` as `/url` linked to `/url`, and `[]()` as nothing. A link text holding only hard breaks reads the same, followed by its hard breaks. - An image reads as an image only alone in its paragraph. Anywhere else — beside other text, in @@ -253,11 +253,12 @@ Serves Goals 1, 4 and 5. - An image's title becomes its caption when the image stays an image, and its link's title when it becomes linked alt text. Inside another link, the title is dropped. - And two more: a shortcut reference matching its definition only under Unicode case folding - stays unresolved, and a link whose text holds an autolink keeps the inner link and leaves the - outer brackets literal text, which the spec requires and the reference itself breaks, nesting one - `` in the other. `corpus/commonmark-spec/exceptions.json` pins the first four `node-model` - where the suite reaches them and the fifth `pending`; the suite holds no example of the last. + And these: a shortcut reference matching its definition only under Unicode case folding stays + unresolved, and a link whose text holds an autolink keeps the inner link and leaves the outer + brackets literal text, which the spec requires and the reference itself breaks, nesting one `` + in the other. `corpus/commonmark-spec/exceptions.json` pins the spellings ADF holds no node for + `node-model` where the suite reaches them, and the case-folded reference `pending`; the suite + holds no example of a link holding an autolink. - Not every document converts back: `adfToLosslessMarkdown` refuses a document nesting past 500 levels, and the node shapes the `unsupported-node-shape` row lists. Show the refusal and keep the document read-only; saving markdown you could not produce is the loss the round-trip exists to diff --git a/src/markdown/parse/blocks.ts b/src/markdown/parse/blocks.ts index 8301004..a5abe84 100644 --- a/src/markdown/parse/blocks.ts +++ b/src/markdown/parse/blocks.ts @@ -75,8 +75,8 @@ type ContainerStack = { type Walk = ParsedBlocks & { claims: Claims - // The block a paragraph of link reference definitions alone last closed after, which ends a list as any block does. - definedAfter: Block | undefined + // The last block before a paragraph of link reference definitions alone, which ends a list as any block does. + endedByDefinitions: Block | undefined leaf: OpenLeaf | undefined leafOpeners: Map> position: SourcePosition @@ -91,8 +91,8 @@ export function parseBlocks(markdown: string, claims: Claims): ParsedBlocks { const walk: Walk = { blocks: [], claims, - definedAfter: undefined, definitions: new Map(), + endedByDefinitions: undefined, leaf: undefined, leafOpeners: new Map(), position: { line: 1, offset: 0 }, @@ -249,12 +249,12 @@ function openContainer(walk: Walk, start: ContainerStart): void { walk.stack.push({ blocks, indentation: start.indentation, kind: 'item', list }) } -// An item opening after its list's last item closed, as an empty item a blank line ends does, continues that list. +// An item opening after its list's last item closed, as an empty item a blank line ends does, continues that list, unless definitions ended it. function openedList(walk: Walk, start: Extract): ListBlock { if (!start.fresh) return start.list const blocks = currentBlocks(walk) const previous = blocks.at(-1) - if ((previous?.kind === 'bulletList' || previous?.kind === 'orderedList') && previous !== walk.definedAfter && continuesList(previous, start.list)) return previous + if ((previous?.kind === 'bulletList' || previous?.kind === 'orderedList') && previous !== walk.endedByDefinitions && continuesList(previous, start.list)) return previous blocks.push(start.list) return start.list } @@ -466,7 +466,8 @@ function closeLeaf(walk: Walk): void { if (leaf === undefined) return if (leaf.kind === 'paragraph') { const paragraph = takeParagraph(walk) - if (paragraph !== undefined) currentBlocks(walk).push(bareTableFault(paragraph, walk.claims) ?? paragraph) + if (paragraph === undefined) walk.endedByDefinitions = currentBlocks(walk).at(-1) + else currentBlocks(walk).push(bareTableFault(paragraph, walk.claims) ?? paragraph) return } walk.leaf = undefined @@ -517,10 +518,7 @@ function takeParagraph(walk: Walk): Extract | unde if (leaf?.kind !== 'paragraph') return undefined walk.leaf = undefined const text = readLinkDefinitions(walk.definitions, leaf.lines.join('\n')) - if (text === '') { - walk.definedAfter = currentBlocks(walk).at(-1) - return undefined - } + if (text === '') return undefined const kept = leaf.positions[leaf.lines.length - text.split('\n').length] return { kind: 'paragraph', position: kept ?? leaf.position, text } } diff --git a/todo.md b/todo.md index b21ac48..a59c61f 100644 --- a/todo.md +++ b/todo.md @@ -6,7 +6,7 @@ `Bar = 9` -`Next ID = 98` +`Next ID = 99` | Goal | W | |---|---| @@ -35,6 +35,7 @@ | 96 | 0.2.1 | defect | **Read and write a text holding many unclosed backtick runs, or many `