mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 11:48:29 +01:00
Answer stability round 1: 1 🔴, 3 🟡 (0 misread); drop the probe files, keep a one-cell delimiter unescaped, and fix the docs
This commit is contained in:
@@ -38,6 +38,7 @@ In `docs/decisions.md`:
|
|||||||
- A callout title keeps its link targets
|
- A callout title keeps its link targets
|
||||||
- The portable flavour's spellings
|
- The portable flavour's spellings
|
||||||
- The portable reader refuses what GFM's reading would drop
|
- The portable reader refuses what GFM's reading would drop
|
||||||
|
- The portable reader reads what GFM's reading keeps
|
||||||
- The HTML dialect
|
- The HTML dialect
|
||||||
- `htmlToAdf` also reads the dialect as a browser serializes it
|
- `htmlToAdf` also reads the dialect as a browser serializes it
|
||||||
- No runtime dependencies
|
- No runtime dependencies
|
||||||
|
|||||||
+7
-4
@@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- `portableMarkdownToAdf` reads GFM's pipe table further: a body row holding fewer cells than the
|
- **Breaking:** `portableMarkdownToAdf` reads more of GFM's pipe table. A body row holding fewer
|
||||||
header is padded with empty cells, a line holding no `|` after the table is a row of one cell,
|
cells than the header is padded with empty cells, where `0.3.1` returned `malformed-pipe-table`.
|
||||||
and `a |` over `--- |` is a one-column table. A row holding more cells stays an error result.
|
A line after the table that holds no `|` and opens no other block is a row of one cell, where it
|
||||||
`MIGRATION.md` shows the change.
|
was a paragraph. A row holding more cells stays an error result.
|
||||||
|
- **Breaking:** `a |` over `--- |` is a one-column table. `portableMarkdownToAdf` reads it as one,
|
||||||
|
and `losslessMarkdownToAdf` returns `malformed-pipe-table` for it, as for any table without
|
||||||
|
leading pipes, where `0.3.1` read both as a paragraph. `MIGRATION.md` shows the change.
|
||||||
- `adfToPortableMarkdown` keeps a link around the note standing in place of a node it marks, such
|
- `adfToPortableMarkdown` keeps a link around the note standing in place of a node it marks, such
|
||||||
as a stored image: `[_(image not included)_](https://example.com/a)`.
|
as a stored image: `[_(image not included)_](https://example.com/a)`.
|
||||||
- **Breaking:** `readAdfDocument` replaces `isAdfDocument`: it returns a `Result<AdfDocument>` whose
|
- **Breaking:** `readAdfDocument` replaces `isAdfDocument`: it returns a `Result<AdfDocument>` whose
|
||||||
|
|||||||
+13
-7
@@ -22,13 +22,19 @@ class that keeps its fields behind getters (Vue 2, MobX) is refused too: hand in
|
|||||||
`JSON.parse(JSON.stringify(document))`. A document holding more than 10,000,000 JSON values is
|
`JSON.parse(JSON.stringify(document))`. A document holding more than 10,000,000 JSON values is
|
||||||
`unsupported-node-shape`.
|
`unsupported-node-shape`.
|
||||||
|
|
||||||
`portableMarkdownToAdf` reads more of GFM's pipe table. A body row holding fewer cells than the
|
`portableMarkdownToAdf` reads more of GFM's pipe table:
|
||||||
header is padded with empty cells, where `0.3.1` returned `malformed-pipe-table`. A line holding no
|
|
||||||
`|` after a table is a row of one cell, where it ended the table and read as a paragraph. `a |` over
|
- A body row holding fewer cells than the header is padded with empty cells, where `0.3.1`
|
||||||
`--- |` is a one-column table, where it read as a paragraph. A markdown string that relied on the
|
returned `malformed-pipe-table`.
|
||||||
old reading takes a blank line between the table and the paragraph. `losslessMarkdownToAdf` reads
|
- A line after a table that holds no `|` and opens no other block is a row of one cell, where it
|
||||||
the first two as before, and now returns `malformed-pipe-table` for `a |` over `--- |` too, as it
|
ended the table and read as a paragraph. Markdown that relied on that takes a blank line between
|
||||||
does for any table without leading pipes.
|
the table and the paragraph.
|
||||||
|
- Two lines that read as a paragraph in `0.3.1`, a line ending in `|` over a line of `-` runs
|
||||||
|
ending in `|` (`a |` over `--- |`), are a one-column table. `losslessMarkdownToAdf` returns
|
||||||
|
`malformed-pipe-table` for them too, as for any table without leading pipes. In either reader,
|
||||||
|
write the first line as `a \|` to keep both as text.
|
||||||
|
|
||||||
|
`losslessMarkdownToAdf` reads the first two as before.
|
||||||
|
|
||||||
## From `0.2.0` to `0.3.0`
|
## From `0.2.0` to `0.3.0`
|
||||||
|
|
||||||
|
|||||||
@@ -602,7 +602,7 @@ Which reader raises which code: [The calls](#the-calls). Codes every direction r
|
|||||||
| Code | Fires when | What you can do |
|
| Code | Fires when | What you can do |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `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-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 `---` delimiter row, a delimiter row whose cell count differs from the header's, or a body row holding more cells than the header; in `losslessMarkdownToAdf` also a body row holding fewer cells, an alignment colon, and a header or delimiter row not opening with a pipe | add a `---` row under the header and give no row more cells than the header, and for `losslessMarkdownToAdf` the header's count exactly; for `losslessMarkdownToAdf`, also open every row with a pipe and drop the colons. To keep the lines literal text, escape the leading pipe of every one — escaping a single row leaves the next to open a fresh table and fail the same way — or, for a table without leading pipes, write one `\|` of its delimiter row as `\\|` |
|
| `malformed-pipe-table` | a pipe row that is no pipe table — a missing `---` delimiter row, a delimiter row whose cell count differs from the header's, or a body row holding more cells than the header; in `losslessMarkdownToAdf` also a body row holding fewer cells, an alignment colon, and a header or delimiter row not opening with a pipe | add a `---` row of the header's cell count under the header and give no body row more cells; for `losslessMarkdownToAdf`, also give every row the header's count, open every row with a pipe and drop the colons. To keep the lines literal text, escape the leading pipe of every one — escaping a single row leaves the next to open a fresh table and fail the same way — or, for a table without leading pipes, write one `\|` of its delimiter row as `\\|` |
|
||||||
| `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 type |
|
| `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 type |
|
||||||
| `unknown-directive-name` | a directive whose name is no node or mark this version spells | check the name in [`spec/flavour.md`](https://github.com/larvit/adf-codec/blob/main/spec/flavour.md), or escape the prefix as `\!adf:`; the spelling itself is well formed, so a later minor may give the name meaning |
|
| `unknown-directive-name` | a directive whose name is no node or mark this version spells | check the name in [`spec/flavour.md`](https://github.com/larvit/adf-codec/blob/main/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 markdown holds raw HTML, a comment and a processing instruction among them; the HTML handed to `htmlToAdf` holds HTML `adfToHtml` does not write, in a browser's spelling or its own, such as an edit to what a carry element shows, unless a code under [Either direction](#either-direction) names the cause | remove the construct, or write what it holds as markdown; for `htmlToAdf`, write the HTML as [`spec/html.md`](https://github.com/larvit/adf-codec/blob/main/spec/html.md) spells it |
|
| `unmappable-html` | the markdown holds raw HTML, a comment and a processing instruction among them; the HTML handed to `htmlToAdf` holds HTML `adfToHtml` does not write, in a browser's spelling or its own, such as an edit to what a carry element shows, unless a code under [Either direction](#either-direction) names the cause | remove the construct, or write what it holds as markdown; for `htmlToAdf`, write the HTML as [`spec/html.md`](https://github.com/larvit/adf-codec/blob/main/spec/html.md) spells it |
|
||||||
@@ -730,10 +730,11 @@ Serves [Goals](#goals) 1, 4 and 5.
|
|||||||
- Both readers narrow GFM's pipe table. A body row holding more cells than the header is an error
|
- Both readers narrow GFM's pipe table. A body row holding more cells than the header is an error
|
||||||
result, where GFM cuts the extra cells.
|
result, where GFM cuts the extra cells.
|
||||||
- `losslessMarkdownToAdf` narrows it further. A body row holding fewer cells is an error result,
|
- `losslessMarkdownToAdf` narrows it further. A body row holding fewer cells is an error result,
|
||||||
where GFM pads it, and a line holding no `|` ends the table, where GFM reads it as a row. It
|
where GFM pads it, and a later line without a leading `|` ends the table, where GFM reads it as
|
||||||
needs a leading `|`: a header or delimiter row without one is an error result. It refuses an
|
a row. A header or delimiter row without a leading `|` is an error result, and so is an
|
||||||
alignment colon in the delimiter row too, since ADF holds no column alignment.
|
alignment colon in the delimiter row, since ADF holds no column alignment.
|
||||||
`portableMarkdownToAdf` pads, reads the line as a row, accepts both and drops the alignment. The
|
`portableMarkdownToAdf` pads a short row, reads a line holding no `|` as a row of one cell unless
|
||||||
|
it opens another block, reads a table without leading pipes, and drops the alignment. The
|
||||||
trailing pipe is canonical output, optional in input.
|
trailing pipe is canonical output, optional in input.
|
||||||
- Past that and `~~`, `losslessMarkdownToAdf` reads no GFM: an autolink literal and a `- [ ]`
|
- Past that and `~~`, `losslessMarkdownToAdf` reads no GFM: an autolink literal and a `- [ ]`
|
||||||
marker stay text, and a checklist is the `taskList` directive — `portableMarkdownToAdf` turns the
|
marker stay text, and a checklist is the `taskList` directive — `portableMarkdownToAdf` turns the
|
||||||
|
|||||||
+10
-3
@@ -393,9 +393,16 @@ breaks Goal 4. A refusal builds no table, but it still breaks Goal 4: a bot gets
|
|||||||
GitHub renders. Goal 7 is a Maintain goal, holding whatever it costs, so it wins over Goal 4, an
|
GitHub renders. Goal 7 is a Maintain goal, holding whatever it costs, so it wins over Goal 4, an
|
||||||
Achieve goal.
|
Achieve goal.
|
||||||
|
|
||||||
A shape whose GFM reading drops nothing is read as GFM reads it: a short body row is padded with
|
|
||||||
empty cells, a line holding no `|` after the table is a row of one cell, and `a |` over `--- |` is
|
## The portable reader reads what GFM's reading keeps
|
||||||
a one-column table.
|
|
||||||
|
2026-11-09, plan item 112, approved by the maintainer. Goals 4 and 7. Valid while GFM's reading
|
||||||
|
of a shape drops no content.
|
||||||
|
|
||||||
|
Where GFM's reading of a shape drops nothing, `portableMarkdownToAdf` reads it as GFM does. A pipe
|
||||||
|
table's short body row is padded with empty cells, a line holding no `|` that opens no other
|
||||||
|
block is a body row of one cell, and `a |` over `--- |` is a one-column table. `losslessMarkdownToAdf`
|
||||||
|
keeps its stricter reading (§The portable reader refuses what GFM's reading would drop).
|
||||||
|
|
||||||
## The HTML dialect
|
## The HTML dialect
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -367,13 +367,14 @@ inline layer's ordinary CommonMark escaping yields the pipe; each cell is the in
|
|||||||
one paragraph, trimmed; canonical form pads cells with single spaces and ends rows with `|`
|
one paragraph, trimmed; canonical form pads cells with single spaces and ends rows with `|`
|
||||||
(optional in input). Named errors: a delimiter row whose cell count differs from the header's,
|
(optional in input). Named errors: a delimiter row whose cell count differs from the header's,
|
||||||
a body row holding more cells than the header, and, in `losslessMarkdownToAdf`, a body row
|
a body row holding more cells than the header, and, in `losslessMarkdownToAdf`, a body row
|
||||||
holding fewer cells and an alignment colon in the delimiter row — ADF holds no column alignment. In a pipe cell a hard break is `!adf:hardBreak{}` and a literal `|` is `\|`; a
|
holding fewer cells and an alignment colon in the delimiter row — ADF holds no column alignment.
|
||||||
`|` inside a quoted attribute value is already `\u007c`, so the split never reaches it.
|
In a pipe cell a hard break is `!adf:hardBreak{}` and a literal `|` is `\|`; a `|` inside a quoted
|
||||||
|
attribute value is already `\u007c`, so the split never reaches it.
|
||||||
`portableMarkdownToAdf` reads GFM's table: any row may drop its leading `|`, a header over a
|
`portableMarkdownToAdf` reads GFM's table: any row may drop its leading `|`, a header over a
|
||||||
delimiter row of one cell reads as a one-column table (`a |` over `--- |`), a body row continues
|
delimiter row of one cell reads as a one-column table (`a |` over `--- |`), a body row continues
|
||||||
the table until a blank line or a line opening another block, a body row holding fewer cells is
|
the table until a blank line or a line opening another block, a body row holding fewer cells is
|
||||||
padded with empty cells, a line holding no `|` is a body row of one cell, and an alignment colon is
|
padded with empty cells, a line holding no `|` that opens no other block is a body row of one
|
||||||
read and its alignment dropped.
|
cell, and an alignment colon is read and its alignment dropped.
|
||||||
|
|
||||||
The directive form nests cells as containers of block content inside `tableRow` containers:
|
The directive form nests cells as containers of block content inside `tableRow` containers:
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
import '../html/emit/adf-to-html.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../markdown/flavour.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../markdown/flavour.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../../markdown/flavour.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../emit/adf-to-html.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../html/emit/adf-to-html.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../emit/image.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../../html/emit/adf-to-html.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../parse/blocks.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../../html/emit/adf-to-html.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../portable/conventions.ts';
|
|
||||||
@@ -34,8 +34,7 @@ test('escapes the delimiter row a hard break leaves opening a pipe table with no
|
|||||||
assert.equal(broken('--- | ---'), 'a | b\\\n\\--- | ---\n')
|
assert.equal(broken('--- | ---'), 'a | b\\\n\\--- | ---\n')
|
||||||
assert.equal(broken(':--- | ---:'), 'a | b\\\n\\:--- | ---:\n')
|
assert.equal(broken(':--- | ---:'), 'a | b\\\n\\:--- | ---:\n')
|
||||||
assert.equal(broken('c | d'), 'a | b\\\nc | d\n')
|
assert.equal(broken('c | d'), 'a | b\\\nc | d\n')
|
||||||
const oneColumn = markdown(adfToLosslessMarkdown(document(paragraph({ text: 'a |', type: 'text' }, { type: 'hardBreak' }, { text: '--- |', type: 'text' }))))
|
assert.equal(readsBack(document(paragraph({ text: 'a |', type: 'text' }, { type: 'hardBreak' }, { text: '--- |', type: 'text' }))), 'a |\\\n--- |\n')
|
||||||
assert.equal(oneColumn, 'a |\\\n\\--- |\n')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('escapes only text that would otherwise open a construct', () => {
|
test('escapes only text that would otherwise open a construct', () => {
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ function openGfmTable(walk: Walk, opener: string): boolean {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// GFM, Tables: a row without its leading `|` continues the table until a blank line or a line opening another block; a line holding no `|` is one cell.
|
// GFM, Tables: a row without its leading `|` continues the table until a blank line or a line opening another block, and a line holding no `|` is one cell.
|
||||||
function pipeTableRow(claims: Claims, line: string): string[] | undefined {
|
function pipeTableRow(claims: Claims, line: string): string[] | undefined {
|
||||||
const cells = pipeCells(line)
|
const cells = pipeCells(line)
|
||||||
if (cells !== undefined || claims.pipeTables !== 'gfm' || isBlankLine(line) || opensBlock(claims, line)) return cells
|
if (cells !== undefined || claims.pipeTables !== 'gfm' || isBlankLine(line) || opensBlock(claims, line)) return cells
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
import '../emit/spelling-questions.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../../html/emit/adf-to-html.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../portable/conventions.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import '../emit/adf-to-markdown.ts';
|
|
||||||
@@ -4,15 +4,16 @@ import { backslashEscape, claimsPipeLine, trimSpace } from './commonmark/grammar
|
|||||||
const alignmentCell = /^:-+:?$|^-+:$/
|
const alignmentCell = /^:-+:?$|^-+:$/
|
||||||
const delimiterCell = /^-+$/
|
const delimiterCell = /^-+$/
|
||||||
|
|
||||||
// spec/flavour.md, Tables: the cells of a row no leading `|` claimed — GFM's form without the outer pipes, one `|` making a one-column row.
|
// spec/flavour.md, Tables: the cells of a row no leading `|` claimed — GFM's form without the outer pipes; a closing `|` after one cell makes a one-column row.
|
||||||
export function barePipeCells(line: string): string[] | undefined {
|
export function barePipeCells(line: string): string[] | undefined {
|
||||||
const cells = splitPipeCells(line, 0)
|
const cells = splitPipeCells(line, 0)
|
||||||
return cells.length > 1 ? withoutClosingCell(cells) : undefined
|
return cells.length > 1 ? withoutClosingCell(cells) : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A one-cell row is no delimiter here: the hard break before it leaves its header line ending in `\`, which splits into two cells.
|
||||||
export function isBareDelimiterRow(line: string): boolean {
|
export function isBareDelimiterRow(line: string): boolean {
|
||||||
const cells = barePipeCells(line)
|
const cells = barePipeCells(line)
|
||||||
return cells !== undefined && isDelimiterRow(cells)
|
return cells !== undefined && cells.length > 1 && isDelimiterRow(cells)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDelimiterRow(cells: readonly string[]): boolean {
|
export function isDelimiterRow(cells: readonly string[]): boolean {
|
||||||
|
|||||||
@@ -406,8 +406,10 @@ test('reads GFM\'s short row padded with empty cells, a line holding no pipe as
|
|||||||
assert.deepEqual(read('a |\n:--- |\nb\n'), [column(['a', 'b'])])
|
assert.deepEqual(read('a |\n:--- |\nb\n'), [column(['a', 'b'])])
|
||||||
assert.deepEqual(read('| a |\n| --- |\nb\n'), [column(['a', 'b'])])
|
assert.deepEqual(read('| a |\n| --- |\nb\n'), [column(['a', 'b'])])
|
||||||
assert.deepEqual(read('a\n--- |\n'), [said('a --- |')])
|
assert.deepEqual(read('a\n--- |\n'), [said('a --- |')])
|
||||||
const long = portableMarkdownToAdf('a | b\n--- | ---\nc | d | e\n')
|
for (const refused of ['a | b\n--- | ---\nc | d | e\n', '| a | b |\n| --- |\n']) {
|
||||||
assert.deepEqual(long.ok ? undefined : long.error.code, 'malformed-pipe-table')
|
const result = portableMarkdownToAdf(refused)
|
||||||
|
assert.deepEqual(result.ok ? undefined : result.error.code, 'malformed-pipe-table', refused)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('reads what CommonMark reads where no row reads, a directive and a carry fence among it, an image sharing its paragraph with a marker among it', () => {
|
test('reads what CommonMark reads where no row reads, a directive and a carry fence among it, an image sharing its paragraph with a marker among it', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user