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

Answer prose round 1: one name per thing in the new docs, the changelog points at MIGRATION's rows, item 82 drops its partial file list

This commit is contained in:
2026-10-05 09:43:52 +02:00
committed by Lillem4n
parent dbc8b91f91
commit 380a894589
8 changed files with 21 additions and 29 deletions
+1 -4
View File
@@ -3,10 +3,7 @@
## Unreleased
- **Breaking:** `markdownToAdf` and `plainMarkdownToAdf` refuse a value that is not a string with
the new code `not-a-string`, at position `{ line: 1, offset: 0 }`. Before, `null`, `undefined`
and a non-empty array threw a `TypeError`, a `String` object converted its text, and a number
read as an empty document; pass `String(value)` where a boxed string arrives. A `switch` over
`ConvertErrorCode` naming every code needs the new case.
the new code `not-a-string`; `MIGRATION.md` lists what each value did before.
- **Breaking:** an attribute value that is not a plain object, array or JSON primitive, such as a
`Date`, `Map`, typed array or class instance, makes `isAdfDocument` return `false` and the
converters refuse it as `not-an-adf-document`. Before, the converters wrote it as `{}` or its own
+4 -5
View File
@@ -62,8 +62,9 @@ Markdown the spelling table leaves alone, which `0.2.0` reads as a different doc
### Error codes
`unspellable-character`, `unspellable-line-start` and `unspellable-link` leave `ConvertErrorCode`:
a `switch` naming one stops compiling. A document `adfToMarkdown` refused with one of them now
`unspellable-character`, `unspellable-line-start` and `unspellable-link` leave `ConvertErrorCode`
and `not-a-string` joins it: a `switch` naming one of the three stops compiling, and one naming
every code needs the new case. A document `adfToMarkdown` refused with one of them now
converts; markdown the parser refused with one of them now refuses with the code the table gives.
| Input | `0.1.0` | `0.2.0` |
@@ -83,6 +84,4 @@ converts; markdown the parser refused with one of them now refuses with the code
| an empty node the `::taskItem` spelling row names, written with a closer | `unsupported-node-shape` | parses |
| `null`, `undefined` or a non-empty array, on parse | throws a `TypeError` | `not-a-string` |
| a `String` object, on parse | the document its text spells | `not-a-string`; pass `String(value)` |
| a number, a boolean, a plain object or an empty array, on parse | an empty document | `not-a-string` |
`ConvertErrorCode` gains `not-a-string`: a `switch` naming every code needs its case.
| any other value that is not a string, such as a number or `{}`, on parse | an empty document | `not-a-string` |
+5 -6
View File
@@ -164,9 +164,9 @@ carries it opaquely and restores it unchanged ([`docs/decisions.md`](https://git
`ConvertError` is `{ code, message, path, position? }`. `code` is the exported `ConvertErrorCode`,
stable across minors and safe to `switch` on exhaustively with no `default`; `message` is free text
and may change in any release. A parse always names a position, so `markdownToAdf` returns
`ParseError` and its `position` reads without a guard; an emit reads no source and carries `path`
alone; one handler typed on `ConvertError` takes both, which is what the composed `markdownToHtml`
and may change in any release. A parse always names a position, so `markdownToAdf` and
`plainMarkdownToAdf` return `ParseError`, whose `position` reads without a guard; an emit reads no
source and carries `path` alone; one handler typed on `ConvertError` takes both, which is what the composed `markdownToHtml`
and `htmlToMarkdown` hand back. `path` is the node's place from the document root, alternating
`'content'` and an index, so `path.map((step) => '/' + step).join('')` is a JSON Pointer at the
node — the empty path being the document itself.
@@ -178,8 +178,7 @@ the identical error: fix the input, or set the document aside.
`position` is `{ line, offset }` into the string passed in: `line` counted from 1, `offset` a
UTF-16 code unit, a JavaScript string index rather than a codepoint or a byte offset. It points at
or before the refusal — currently the start of the line the enclosing block begins on; a later
minor may narrow that, never widen it. `not-a-string`, having no string to point into, names
`{ line: 1, offset: 0 }`.
minor may narrow that, never widen it. For `not-a-string` it is `{ line: 1, offset: 0 }`.
Parsing — `markdownToAdf` and `plainMarkdownToAdf`, and `htmlToAdf` at `0.2.0`:
@@ -187,7 +186,7 @@ Parsing — `markdownToAdf` and `plainMarkdownToAdf`, and `htmlToAdf` at `0.2.0`
| --- | --- | --- |
| `malformed-directive` | an `!adf:` the grammar cannot read — a prefix completing no directive, an unclosed container, `[content]` or `{attrs}`, a closer with no container of its name open, a leaf given a body, `{attrs}` out of order or duplicated, invalid JSON in an opaque carry | write the spelling the message names, or keep it literal: escape the prefix — `\!adf:`, block and inline alike — or, for a code fence whose info string opens `adf:`, drop the info string and wrap the fence in `!adf:codeBlock {language="adf:…"}` |
| `malformed-pipe-table` | a pipe row that is no pipe table — a missing or ragged `---` delimiter row, an alignment colon in it, or a row not opening with a pipe | open every row with a pipe and give the delimiter row the header's cell count; to keep the lines literal text instead, escape the leading pipe of every one — escaping a single row leaves the next to open a fresh table and fail the same way |
| `not-a-string` | the value handed in is not a string: `null`, `undefined`, a number, an object, a `String` object among them | pass the input as a string, `String(value)` for a `String` object; the message names the type passed |
| `not-a-string` | the value handed in is not a string, such as `null`, a number or a `String` object | pass the input as a string, `String(value)` for a `String` object; the message names the value's `typeof` |
| `unknown-directive-name` | a directive whose name is no node or mark this version spells | check the name in `spec/flavour.md`, or escape the prefix as `\!adf:`; the spelling itself is well formed, so a later minor may give the name meaning |
| `unmappable-html` | the input holds an HTML construct the documented element set does not map, a comment and a processing instruction among them — at this version that is every raw HTML construct in markdown, the element set landing at `0.2.0` | remove the construct, or write what it holds in the lossless flavour |
| `unmappable-image` | an image sits inside other content that is not another image's description, or carries a title | give the image a paragraph of its own and drop the title |
+3 -3
View File
@@ -383,9 +383,9 @@ handles one cause alike whichever node, attribute or direction raised it.
knows which direction it called and parting them after `0.1.0` is MAJOR (2026-09-23).
- A non-finite number takes two codes: `unsupported-node-shape` parsing, `not-an-adf-document`
emitting — no document holds one, so no round-trip crosses them (2026-09-23).
- A value handed to a reader of markdown or HTML that is not a string is `not-a-string`, its
position `{ line: 1, offset: 0 }` and its path empty: no code read true of it, and
`not-an-adf-document` names the other direction's input (2026-10-05).
- `markdownToAdf`, `plainMarkdownToAdf` and `htmlToAdf` refuse a value that is not a string as
`not-a-string`, at position `{ line: 1, offset: 0 }` with an empty path: no existing code reads
true of it, and `not-an-adf-document` names the emitters' input (2026-10-05).
## `message` and `path`
+1 -1
View File
@@ -11,7 +11,7 @@ import { readEntityReference } from '../commonmark/entity-references.ts'
export type DelimiterRole = 'close' | 'open'
// A segment's kind: `backslash` text, `bracketed` text inside `[…]` (link text, alt, a directive's content), `bracketed-link-target` a link target inside `[…]`, `none` syntax written as is.
// A segment's kind: `backslash` text, `bracketed` text inside `[…]` (link text, alt, a directive's content), `bracketed-link-target` syntax: a link target inside `[…]`, `none` syntax written as is.
export type InlineEscaping = 'backslash' | 'bracketed' | 'bracketed-link-target' | 'none'
export type NodeRange = { first: number; last: number }
+2 -2
View File
@@ -344,7 +344,7 @@ function holdsImage(pieces: readonly Piece[]): boolean {
return pieces.some((piece) => piece.kind === 'image')
}
// An `ownMarks` piece restores its link with its node, so only a link a `nodes` piece spells counts.
// Only a link a `nodes` piece spells makes the outer brackets literal; an `ownMarks` node's link is its own, so the own-marks guard in `closeLink` refuses it.
function holdsLink(pieces: readonly Piece[]): boolean {
return pieces.some((piece) => piece.kind === 'nodes' && marksLink(piece.nodes))
}
@@ -647,7 +647,7 @@ function markType(character: string, used: number): string {
return used === 2 ? 'strong' : 'em'
}
// Nested spellings of one mark (`*(*a*)*`) name it once: a node carries one mark of a type.
// `*(*a*)*` builds one `em`: a node holds at most one mark of each type.
function applyMark(nodes: readonly Inline[], mark: AdfMark): Inline[] {
return nodes.map((node) => {
if (!isNode(node)) return node
+1 -1
View File
@@ -237,7 +237,7 @@ function leafEdges(leaf: AdfNode, previous: AdfNode | undefined, next: AdfNode |
return edges
}
// How many of the leaf's marks the edge whitespace keeps, `undefined` where it goes at a line edge.
// How many of the leaf's marks the edge whitespace keeps, `undefined` where it drops at a line edge.
function edgeDepth(marks: readonly AdfMark[], neighbour: AdfNode | undefined, whitespace: string): number | undefined {
if (whitespace === '') return marks.length
const lineEdge = neighbour === undefined || neighbour.type === 'hardBreak'
+4 -7
View File
@@ -35,7 +35,7 @@
| 68 | 0.2.0 | | **Carry a non-text inline node holding `content` or `text`, a block holding `text`, and a leaf block holding `content`.** | 3 | 3 | 4 | 7 | 1 | 18.2 |
| 75 | 0.2.0 | | **Make the generated-markdown property write block carries as `adf:` fences, canonical and hostile.** | 2 | 2 | 3 | 7 | 1 | 17.5 |
| 65 | 0.2.0 | defect | **Read an image not alone in its paragraph as its alt text linked to its URL, and a titled image alone in its paragraph as the image captioned with its title.** | 5 | 5 | 7 | 7 | 4, 5, 7 | 17.1 |
| 81 | 0.2.0 | | **Fail the gate when the README's error tables list other codes than `ConvertErrorCode`.** | 1 | 2 | 5 | 5 | 3 | 16.3 |
| 81 | 0.2.0 | | **Fail the gate unless the README's error tables list exactly the codes `ConvertErrorCode` holds.** | 1 | 2 | 5 | 5 | 3 | 16.3 |
| 49 | 0.2.0 | | **Read a list whose bullet or ordered delimiter changes as two lists, in both readers, and retire `!adf:listBreak`.** | 4 | 5 | 5 | 8 | 4, 5 | 15.5 |
| 46 | 0.2.0 | | **Publish the bundle size in the README, failing the release pipeline when it drifts.** | 2 | 4 | 5 | 5 | 3, 8 | 14.8 |
| 61 | 0.2.0 | decision | **Have the emitter ask the inline reader how a line reads back, in place of `line-escaping.ts` predicting it.** | 6 | 8 | 3 | 8 | 1 | 14.0 |
@@ -166,7 +166,7 @@ its paragraph, it reads as the image with a `link` mark to `/uri`, which ADF's `
alone in its paragraph, the image reads as its alt text linked to `/uri`, and its URL drops. A
writer panel chose this reading 3 of 3 (2026-10-04), by Goal 6, which outranks Goal 7.
### 81. Fail the gate when the README's error tables list other codes than `ConvertErrorCode`.
### 81. Fail the gate unless the README's error tables list exactly the codes `ConvertErrorCode` holds.
`src/result.test.ts` checks the union against the call sites only, so a code added or removed
leaves the README's tables a false contract under a green gate. Assert that the tables list exactly
@@ -336,11 +336,8 @@ agree. Found by the technical-principles audit, 2026-10-04.
### 82. Give the directive tests and fixtures random UUID v7 ids in place of `a-1`, `a`, `x` and `h`.
Technical principle: tests use random UUID v7 ids. The placeholders sit in
`markdown-to-adf.test.ts`, `adf-to-markdown.test.ts`, `blocks.test.ts` and
`corpus/errors/directive-value-uncanonical.md`, so no lossless directive test reads the digit-led hex
id an editor writes; `plain-markdown-to-adf.test.ts` already uses v7s. Found by the
technical-principles audit of item 78, 2026-10-05.
Technical principle: tests use random UUID v7 ids; `plain-markdown-to-adf.test.ts` already does.
Found by the technical-principles audit of item 78, 2026-10-05.
### 57. Make each `ci.sh` leg build what it reads, so one leg run alone tests the current tree.