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:
+1
-4
@@ -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
@@ -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` |
|
||||
|
||||
@@ -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
@@ -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`
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user