mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 19:58:28 +01:00
Answer prose round 1: 0 🔴, 7 🟡; state the seam's comments and the decision exactly, and reflow
This commit is contained in:
+13
-14
@@ -911,9 +911,8 @@ is the markdown flavour's choice, not ADF's.
|
|||||||
## The source parts by ADF and format
|
## The source parts by ADF and format
|
||||||
|
|
||||||
2026-08-27, placement 2026-09-18, `adf/`'s bar 2026-09-21, `plain/` 2026-10-03, the import
|
2026-08-27, placement 2026-09-18, `adf/`'s bar 2026-09-21, `plain/` 2026-10-03, the import
|
||||||
boundaries' lint 2026-10-22, the maintainer; the portable flavour's own directory, item 62,
|
boundaries' lint 2026-10-22, the maintainer; `parse/` and `emit/` free of `portable/`,
|
||||||
2026-10-22. Goal 2. Valid while each format has a reader and a
|
2026-10-22. Goal 2. Valid while each format has a reader and a writer through ADF.
|
||||||
writer through ADF.
|
|
||||||
|
|
||||||
`src/adf/` holds ADF's own knowledge, imports no format, and is where a construct both formats read
|
`src/adf/` holds ADF's own knowledge, imports no format, and is where a construct both formats read
|
||||||
lives: the question is answered in ADF's vocabulary — a node type, an attribute kind, a content
|
lives: the question is answered in ADF's vocabulary — a node type, an attribute kind, a content
|
||||||
@@ -926,14 +925,14 @@ them. A primitive knowing neither ADF nor a format stays at `src/` root. A const
|
|||||||
`spec/flavour.md` draws, and a placement nothing here settles goes beside its only reader, or in
|
`spec/flavour.md` draws, and a placement nothing here settles goes beside its only reader, or in
|
||||||
what both read where there are two.
|
what both read where there are two.
|
||||||
|
|
||||||
A flavour's own code, both directions included, sits in its own directory: `markdown/portable/`.
|
A flavour's own code, both directions included, sits in its own directory: `markdown/portable/`. The
|
||||||
It hands `parse/` and `emit/` the blocks it reads and writes its own way, so neither imports it.
|
portable reader and writer hand `parse/` and `emit/` their own block readers and writers
|
||||||
Syntax that `parse/` and `emit/` read for a claim in `flavourClaims`, such as `==`, sits in
|
(`FlavourBlockReaders`, `FlavourBlockWriters`), so neither imports `portable/`. Syntax that `parse/`
|
||||||
`markdown/` itself. The directive grammar, both
|
and `emit/` read under a `flavourClaims` claim, such as `==`, sits outside `portable/`. The
|
||||||
directions included, sits in `markdown/directive/`, which imports neither `emit/` nor `parse/`.
|
directive grammar, both directions included, sits in `markdown/directive/`, which imports neither
|
||||||
Otherwise each format directory parts into `emit/` (ADF→format) and
|
`emit/` nor `parse/`. Otherwise each format directory parts into `emit/` (ADF→format) and `parse/`
|
||||||
`parse/` (format→ADF), the rest of it holding what both directions read. A construct's reader lives
|
(format→ADF), the rest of it holding what both directions read. A construct's reader lives there
|
||||||
there beside the regex the emitter escapes against, so the two cannot drift; a reader with no emit
|
beside the regex the emitter escapes against, so the two cannot drift; a reader with no emit
|
||||||
counterpart goes in `parse/`, unless it is part of a construct that side already holds — a grammar
|
counterpart goes in `parse/`, unless it is part of a construct that side already holds — a grammar
|
||||||
stays in one file rather than splitting across the seam. A rule both directions must answer alike —
|
stays in one file rather than splitting across the seam. A rule both directions must answer alike —
|
||||||
whether a list marker interrupts a paragraph — is one function there too, never a copy per
|
whether a list marker interrupts a paragraph — is one function there too, never a copy per
|
||||||
@@ -944,6 +943,6 @@ is the only import `parse/` takes from `emit/`, through `emit/spelling-questions
|
|||||||
|
|
||||||
`.oxlintrc.json` enforces that limit for `markdown/parse/`, keeps `parse/` and `emit/` from
|
`.oxlintrc.json` enforces that limit for `markdown/parse/`, keeps `parse/` and `emit/` from
|
||||||
importing `markdown/portable/`, and keeps `markdown/directive/` from importing `emit/` or `parse/`,
|
importing `markdown/portable/`, and keeps `markdown/directive/` from importing `emit/` or `parse/`,
|
||||||
as `no-restricted-imports` overrides. They are lint rules because the
|
as `no-restricted-imports` overrides. They are lint rules because the technical principles refuse a
|
||||||
technical principles refuse a test that scans the source, and a lint rule reads every import
|
test that scans the source, and a lint rule reads every import spelling: `from`, a bare `import`,
|
||||||
spelling: `from`, a bare `import`, double quotes and a dynamic `import()`.
|
double quotes and a dynamic `import()`.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type BlockContainer = 'directive' | 'document' | 'list-item'
|
|||||||
type Delimiters = readonly [string, string]
|
type Delimiters = readonly [string, string]
|
||||||
// headroom: the nesting levels the deepest node below may still spend before `largestNesting` refuses it.
|
// headroom: the nesting levels the deepest node below may still spend before `largestNesting` refuses it.
|
||||||
export type EmittedBlock = SpelledBlock & { headroom: number }
|
export type EmittedBlock = SpelledBlock & { headroom: number }
|
||||||
// A flavour's own blocks, spelled before CommonMark's, and the openings its reader claims, which a list item or quote escapes.
|
// spell: the flavour's own blocks, tried before CommonMark's; claimsItem, claimsQuote: whether a list item's or quote's text opens with a marker the flavour's reader would take, so the writer escapes it.
|
||||||
export type FlavourBlockWriters = {
|
export type FlavourBlockWriters = {
|
||||||
claimsItem: (text: string) => boolean
|
claimsItem: (text: string) => boolean
|
||||||
claimsQuote: (text: string) => boolean
|
claimsQuote: (text: string) => boolean
|
||||||
@@ -202,7 +202,7 @@ function alertEscaped(body: string, writing: Writing): string {
|
|||||||
return writing.flavour === 'portable' && writing.blocks.claimsQuote(body) ? `\\${body}` : body
|
return writing.flavour === 'portable' && writing.blocks.claimsQuote(body) ? `\\${body}` : body
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each quote speller calls it directly, not through a wrapper, so a quote level spends one frame between walks (docs/decisions.md §Nothing recurses unbounded).
|
// Quote spellers call this directly, never through a wrapper: each frame a quote level adds lowers the depth the stack holds (docs/decisions.md §Nothing recurses unbounded).
|
||||||
export function quotedBlocks(node: AdfNode, path: ConvertErrorPath, depth: number, writing: Writing, bodyText: (body: string) => string): Result<EmittedBlock> {
|
export function quotedBlocks(node: AdfNode, path: ConvertErrorPath, depth: number, writing: Writing, bodyText: (body: string) => string): Result<EmittedBlock> {
|
||||||
const inner = walkBlocks(nodeContent(node), path, depth + 1, writing)
|
const inner = walkBlocks(nodeContent(node), path, depth + 1, writing)
|
||||||
if (!inner.ok) return inner
|
if (!inner.ok) return inner
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export type Flavour = 'commonmark' | 'lossless' | 'portable'
|
|||||||
|
|
||||||
export type WrittenFlavour = Exclude<Flavour, 'commonmark'>
|
export type WrittenFlavour = Exclude<Flavour, 'commonmark'>
|
||||||
|
|
||||||
// What a reader reads past CommonMark, and so what its writer escapes, but for the portable callouts and task lists that `FlavourBlockReaders` and `FlavourBlockWriters` carry (docs/decisions.md §Each call names its flavour).
|
// What a reader reads past CommonMark, and so what its writer escapes; the portable flavour's alerts, callouts and task markers are left to `FlavourBlockReaders` and `FlavourBlockWriters` (docs/decisions.md §Each call names its flavour).
|
||||||
export type Claims = {
|
export type Claims = {
|
||||||
carryFence: boolean
|
carryFence: boolean
|
||||||
directives: boolean
|
directives: boolean
|
||||||
@@ -17,7 +17,7 @@ type WrittenClaims = Claims & { pipeTables: 'flavour' | 'gfm'; strikethrough: tr
|
|||||||
// Directives exclude highlights: a directive's slot reads `==`.
|
// Directives exclude highlights: a directive's slot reads `==`.
|
||||||
type ReadClaims = Claims & ({ directives: false } | { directives: true; highlights: false })
|
type ReadClaims = Claims & ({ directives: false } | { directives: true; highlights: false })
|
||||||
|
|
||||||
// A folded callout inside an expand directive would read as an expand, not a nestedExpand, so the portable flavour reads no directives.
|
// Pinned false: inside an expand directive, a folded callout would read as an expand, not a nestedExpand.
|
||||||
export const flavourClaims: Readonly<{ commonmark: ReadClaims; portable: { directives: false } } & Record<WrittenFlavour, ReadClaims & WrittenClaims>> = {
|
export const flavourClaims: Readonly<{ commonmark: ReadClaims; portable: { directives: false } } & Record<WrittenFlavour, ReadClaims & WrittenClaims>> = {
|
||||||
commonmark: { carryFence: false, directives: false, highlights: false, pipeTables: false, strikethrough: false },
|
commonmark: { carryFence: false, directives: false, highlights: false, pipeTables: false, strikethrough: false },
|
||||||
lossless: { carryFence: true, directives: true, highlights: false, pipeTables: 'flavour', strikethrough: true },
|
lossless: { carryFence: true, directives: true, highlights: false, pipeTables: 'flavour', strikethrough: true },
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { spellStringAttribute, unsupportedNodeShape } from '../directive/syntax.
|
|||||||
import { spellsEmpty } from '../directive/empty-keys.ts'
|
import { spellsEmpty } from '../directive/empty-keys.ts'
|
||||||
import { withinEngineLimits } from '../length-bound.ts'
|
import { withinEngineLimits } from '../length-bound.ts'
|
||||||
|
|
||||||
// A flavour's own reading of a quote and of a bullet list, which may read to several blocks; `path` is the container's, `index` the list's among its blocks.
|
// A flavour's own reading of a quote, and of a bullet list into one or more blocks; `path` is the container's, `index` the list's place among the container's blocks.
|
||||||
export type FlavourBlockReaders = {
|
export type FlavourBlockReaders = {
|
||||||
bulletList: (items: readonly Block[][], reading: FlavourReading, path: ConvertErrorPath, depth: number, index: number) => Result<AdfNode[]>
|
bulletList: (items: readonly Block[][], reading: FlavourReading, path: ConvertErrorPath, depth: number, index: number) => Result<AdfNode[]>
|
||||||
quote: (blocks: readonly Block[], reading: FlavourReading, path: ConvertErrorPath, depth: number) => Result<AdfNode>
|
quote: (blocks: readonly Block[], reading: FlavourReading, path: ConvertErrorPath, depth: number) => Result<AdfNode>
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ type Paragraph = Extract<Block, { kind: 'paragraph' }>
|
|||||||
// The paragraph a task marker opens: a task item holds no image, so a lone one there reads as its linked alt text.
|
// The paragraph a task marker opens: a task item holds no image, so a lone one there reads as its linked alt text.
|
||||||
type TaskParagraph = { kind: 'taskParagraph'; position: SourcePosition; text: string }
|
type TaskParagraph = { kind: 'taskParagraph'; position: SourcePosition; text: string }
|
||||||
|
|
||||||
// Inside an expand, a folded callout reads as a nestedExpand.
|
|
||||||
const expandBlockReaders: FlavourBlockReaders = { bulletList: bulletBlocks, quote: quoteReader('nestedExpand') }
|
const expandBlockReaders: FlavourBlockReaders = { bulletList: bulletBlocks, quote: quoteReader('nestedExpand') }
|
||||||
const portableBlockReaders: FlavourBlockReaders = { bulletList: bulletBlocks, quote: quoteReader('expand') }
|
const portableBlockReaders: FlavourBlockReaders = { bulletList: bulletBlocks, quote: quoteReader('expand') }
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ function paragraphsOf(position: SourcePosition, ...texts: string[]): Paragraph[]
|
|||||||
return texts.filter((text) => text !== '').map((text) => ({ kind: 'paragraph', position, text }))
|
return texts.filter((text) => text !== '').map((text) => ({ kind: 'paragraph', position, text }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// The closure is the reader rather than a wrapper around one, so a quote level spends one stack frame (docs/decisions.md §Nothing recurses unbounded).
|
// The closure is the reader itself, never a wrapper calling one: a wrapper adds a stack frame to every quote level (docs/decisions.md §Nothing recurses unbounded).
|
||||||
function quoteReader(foldedType: 'expand' | 'nestedExpand'): FlavourBlockReaders['quote'] {
|
function quoteReader(foldedType: 'expand' | 'nestedExpand'): FlavourBlockReaders['quote'] {
|
||||||
return (blocks, reading, path, depth) => {
|
return (blocks, reading, path, depth) => {
|
||||||
const [first, ...body] = blocks
|
const [first, ...body] = blocks
|
||||||
|
|||||||
@@ -142,8 +142,8 @@ wait for its publish). Found while planning discoverability, 2026-10-04.
|
|||||||
|
|
||||||
### 128. Pin the portable flavour's block reading and writing with tests: callout titles past one line, a link beside other marks in a title, the nesting limit of callouts and task lists, refusals inside tasks and their paths, and empty or non-paragraph tasks.
|
### 128. Pin the portable flavour's block reading and writing with tests: callout titles past one line, a link beside other marks in a title, the nesting limit of callouts and task lists, refusals inside tasks and their paths, and empty or non-paragraph tasks.
|
||||||
|
|
||||||
Mutation testing of item 62's move left about 40 mutants alive in code it moved verbatim, so the
|
Mutation testing left about 40 mutants alive in code moved verbatim into `src/markdown/portable/`
|
||||||
gaps predate it: `tryTaskList` and `taskBlocks` in `src/markdown/portable/block-spelling.ts`, and
|
on 2026-10-22, so the gaps predate the move: `tryTaskList` and `taskBlocks` in `src/markdown/portable/block-spelling.ts`, and
|
||||||
`markerLine`, `quoteReader`, `titleText`, `taskBlocks` and `readTaskBlock` in
|
`markerLine`, `quoteReader`, `titleText`, `taskBlocks` and `readTaskBlock` in
|
||||||
`src/markdown/portable/portable-markdown-to-adf.ts`. Each test takes its expected value from
|
`src/markdown/portable/portable-markdown-to-adf.ts`. Each test takes its expected value from
|
||||||
`spec/flavour.md` or `docs/decisions.md`, never from the code.
|
`spec/flavour.md` or `docs/decisions.md`, never from the code.
|
||||||
|
|||||||
Reference in New Issue
Block a user