Compare commits
2 Commits
fbc719cca4
...
ca3c929ba9
| Author | SHA1 | Date | |
|---|---|---|---|
| ca3c929ba9 | |||
| 05c2db34a1 |
@@ -147,7 +147,7 @@ not take — is
|
||||
`unsupported-node-shape`, the emitter's code for the same mismatch read the other way — one code
|
||||
across both directions for good, since the call site knows which direction it called and parting
|
||||
them after `0.1.0` is MAJOR. `unmappable-html` names the version rather than the element: this one
|
||||
converts no raw HTML, so at `0.3.0` the mapped elements stop erroring and the code stays for what
|
||||
converts no raw HTML, so at `0.2.0` the mapped elements stop erroring and the code stays for what
|
||||
no ADF node carries. A refusal found before its path is known — the block walk's, a directive
|
||||
reader's — is a `ConvertFault`, the code and message alone; the node walk attaches the path as it
|
||||
descends, so a document reports its first error in document order. `not-an-adf-document` carries
|
||||
@@ -176,7 +176,7 @@ A parse names a position for every refusal it returns, so the type says so rathe
|
||||
`Result<T, E extends ConvertError = ConvertError>`, and a direction reading a source returns
|
||||
`Result<T, ParseError>` — `ConvertError` with `position` required. An optional field a direction
|
||||
always fills is a branch a consumer cannot take, and the `!` §11 bans is how they take it anyway.
|
||||
`htmlToAdf` inherits this at `0.3.0`; the composed `markdownToHtml` and `htmlToMarkdown` keep the
|
||||
`htmlToAdf` inherits this at `0.2.0`; the composed `markdownToHtml` and `htmlToMarkdown` keep the
|
||||
wide `Result<T>`, since half their refusals come from an emit stage that read no source.
|
||||
|
||||
## 9. Release automation
|
||||
@@ -297,10 +297,17 @@ someone spells it or pins it.
|
||||
has — for the fallback to refuse at zero rather than walk again; counting every list twice
|
||||
halved the list limit, counting the directive form once doubled the parser's frames per level
|
||||
(the maintainer, 2026-09-18).
|
||||
- Nothing spreads an unbounded array into a call — a node's siblings, a code block's held lines, a
|
||||
mark run's segments: the argument list caps near 125k and throws a `RangeError` where a `Result`
|
||||
is owed. A walk pushes one at a time. A literal spread (`[...value]`) is not the same thing and
|
||||
is fine (4c).
|
||||
- A reader takes the text and an index — a sticky regex whose `lastIndex` the caller sets on the
|
||||
line before it reads, `indexOf` — never a fresh slice per character, and a per-character walk
|
||||
hoists the scan that does not vary with the character. The pipeline persona feeds documents
|
||||
nobody typed, and a megabyte through a quadratic walk is a minute rather than a millisecond.
|
||||
nobody typed, and a megabyte through a quadratic walk is a minute rather than a millisecond. A
|
||||
scan may keep what it read for a later walk of the same text, and the fallback where it kept
|
||||
nothing must be the same reader over the same text at the same index, so the two cannot disagree
|
||||
— which is what makes the kept value a memo rather than a second spelling (4c).
|
||||
- No casts: `as`, `as unknown as`, non-null `!`. A boundary owes a type guard validating the
|
||||
fields it claims (`isAdfDocument`); past it everything is typed. Make invalid states
|
||||
unrepresentable.
|
||||
|
||||
@@ -4,7 +4,7 @@ Lossless conversion between **Atlassian Document Format** (ADF), an extended mar
|
||||
an HTML dialect.
|
||||
|
||||
**Status: published — the markdown round-trip (`adfToMarkdown`, `markdownToAdf`); HTML at
|
||||
`0.3.0`.**
|
||||
`0.2.0`.**
|
||||
Plan: `todo.md`. Decisions: `AGENTS.md`. The flavour's grammar:
|
||||
[`spec/flavour.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/spec/flavour.md).
|
||||
Upgrading from `0.1.0`: [convert your markdown first](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/MIGRATION.md).
|
||||
@@ -44,10 +44,10 @@ adfToMarkdown(doc: AdfDocument): Result<string>
|
||||
markdownToAdf(markdown: string): Result<AdfDocument, ParseError>
|
||||
isAdfDocument(v: unknown): v is AdfDocument
|
||||
|
||||
adfToHtml(doc: AdfDocument): Result<string> // 0.3.0
|
||||
htmlToAdf(html: string): Result<AdfDocument, ParseError> // 0.3.0
|
||||
markdownToHtml(markdown: string): Result<string> // 0.3.0, via ADF
|
||||
htmlToMarkdown(html: string): Result<string> // 0.3.0, via ADF
|
||||
adfToHtml(doc: AdfDocument): Result<string> // 0.2.0
|
||||
htmlToAdf(html: string): Result<AdfDocument, ParseError> // 0.2.0
|
||||
markdownToHtml(markdown: string): Result<string> // 0.2.0, via ADF
|
||||
htmlToMarkdown(html: string): Result<string> // 0.2.0, via ADF
|
||||
```
|
||||
|
||||
`Result<T>` is `{ ok: true; value: T } | { ok: false; error: ConvertError }` — nothing throws.
|
||||
@@ -75,17 +75,17 @@ UTF-16 code unit, a JavaScript string index rather than a codepoint or a byte of
|
||||
or before the refusal — currently the start of the line the enclosing block begins on; a later
|
||||
minor may narrow that, never widen it.
|
||||
|
||||
Parsing — `markdownToAdf`, and `htmlToAdf` at `0.3.0`:
|
||||
Parsing — `markdownToAdf`, and `htmlToAdf` at `0.2.0`:
|
||||
|
||||
| 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 a `carry` | write the spelling the message names, or escape the prefix — `\!adf:`, block and inline alike — to keep it literal text |
|
||||
| `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 |
|
||||
| `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 markdown holds a raw HTML tag, comment or processing instruction | remove it or write it in the flavour — ADF holds no raw-HTML node, and the element mapping lands at `0.3.0` |
|
||||
| `unmappable-html` | the markdown holds a raw HTML tag, comment or processing instruction | remove it or write it in the flavour — ADF holds no raw-HTML node, and the element mapping lands at `0.2.0` |
|
||||
| `unmappable-image` | an image sits inside other content, or carries a title | give the image a paragraph of its own and drop the title |
|
||||
|
||||
Emitting — `adfToMarkdown`, and `adfToHtml` at `0.3.0`:
|
||||
Emitting — `adfToMarkdown`, and `adfToHtml` at `0.2.0`:
|
||||
|
||||
| Code | Fires when | What you can do |
|
||||
| --- | --- | --- |
|
||||
@@ -121,7 +121,7 @@ emit refuses:
|
||||
reference matching its definition only under Unicode case folding stays unresolved. Each is
|
||||
pinned `pending` in `corpus/commonmark-spec/exceptions.json`.
|
||||
- Raw HTML in markdown input is an error result, never a silent drop — a tag, a comment and a
|
||||
processing instruction alike. ADF holds no raw-HTML node; the element mapping ships at `0.3.0`.
|
||||
processing instruction alike. ADF holds no raw-HTML node; the element mapping ships at `0.2.0`.
|
||||
- Not every document converts back: `adfToMarkdown` is partial on valid ADF — a text node holding
|
||||
a carriage return, or a paragraph line beginning with a code span whose backticks read back as a
|
||||
fence. Show the refusal and keep the document read-only; saving markdown you could not produce
|
||||
@@ -134,7 +134,7 @@ emit refuses:
|
||||
is the `taskList` directive.
|
||||
- A document nested deeper than 500 levels is an error result, not a stack overflow.
|
||||
- The emitted formats are semver surface (AGENTS.md §8).
|
||||
- **`0.3.0`** — `htmlToAdf(adfToHtml(doc))` equals `doc`; fidelity HTML cannot express rides
|
||||
- **`0.2.0`** — `htmlToAdf(adfToHtml(doc))` equals `doc`; fidelity HTML cannot express rides
|
||||
`data-*` attributes. Foreign HTML maps a documented element set, an unmappable element is an
|
||||
error, and well-formed HTML only — no tag-soup recovery.
|
||||
|
||||
|
||||
@@ -174,7 +174,6 @@ export function isThematicBreak(line: string): boolean {
|
||||
return holdsThematicBreak(thematicBreakTail(line), line)
|
||||
}
|
||||
|
||||
// A break runs to the line's end, so one scan of that run answers every level the list walk opens.
|
||||
export function thematicBreakTail(text: string): ThematicBreakTail | undefined {
|
||||
let marker: string | undefined
|
||||
let markers = 0
|
||||
@@ -195,6 +194,8 @@ export function thematicBreakTail(text: string): ThematicBreakTail | undefined {
|
||||
return { longest: text.length - first, marker, shortest: text.length - third }
|
||||
}
|
||||
|
||||
// `text` is a suffix of what `tail` was read from, or opens with a space: a length alone names a
|
||||
// suffix, and the marker check is what refuses one starting mid-run — `--- ---` holds ` ---`.
|
||||
export function holdsThematicBreak(tail: ThematicBreakTail | undefined, text: string): boolean {
|
||||
if (tail === undefined) return false
|
||||
return text.length >= tail.shortest && text.length <= tail.longest && text.charAt(0) === tail.marker
|
||||
|
||||
@@ -17,7 +17,7 @@ export type DirectiveLine =
|
||||
| { argument: string | undefined; attributes: DirectiveAttributes; kind: 'opener'; name: string }
|
||||
| { kind: 'closer'; name: string }
|
||||
|
||||
// spans: the directives the content holds, at their offset in it, so reading the slot back never scans them again.
|
||||
// spans keys index content, so the two travel together: separate them and every offset is wrong.
|
||||
export type DirectiveSpan = { attributes: DirectiveAttributes; content: string | undefined; length: number; name: string; spans: NestedSpans }
|
||||
|
||||
export type NestedSpans = ReadonlyMap<number, DirectiveSpan>
|
||||
@@ -254,10 +254,11 @@ function readDirectiveContent(text: string, start: number, depth: number): Read<
|
||||
cursor = span
|
||||
continue
|
||||
}
|
||||
const nested = keepNestedSpan(text, cursor, depth, start, spans)
|
||||
const nested = readNestedDirective(text, cursor, depth + 1)
|
||||
if (nested?.fault !== undefined) return { fault: nested.fault }
|
||||
if (nested !== undefined) {
|
||||
cursor = nested.value
|
||||
spans.set(cursor - start, nested.value)
|
||||
cursor += nested.value.length
|
||||
continue
|
||||
}
|
||||
if (character === ']' && brackets === 0) return { value: { end: cursor, spans } }
|
||||
@@ -268,14 +269,6 @@ function readDirectiveContent(text: string, start: number, depth: number): Read<
|
||||
return { fault: malformedDirective(`an inline directive [content] is unclosed; ${directiveEscape}`) }
|
||||
}
|
||||
|
||||
function keepNestedSpan(text: string, cursor: number, depth: number, start: number, spans: Map<number, DirectiveSpan>): Read<number> | undefined {
|
||||
const nested = readNestedDirective(text, cursor, depth + 1)
|
||||
if (nested === undefined) return undefined
|
||||
if (nested.fault !== undefined) return { fault: nested.fault }
|
||||
spans.set(cursor - start, nested.value)
|
||||
return { value: cursor + nested.value.length }
|
||||
}
|
||||
|
||||
// `undefined` where the span crosses the line ending an inline directive may not cross.
|
||||
function readCodeSpanEnd(text: string, index: number): number | undefined {
|
||||
const opener = backtickRun(text, index)
|
||||
|
||||
@@ -736,3 +736,8 @@ test('carries whitespace CommonMark strips in the reserved text directive', () =
|
||||
// CommonMark strips spaces and tabs alone, so the whitespace beside them is plain text.
|
||||
assert.equal(emitted({ text: '\va\f', type: 'text' }), '\va\f\n')
|
||||
})
|
||||
|
||||
test("joins a mark run's segments as a walk rather than as one call's arguments", () => {
|
||||
const run = Array.from({ length: 200000 }, (): AdfNode => ({ marks: [{ type: 'strong' }], text: 'a', type: 'text' }))
|
||||
assert.equal(markdown(adfToMarkdown(document({ content: run, type: 'paragraph' }))), `**${'a'.repeat(200000)}**\n`)
|
||||
})
|
||||
|
||||
@@ -166,7 +166,7 @@ function emitRun(nodes: readonly AdfNode[], depth: number, firstIndex: number, c
|
||||
const emitted = run.kind === 'plain' ? emitLeaf(run.node, runContext, run.index) : emitMarkedRun(run.nodes, run.mark, depth, run.index, runContext)
|
||||
if (!emitted.ok) return emitted
|
||||
if (emitted.value.carry !== undefined) return emitted
|
||||
segments.push(...emitted.value.segments)
|
||||
for (const segment of emitted.value.segments) segments.push(segment)
|
||||
}
|
||||
return success({ segments })
|
||||
}
|
||||
|
||||
@@ -118,3 +118,7 @@ test('closes the containers a closer names past as unclosed, and crosses no list
|
||||
assert.deepEqual(kinds('!adf:rule {localId=a-1}\nPart.\n!adf:/rule\n'), ['fault', 'paragraph'])
|
||||
assert.deepEqual(kinds('!adf:rule {localId=a-1}\n!adf:/rule\n!adf:/rule\n'), ['fault', 'fault'])
|
||||
})
|
||||
|
||||
test("releases an indented code block's held blank lines as a walk rather than as one call's arguments", () => {
|
||||
assert.deepEqual(kinds(` a\n${'\n'.repeat(200000)} b\n`), ['code'])
|
||||
})
|
||||
|
||||
@@ -365,7 +365,8 @@ function readIndentedCodeLine(leaf: Extract<OpenLeaf, { kind: 'indented-code' }>
|
||||
return true
|
||||
}
|
||||
if (leadingColumns(line) < indentedCodeColumns) return false
|
||||
leaf.lines.push(...leaf.held, removeColumns(line, indentedCodeColumns).text)
|
||||
for (const held of leaf.held) leaf.lines.push(held)
|
||||
leaf.lines.push(removeColumns(line, indentedCodeColumns).text)
|
||||
leaf.held.length = 0
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -534,6 +534,11 @@ The done `todo.md` items in full, as they were written. `todo.md` keeps a one-li
|
||||
38 ms where the tail scan alone would have left it quadratic.
|
||||
The sixth site the sweep found went to 18 rather than landing here (the maintainer,
|
||||
2026-09-18).
|
||||
**Widened** (the systems-architect, 2026-09-18): the guard's spread was a class rather than a
|
||||
site, and two more threw out of the public API — `readIndentedCodeLine` releasing the blank
|
||||
lines an indented code block held (200k of them at 200 kB), and `emitRun` joining a mark
|
||||
run's segments (200k nodes under one mark). Both fixed here with the same loop and a test
|
||||
each, and §11 gained the rule so the spelling cannot walk back in.
|
||||
- [x] **5a — Rename to `@larvit/adf-codec` (`0.1.0`).** Before the first publish, the name being
|
||||
the published identity: `package.json` `name` and `repository`, the Gitea repo and its
|
||||
remote, the README title, §6's published-as line, the checkout directory.
|
||||
|
||||
@@ -5,11 +5,18 @@ milestone. A done item shrinks to its title here; its full text moves to `todo-h
|
||||
|
||||
## Milestones
|
||||
|
||||
Shipping order: 3h, 3i, 3j, 5a, 5b, 5c, 5d, 5 → `0.1.0` (shipped 2026-09-05); 3k, 11, 4, 12, 13, 4b, 4c, 14, 15, 16, 10, 5g → `0.2.0`;
|
||||
4d, 5f, 18 → `0.2.1`; 6, 7 → `0.3.0`; 9, 17 → TBD; 5e last.
|
||||
The numbering is the order the work was planned in, not the order it ships. `0.2.0`'s order is settled
|
||||
(the maintainer, 2026-09-13): 11 makes the tables 4 generates from answer to Atlassian's schema, 4
|
||||
proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c change.
|
||||
Shipping order: 3h, 3i, 3j, 5a, 5b, 5c, 5d, 5 → `0.1.0` (shipped 2026-09-05); 3k, 11, 4, 12, 13, 4b,
|
||||
4c, 14, 15, 16, 18, 4d, 17, 10, 6, 7, 5f, 5g → `0.2.0`; 8, 9 → TBD; 5e last.
|
||||
The numbering is the order the work was planned in, not the order it ships. Everything known and
|
||||
shaped ships in one release rather than a string of them: nothing waits on a version, and no
|
||||
consumer is served by the churn (the maintainer, 2026-09-18). So `0.2.0` completes §1's three
|
||||
formats, and `0.2.1` and `0.3.0` are gone. `8` and `9` stay out as the two goals nothing has shaped
|
||||
yet. `0.2.0`'s order is settled (the maintainer, 2026-09-13, extended 2026-09-18): 11 makes the
|
||||
tables 4 generates from answer to Atlassian's schema, 4 proves 12, 13 spells 11's gaps in 12's
|
||||
grammar, and 12 rewrites code 4b and 4c change; then 14 moves the files 15, 16 and 10 edit and HTML
|
||||
is written against that layout, 4d marks the gate legs before 17 adds one, 17 puts the complexity
|
||||
guardrail under the largest body of new code, and 5f and 5g read last because 7 is what changes the
|
||||
bundle size and the tagline.
|
||||
|
||||
- [x] **0 — Scaffold.**
|
||||
- [x] **1a — The directive grammar.**
|
||||
@@ -50,7 +57,7 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c
|
||||
- [x] **4.4 — The real payloads.**
|
||||
- [x] **4b — The block walk's retry (`0.2.0`).**
|
||||
- [x] **4c — The scanning rule's remaining sites (`0.2.0`).**
|
||||
- [ ] **4d — What the gate says while it runs (`0.2.1`).** `ci.sh` runs nine legs and announces
|
||||
- [ ] **4d — What the gate says while it runs (`0.2.0`).** `ci.sh` runs nine legs and announces
|
||||
none of them, so five minutes of a Gitea run read as silence and a hang cannot be told from
|
||||
a slow pull — the maintainer hit exactly this on the `0.1.0` release. Three causes, each its
|
||||
own fix. The legs need markers: `plainpages`' `ci.sh` prints a `step()` header per leg and
|
||||
@@ -77,7 +84,7 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c
|
||||
and is the trade to weigh rather than discover on a red release run.
|
||||
**Settled** (the maintainer, 2026-09-13): last of the known work, clear of `0.2.0`, placed
|
||||
there knowing the cutoff may land before `0.2.0` ships.
|
||||
- [ ] **5f — Publish the bundle size (`0.2.1`).** Measure the shipped artifact and put the number in the
|
||||
- [ ] **5f — Publish the bundle size (`0.2.0`).** Measure the shipped artifact and put the number in the
|
||||
README, kept honest by the release pipeline rather than by a human re-reading it. The
|
||||
quantity is what a consumer downloads and loads: the tarball `npm pack` produces, its
|
||||
unpacked `dist`, and the built JavaScript minified + gzipped — the figure the competitors
|
||||
@@ -95,10 +102,10 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c
|
||||
"why" note left. The top follows the package-README order: an npm version badge and the Gitea
|
||||
Actions badge, a tagline that is also `package.json`'s `description`, a feature list and a
|
||||
one-line table of contents, then install and the shortest runnable example; a table of
|
||||
everything exported sits near the bottom. The HTML directions are one aside line under the API
|
||||
until `0.3.0` ships them, the `// 0.3.0` signatures and the `0.3.0` guarantee going until then.
|
||||
The tagline and `description` read "Lossless conversion between Atlassian Document Format and
|
||||
extended markdown" until 7 restores HTML.
|
||||
everything exported sits near the bottom. The HTML directions were to stay an aside until a
|
||||
later release shipped them; 7 now ships in this one and reads ahead of this item, so the
|
||||
README documents HTML as it documents markdown, the tagline and `description` naming both
|
||||
(the maintainer, 2026-09-13, revised 2026-09-18).
|
||||
- [x] **5a — Rename to `@larvit/adf-codec`.**
|
||||
- [x] **5b — The consumer's error surface.**
|
||||
- [x] **5b1 — The error's source position.**
|
||||
@@ -107,11 +114,11 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c
|
||||
- [x] **5b4 — The README's consumer surface.**
|
||||
- [x] **5c — The build and the release pipeline.**
|
||||
- [x] **5d — The browser leg.**
|
||||
- [ ] **6 — The HTML dialect spec (`0.3.0`).** Element-by-element mapping, the `data-*` fidelity
|
||||
- [ ] **6 — The HTML dialect spec (`0.2.0`).** Element-by-element mapping, the `data-*` fidelity
|
||||
scheme, the opaque-carry form, and the documented foreign-element set `htmlToAdf` accepts.
|
||||
- [ ] **7 — HTML, ship `0.3.0`.** `adfToHtml`, `htmlToAdf`, the composed `markdownToHtml` /
|
||||
`htmlToMarkdown`. CommonMark spec suite runs against `markdownToHtml` from here (§10). The
|
||||
README's tagline and `package.json`'s `description` regain HTML (5g).
|
||||
- [ ] **7 — HTML, the third format (`0.2.0`).** `adfToHtml`, `htmlToAdf`, the composed
|
||||
`markdownToHtml` / `htmlToMarkdown`. CommonMark spec suite runs against `markdownToHtml` from
|
||||
here (§10). The README's tagline and `package.json`'s `description` regain HTML (5g).
|
||||
- [ ] **8 — CLI.** A later goal, shaped around the personas once the library exists.
|
||||
- [ ] **9 — The online sandbox.** A web page with two textboxes converting back and forth between ADF and markdown, powered by the library's browser build.
|
||||
- [ ] **10 — Lossy conversion (`0.2.0`).** Markdown other tools render readably, to and from ADF,
|
||||
@@ -206,11 +213,15 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c
|
||||
brackets stay literal text, CommonMark's rule that no link holds another — rather than
|
||||
dropping the outer link silently as `closeLink`'s `applyMark` does today, with a normalization
|
||||
fixture per shape (the stability-reviewer, 2026-09-16; the maintainer, 2026-09-17).
|
||||
- [ ] **17 — A machine-enforced size guardrail.** Add a per-function complexity check to the gate —
|
||||
branch count or size — so the fits-in-your-head guardrail fails the build rather than
|
||||
waiting for a review to catch it (the systems-architect, 2026-09-16); placed after `0.3.0`
|
||||
(the maintainer, 2026-09-17).
|
||||
- [ ] **18 — The subtree the directive spelling asks about (`0.2.1`).** The parser asks
|
||||
- [ ] **17 — A machine-enforced size guardrail (`0.2.0`).** Add a per-function complexity check to
|
||||
the gate — branch count or size — so the fits-in-your-head guardrail fails the build rather
|
||||
than waiting for a review to catch it (the systems-architect, 2026-09-16). It reads ahead of
|
||||
6, 7 and 10 so the largest body of new code is written under it, which is also what decides
|
||||
the threshold: today's worst is `readDirectiveContent`, 27 lines and about 12 decision points
|
||||
over four concerns in one loop — escape, code span, nested directive, bracket balance — which
|
||||
4c left half-split and this item either passes or forces apart (the systems-architect and the
|
||||
maintainer, 2026-09-18).
|
||||
- [ ] **18 — The subtree the directive spelling asks about (`0.2.0`).** The parser asks
|
||||
`commonMarkSpelling` at every directive-spelled block and the answer emits the whole subtree
|
||||
below, so a node at depth d is spelled d times: three nested rule-first directive lists cost
|
||||
18 asks over 10 nodes, and 250 levels parse in 1.2 s at 16.4 kB, 4.9 s at 261 kB with a
|
||||
|
||||
Reference in New Issue
Block a user