mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 11:48:29 +01:00
Refuse a pipe table whose padding would add more cells than the table holds
This commit is contained in:
+2
-1
@@ -3,7 +3,8 @@
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- **Breaking:** `portableMarkdownToAdf` reads more of GFM's pipe table. A body row holding fewer
|
- **Breaking:** `portableMarkdownToAdf` reads more of GFM's pipe table. A body row holding fewer
|
||||||
cells than the header is padded with empty cells, where `0.3.1` returned `malformed-pipe-table`.
|
cells than the header is padded with empty cells, where `0.3.1` returned `malformed-pipe-table`;
|
||||||
|
a table whose padding would add more cells than the table holds is `malformed-pipe-table`.
|
||||||
A line after the table that holds no `|` and opens no other block is a row of one cell, where it
|
A line after the table that holds no `|` and opens no other block is a row of one cell, where it
|
||||||
was a paragraph. A row holding more cells stays an error result.
|
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,
|
- **Breaking:** `a |` over `--- |` is a one-column table. `portableMarkdownToAdf` reads it as one,
|
||||||
|
|||||||
@@ -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 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 `\\|` |
|
| `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, a body row holding more cells than the header, or short rows whose padding would add more cells than the table holds; 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 |
|
||||||
@@ -733,9 +733,10 @@ Serves [Goals](#goals) 1, 4 and 5.
|
|||||||
where GFM pads it, and a later line without a leading `|` ends the table, where GFM reads it as
|
where GFM pads it, and a later line without a leading `|` ends the table, where GFM reads it as
|
||||||
a row. A header or delimiter row without a leading `|` is an error result, and so is 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, since ADF holds no column alignment.
|
alignment colon in the delimiter row, since ADF holds no column alignment.
|
||||||
`portableMarkdownToAdf` pads a short row, reads a line holding no `|` as a row of one cell unless
|
`portableMarkdownToAdf` pads a short row with empty cells, as long as the padding adds no more
|
||||||
it opens another block, reads a table without leading pipes, and drops the alignment. The
|
cells than the table holds, reads a line holding no `|` as a row of one cell unless it opens
|
||||||
trailing pipe is canonical output, optional in input.
|
another block, reads a table without leading pipes, and drops the alignment. The 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
|
||||||
marker into a `taskList`.
|
marker into a `taskList`.
|
||||||
|
|||||||
@@ -404,6 +404,13 @@ table's short body row is padded with empty cells, a line holding no `|` that op
|
|||||||
block is a body row of one cell, and `a |` over `--- |` is a one-column table. `losslessMarkdownToAdf`
|
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).
|
keeps its stricter reading (§The portable reader refuses what GFM's reading would drop).
|
||||||
|
|
||||||
|
Padding builds cells the markdown never holds, so a header of N columns over M one-cell rows would
|
||||||
|
build N×M cells from N+M cells of source, against Goal 9. `portableMarkdownToAdf` pads a table only
|
||||||
|
while the cells the padding adds stay within the cells the table's source holds (header, delimiter
|
||||||
|
and body rows); past that, the table is `malformed-pipe-table`, and the markdown writes the empty
|
||||||
|
cells out. The output then stays within twice the input's cells, and the bound needs no number of
|
||||||
|
its own.
|
||||||
|
|
||||||
## The HTML dialect
|
## The HTML dialect
|
||||||
|
|
||||||
2026-08-23, the maintainer; Atlassian's HTML+ 2026-10-28, the maintainer. Goals 6 and 8. Valid
|
2026-08-23, the maintainer; Atlassian's HTML+ 2026-10-28, the maintainer. Goals 6 and 8. Valid
|
||||||
|
|||||||
+3
-2
@@ -373,8 +373,9 @@ 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 `|` that opens no other block is a body row of one
|
padded with empty cells, a line holding no `|` that opens no other block is a body row of one cell,
|
||||||
cell, and an alignment colon is read and its alignment dropped.
|
and an alignment colon is read and its alignment dropped. The padding of a table may add no more
|
||||||
|
cells than its header, delimiter row and body rows hold; past that it is a named error.
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
|
|||||||
@@ -551,6 +551,15 @@ function pipeTableBlock(rows: readonly [string[], ...string[][]], position: Sour
|
|||||||
const padsShortRows = claims.pipeTables === 'gfm'
|
const padsShortRows = claims.pipeTables === 'gfm'
|
||||||
const ragged = [delimiter, ...body].find((row) => (padsShortRows && row !== delimiter ? row.length > header.length : row.length !== header.length))
|
const ragged = [delimiter, ...body].find((row) => (padsShortRows && row !== delimiter ? row.length > header.length : row.length !== header.length))
|
||||||
if (ragged !== undefined) return faultedBlock(`a pipe table row holds ${cellCount(ragged.length)} where its header holds ${cellCount(header.length)}`, position)
|
if (ragged !== undefined) return faultedBlock(`a pipe table row holds ${cellCount(ragged.length)} where its header holds ${cellCount(header.length)}`, position)
|
||||||
|
let held = header.length + delimiter.length
|
||||||
|
let padding = 0
|
||||||
|
for (const row of body) {
|
||||||
|
held += row.length
|
||||||
|
padding += header.length - row.length
|
||||||
|
}
|
||||||
|
if (padding > held) {
|
||||||
|
return faultedBlock(`a pipe table's short rows would pad ${cellCount(padding)}, more than the ${cellCount(held)} it holds: write the empty cells out`, position)
|
||||||
|
}
|
||||||
const padded = body.map((row) => [...row, ...Array.from({ length: header.length - row.length }, () => '')])
|
const padded = body.map((row) => [...row, ...Array.from({ length: header.length - row.length }, () => '')])
|
||||||
return { kind: 'table', position, rows: [header, ...padded] }
|
return { kind: 'table', position, rows: [header, ...padded] }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -406,6 +406,12 @@ 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 wide = (rows: number): string => `a | b | c | d\n--- | --- | --- | ---\n${'x\n'.repeat(rows)}`
|
||||||
|
assert.ok(portableMarkdownToAdf(wide(4)).ok)
|
||||||
|
const padded = portableMarkdownToAdf(wide(5))
|
||||||
|
assert.deepEqual(padded.ok ? undefined : [padded.error.code, padded.error.position], ['malformed-pipe-table', { line: 1, offset: 0 }])
|
||||||
|
const amplified = portableMarkdownToAdf(`${'a|'.repeat(2000)}\n${'-|'.repeat(2000)}\n${'x\n'.repeat(2000)}`)
|
||||||
|
assert.deepEqual(amplified.ok ? undefined : amplified.error.code, 'malformed-pipe-table')
|
||||||
for (const refused of ['a | b\n--- | ---\nc | d | e\n', '| a | b |\n| --- |\n']) {
|
for (const refused of ['a | b\n--- | ---\nc | d | e\n', '| a | b |\n| --- |\n']) {
|
||||||
const result = portableMarkdownToAdf(refused)
|
const result = portableMarkdownToAdf(refused)
|
||||||
assert.deepEqual(result.ok ? undefined : result.error.code, 'malformed-pipe-table', refused)
|
assert.deepEqual(result.ok ? undefined : result.error.code, 'malformed-pipe-table', refused)
|
||||||
|
|||||||
Reference in New Issue
Block a user