markdown-codec 0.0.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +215 -0
- package/dist/ast/ast.cjs +25 -0
- package/dist/ast/ast.d.cts +2 -0
- package/dist/ast/ast.d.ts +2 -0
- package/dist/ast/ast.js +23 -0
- package/dist/ast-BXYwy08e.d.cts +160 -0
- package/dist/ast-BXYwy08e.d.ts +160 -0
- package/dist/block/block.cjs +530 -0
- package/dist/block/block.d.cts +18 -0
- package/dist/block/block.d.ts +18 -0
- package/dist/block/block.js +529 -0
- package/dist/block/definitions.cjs +59 -0
- package/dist/block/definitions.d.cts +6 -0
- package/dist/block/definitions.d.ts +6 -0
- package/dist/block/definitions.js +58 -0
- package/dist/block/line.cjs +76 -0
- package/dist/block/line.d.cts +28 -0
- package/dist/block/line.d.ts +28 -0
- package/dist/block/line.js +74 -0
- package/dist/block/list.cjs +110 -0
- package/dist/block/list.d.cts +8 -0
- package/dist/block/list.d.ts +8 -0
- package/dist/block/list.js +107 -0
- package/dist/block/node.cjs +68 -0
- package/dist/block/node.d.cts +45 -0
- package/dist/block/node.d.ts +45 -0
- package/dist/block/node.js +65 -0
- package/dist/block/table.cjs +64 -0
- package/dist/block/table.d.cts +7 -0
- package/dist/block/table.d.ts +7 -0
- package/dist/block/table.js +61 -0
- package/dist/codec.cjs +22 -0
- package/dist/codec.d.cts +361 -0
- package/dist/codec.d.ts +361 -0
- package/dist/codec.js +20 -0
- package/dist/defaults/defaults.cjs +31 -0
- package/dist/defaults/defaults.d.cts +16 -0
- package/dist/defaults/defaults.d.ts +16 -0
- package/dist/defaults/defaults.js +20 -0
- package/dist/diagnostics/diagnostics.cjs +85 -0
- package/dist/diagnostics/diagnostics.d.cts +2 -0
- package/dist/diagnostics/diagnostics.d.ts +2 -0
- package/dist/diagnostics/diagnostics.js +77 -0
- package/dist/diagnostics-DmuWc7d8.d.cts +61 -0
- package/dist/diagnostics-DmuWc7d8.d.ts +61 -0
- package/dist/emit/emit.cjs +228 -0
- package/dist/emit/emit.d.cts +6 -0
- package/dist/emit/emit.d.ts +6 -0
- package/dist/emit/emit.js +227 -0
- package/dist/emit/front-matter.cjs +24 -0
- package/dist/emit/front-matter.d.cts +5 -0
- package/dist/emit/front-matter.d.ts +5 -0
- package/dist/emit/front-matter.js +23 -0
- package/dist/emit/image.cjs +10 -0
- package/dist/emit/image.d.cts +5 -0
- package/dist/emit/image.d.ts +5 -0
- package/dist/emit/image.js +9 -0
- package/dist/emit/inline.cjs +183 -0
- package/dist/emit/inline.d.cts +12 -0
- package/dist/emit/inline.d.ts +12 -0
- package/dist/emit/inline.js +180 -0
- package/dist/emit/table.cjs +73 -0
- package/dist/emit/table.d.cts +10 -0
- package/dist/emit/table.d.ts +10 -0
- package/dist/emit/table.js +72 -0
- package/dist/html/html.cjs +116 -0
- package/dist/html/html.d.cts +2 -0
- package/dist/html/html.d.ts +2 -0
- package/dist/html/html.js +113 -0
- package/dist/html/render.cjs +193 -0
- package/dist/html/render.d.cts +8 -0
- package/dist/html/render.d.ts +8 -0
- package/dist/html/render.js +189 -0
- package/dist/html-bkz2QTuq.d.cts +7 -0
- package/dist/html-bkz2QTuq.d.ts +7 -0
- package/dist/image/image.cjs +129 -0
- package/dist/image/image.d.cts +2 -0
- package/dist/image/image.d.ts +2 -0
- package/dist/image/image.js +125 -0
- package/dist/image-C4KYmz_L.d.cts +19 -0
- package/dist/image-Cm3hT5PS.d.ts +19 -0
- package/dist/image-DescdRgq.d.cts +12 -0
- package/dist/image-DescdRgq.d.ts +12 -0
- package/dist/index.cjs +17 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/inline/chars.cjs +50 -0
- package/dist/inline/chars.d.cts +11 -0
- package/dist/inline/chars.d.ts +11 -0
- package/dist/inline/chars.js +42 -0
- package/dist/inline/delimiter.cjs +136 -0
- package/dist/inline/delimiter.d.cts +29 -0
- package/dist/inline/delimiter.d.ts +29 -0
- package/dist/inline/delimiter.js +132 -0
- package/dist/inline/entity.cjs +67 -0
- package/dist/inline/entity.d.cts +9 -0
- package/dist/inline/entity.d.ts +9 -0
- package/dist/inline/entity.js +65 -0
- package/dist/inline/gfm-autolink.cjs +156 -0
- package/dist/inline/gfm-autolink.d.cts +5 -0
- package/dist/inline/gfm-autolink.d.ts +5 -0
- package/dist/inline/gfm-autolink.js +155 -0
- package/dist/inline/inline.cjs +457 -0
- package/dist/inline/inline.d.cts +2 -0
- package/dist/inline/inline.d.ts +2 -0
- package/dist/inline/inline.js +456 -0
- package/dist/inline/link.cjs +130 -0
- package/dist/inline/link.d.cts +2 -0
- package/dist/inline/link.d.ts +2 -0
- package/dist/inline/link.js +124 -0
- package/dist/inline/node.cjs +57 -0
- package/dist/inline/node.d.cts +23 -0
- package/dist/inline/node.d.ts +23 -0
- package/dist/inline/node.js +55 -0
- package/dist/inline-B50cM6cn.d.ts +10 -0
- package/dist/inline-C9YEGDV5.d.cts +10 -0
- package/dist/link-Dv4kxVjk.d.cts +18 -0
- package/dist/link-Dv4kxVjk.d.ts +18 -0
- package/dist/lower/front-matter.cjs +75 -0
- package/dist/lower/front-matter.d.cts +10 -0
- package/dist/lower/front-matter.d.ts +10 -0
- package/dist/lower/front-matter.js +74 -0
- package/dist/lower/image.cjs +29 -0
- package/dist/lower/image.d.cts +2 -0
- package/dist/lower/image.d.ts +2 -0
- package/dist/lower/image.js +28 -0
- package/dist/lower/inline.cjs +96 -0
- package/dist/lower/inline.d.cts +19 -0
- package/dist/lower/inline.d.ts +19 -0
- package/dist/lower/inline.js +93 -0
- package/dist/lower/lower.cjs +293 -0
- package/dist/lower/lower.d.cts +8 -0
- package/dist/lower/lower.d.ts +8 -0
- package/dist/lower/lower.js +291 -0
- package/dist/lower/table.cjs +27 -0
- package/dist/lower/table.d.cts +7 -0
- package/dist/lower/table.d.ts +7 -0
- package/dist/lower/table.js +26 -0
- package/dist/options/options.cjs +0 -0
- package/dist/options/options.d.cts +43 -0
- package/dist/options/options.d.ts +43 -0
- package/dist/options/options.js +0 -0
- package/dist/read.cjs +20 -0
- package/dist/read.d.cts +11 -0
- package/dist/read.d.ts +11 -0
- package/dist/read.js +19 -0
- package/dist/scan/entity-table.cjs +2131 -0
- package/dist/scan/entity-table.d.cts +4 -0
- package/dist/scan/entity-table.d.ts +4 -0
- package/dist/scan/entity-table.js +2130 -0
- package/dist/scan/scan.cjs +80 -0
- package/dist/scan/scan.d.cts +2 -0
- package/dist/scan/scan.d.ts +2 -0
- package/dist/scan/scan.js +78 -0
- package/dist/scan-DeBxgG-r.d.cts +30 -0
- package/dist/scan-DeBxgG-r.d.ts +30 -0
- package/dist/shared/list-id.cjs +36 -0
- package/dist/shared/list-id.d.cts +22 -0
- package/dist/shared/list-id.d.ts +22 -0
- package/dist/shared/list-id.js +32 -0
- package/dist/shared/style-constants.cjs +34 -0
- package/dist/shared/style-constants.d.cts +14 -0
- package/dist/shared/style-constants.d.ts +14 -0
- package/dist/shared/style-constants.js +23 -0
- package/dist/write.cjs +9 -0
- package/dist/write.d.cts +6 -0
- package/dist/write.d.ts +6 -0
- package/dist/write.js +8 -0
- package/package.json +92 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joseph Mearman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# markdown-codec
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ExaDev/markdown-codec) [](https://www.npmjs.com/package/markdown-codec) [](https://github.com/ExaDev/markdown-codec/releases/latest) [](https://github.com/ExaDev/markdown-codec/actions)
|
|
4
|
+
|
|
5
|
+
> Hand-written CommonMark+GFM ⇄ `ContentDocument` codec, built on [document-schema.js](https://github.com/ExaDev/document-schema.js).
|
|
6
|
+
|
|
7
|
+
`markdown-codec` is a sibling of [`pdf-codec`](https://github.com/ExaDev/pdf-codec): the same "hand-write the format instead of wrapping a third-party library" bet, aimed at CommonMark and its GitHub Flavored Markdown (GFM) extensions rather than PDF. No `micromark`/`remark`/`marked`/`markdown-it`/`commonmark`/`mdast`/`unified`/`turndown`/`showdown` dependency anywhere in this package — see `eslint.config.ts`'s `no-restricted-imports` rule, which bans importing any of them by name, matching this family's own zero-supply-chain-surface ethos: the only runtime dependencies are `document-schema.js` (the shared pivot) and `zod` (schema validation). `readMarkdown`/`writeMarkdown` read and write `document-schema.js`'s shared `ContentDocument` directly, the same pivot [`documents.js`](https://github.com/ExaDev/documents.js) already builds docx/pptx/odt/odp conversions around, so a caller can bridge markdown to any other format that pivot already supports without this package knowing anything about docx, PDF, or ODF.
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
graph TD
|
|
11
|
+
schema("document-schema.js")
|
|
12
|
+
ooxml("ooxml.js")
|
|
13
|
+
odf("odf.js")
|
|
14
|
+
pdfcodec("pdf-codec")
|
|
15
|
+
mdcodec("markdown-codec")
|
|
16
|
+
documents("documents.js")
|
|
17
|
+
cli("document-cli")
|
|
18
|
+
|
|
19
|
+
schema --> ooxml
|
|
20
|
+
schema --> odf
|
|
21
|
+
schema --> pdfcodec
|
|
22
|
+
schema --> mdcodec
|
|
23
|
+
schema --> documents
|
|
24
|
+
ooxml --> documents
|
|
25
|
+
odf --> documents
|
|
26
|
+
pdfcodec --> documents
|
|
27
|
+
mdcodec --> documents
|
|
28
|
+
documents --> cli
|
|
29
|
+
odf --> cli
|
|
30
|
+
|
|
31
|
+
click schema "https://github.com/ExaDev/document-schema.js" "document-schema.js"
|
|
32
|
+
click ooxml "https://github.com/ExaDev/ooxml.js" "ooxml.js"
|
|
33
|
+
click odf "https://github.com/ExaDev/odf.js" "odf.js"
|
|
34
|
+
click pdfcodec "https://github.com/ExaDev/pdf-codec" "pdf-codec"
|
|
35
|
+
click mdcodec "https://github.com/ExaDev/markdown-codec" "markdown-codec"
|
|
36
|
+
click documents "https://github.com/ExaDev/documents.js" "documents.js"
|
|
37
|
+
click cli "https://github.com/ExaDev/document-cli" "document-cli"
|
|
38
|
+
|
|
39
|
+
style mdcodec fill:#f9a825,stroke:#333,stroke-width:3px
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Status
|
|
43
|
+
|
|
44
|
+
The scanner, block parser (`src/block/`), and inline parser (`src/inline/`) are complete hand-written implementations of CommonMark 0.31.2's own two-phase parsing algorithm, plus GFM's table/strikethrough/autolink/task-list-item extensions. `readMarkdown`/`writeMarkdown`/`markdownCodec` (`src/read.ts`/`src/write.ts`/`src/codec.ts`) are wired and real — front matter extraction, block/inline parsing, and lowering to `ContentDocument` compose in one call through `src/lower/lower.ts`'s `lowerMarkdown`; `src/emit/emit.ts`'s `emitMarkdown` is the structural inverse. Tooling (build, lint, typecheck, CI, release) is fully wired.
|
|
45
|
+
|
|
46
|
+
The conformance suites (`src/conformance.test.ts`, `src/gfm-conformance.test.ts`) measure the real public surface end to end — `readMarkdown` → `writeMarkdown` → reparse → render to HTML — against the vendored CommonMark and GFM spec corpora, and are a materially stricter bar than measuring the bare parser alone: a round trip through `ContentDocument` has to survive `src/lower`'s own semantic mapping *and* `src/emit`'s own inverse rendering with no loss the reparse can detect. See [Fidelity](#fidelity) for what that measures and why the number is lower than 100%: it is dominated by what `ContentDocument` itself can represent, not by parsing gaps.
|
|
47
|
+
|
|
48
|
+
## Getting started
|
|
49
|
+
|
|
50
|
+
Requires Node.js `>=20` and pnpm `11.6.0` (pinned via `packageManager` in `package.json`).
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
pnpm install
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Install as a dependency in another project:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
pnpm add markdown-codec
|
|
60
|
+
# or
|
|
61
|
+
npm install markdown-codec
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This package is not yet published to npm, pending its own npm trusted-publisher configuration (the `Release` CI job runs and fails at the "no npm token" verification step until that is set up — every other job, including `Test`/`Smoke test`/`Lint`/`Typecheck`, is green). Until then, [`documents.js`](https://github.com/ExaDev/documents.js) consumes this package via a pinned git commit (`markdown-codec@github:ExaDev/markdown-codec#<commit>`) instead of a semver range, and `dist/` is deliberately committed rather than gitignored, so a git-tarball install has a working build with no install-time compile step: pnpm's own git-dependency preparation sandbox proved unreliable at running a `tsdown` build reliably in CI (two independent, environment-specific failures surfaced while chasing this — a Node.js ESM-loader bug in tsdown's default config loader, then dts generation silently producing zero output under the same sandbox even after routing around the first bug), so shipping the build output directly sidesteps the whole class of problem rather than chasing a third variant of it. Both `dist/` and this note should be removed once a real npm release makes them unnecessary.
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
Reading and writing markdown text:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { readMarkdown, writeMarkdown } from 'markdown-codec';
|
|
72
|
+
|
|
73
|
+
const { document, diagnostics } = readMarkdown('# Title\n\nSome **bold** text with a [link](https://example.com).', {
|
|
74
|
+
frontMatter: true, // parse a leading YAML front matter block into ContentDocument.metadata
|
|
75
|
+
images: (destination) => undefined, // a synchronous MarkdownImageResolver port for non-data: URI images
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const markdown = writeMarkdown(document, {
|
|
79
|
+
bulletListMarker: '-',
|
|
80
|
+
emphasisMarker: '_',
|
|
81
|
+
frontMatter: true, // emit ContentDocument.metadata back out as a leading front matter block
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Both accept an optional `signal` (`AbortSignal`) and `sink` (a `MarkdownDiagnosticSink`, called once per construct either side cannot represent losslessly — see [Gotchas](#gotchas-and-quirks) for the full list, one entry per named `MarkdownDiagnosticCodes` code). `writeMarkdown` throws `MarkdownUnsupportedDocumentKindError` for a non-`'wordprocessing'` `ContentDocument` — markdown has no presentation/spreadsheet/drawing equivalent to render.
|
|
86
|
+
|
|
87
|
+
The same round trip is also available as a schema-validated [`z.codec()`](https://zod.dev) pair, mirroring `pdf-codec`'s own `pdfCodec` convention:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { z } from 'zod';
|
|
91
|
+
import { markdownCodec, MarkdownBytesSchema } from 'markdown-codec';
|
|
92
|
+
|
|
93
|
+
const document = z.decode(markdownCodec, bytes); // throws if bytes are not well-formed UTF-8
|
|
94
|
+
const bytes2 = z.encode(markdownCodec, document);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`MarkdownBytesSchema` checks for well-formed UTF-8 — the one thing genuinely worth validating about arbitrary markdown bytes, since markdown has no magic-byte header of its own and CommonMark's grammar has no "this is not markdown" rejection path (worst case, an unparseable line becomes an ordinary paragraph). This is the no-extra-options form only — `readMarkdown`/`writeMarkdown` remain the entry points wherever a caller needs an `AbortSignal` or a diagnostic sink, since `z.codec()`'s fixed `decode(input)`/`encode(output)` signature has no room for side-channel options.
|
|
98
|
+
|
|
99
|
+
Every construct-mapping gap either side cannot represent losslessly reports through the sink as a stable, namespaced code (e.g. `md/nested-emphasis-flattened`) — see `MarkdownDiagnosticCodes` (`src/diagnostics/diagnostics.ts`) and [Gotchas](#gotchas-and-quirks) below for the full, named list.
|
|
100
|
+
|
|
101
|
+
## Architecture
|
|
102
|
+
|
|
103
|
+
Modelled on `pdf-codec`'s own layering (generic primitives outward to the two conversion directions), aimed at CommonMark+GFM instead of PDF:
|
|
104
|
+
|
|
105
|
+
- **`src/diagnostics/`** — the read-side diagnostic sink, matching `pdf-codec`'s own three-tier `PdfDiagnosticSink` policy: throw (`MarkdownParseError` and its subclasses — invalid UTF-8, input-too-large, nesting-limit-exceeded) for input this package cannot meaningfully process at all; recover-with-diagnostic for markdown that is spec-legal but almost certainly a typo (an unclosed fence, a table cell-count mismatch, a duplicate link reference, a list marker-type conflict); degrade-with-diagnostic for an individual construct `src/lower`'s or `src/emit`'s own `ContentDocument` mapping cannot represent, while the rest of the document still reads. `MarkdownDiagnosticCodes` names every code either tier can produce; `src/diagnostics/diagnostics.test.ts` asserts the whole table is reachable from real input.
|
|
106
|
+
- **`src/ast/`** — this package's own markdown AST node types (document/block/inline discriminated union), Zod-first like every other model in this family: every node type inferred from its schema, never hand-written.
|
|
107
|
+
- **`src/options/`** / **`src/defaults/`** — `readMarkdown`/`writeMarkdown`'s own options (GFM extension toggles, a diagnostic sink, an `AbortSignal`, `writeMarkdown`'s own style choices — heading/bullet/ordered-delimiter/emphasis/code-fence/thematic-break characters, line ending, front matter emission) and their default values.
|
|
108
|
+
- **`src/scan/`** — the hand-written CommonMark line/character scanner feeding block parsing, plus `entity-table.ts` (auto-generated by `scripts/generate-entity-table.mjs` from `assets/html-entities/entities.json`, committed to the repository so this package never needs a filesystem read of the vendored asset at runtime).
|
|
109
|
+
- **`src/block/`** — CommonMark's block-structure algorithm (open-block stack, continuation-line matching): paragraphs, headings, code blocks, block quotes, lists (including GFM task-list-item markers), thematic breaks, link reference definitions, GFM tables.
|
|
110
|
+
- **`src/inline/`** — inline-level parsing within a block's own content: emphasis, code spans, links, autolinks, raw inline HTML, GFM strikethrough, line breaks.
|
|
111
|
+
- **`src/html/`** — raw block/inline HTML recognition (CommonMark's own bounded seven-condition block-HTML rules and inline tag syntax — not a general HTML parser) plus `render.ts`, the real CommonMark-HTML conformance oracle `src/conformance.test.ts`/`src/gfm-conformance.test.ts` render parsed documents through — internal plumbing, never re-exported from `src/index.ts`.
|
|
112
|
+
- **`src/image/`** — a hand-written PNG/JPEG dimension reader plus an isomorphic base64 codec, shared by `src/lower/image.ts`'s data: URI decoding and `src/emit/image.ts`'s re-encoding.
|
|
113
|
+
- **`src/shared/`** — string-shape conventions `src/lower` (mint/read) and `src/emit` (read/write) must agree on exactly: `style-constants.ts` (heading/quote/code-block/rule/HTML-preformatted styleIds, the monospace font family, the blockquote per-level indent unit, the GFM task-checkbox glyph pair) and `list-id.ts` (the opaque `numId` grammar a list's own type/task/tightness is packed into, since `ContentListMembership` itself carries only `{numId, level}`).
|
|
114
|
+
- **`src/lower/`** — the AST → `ContentDocument` lowering stage: the markdown-side counterpart to `ooxml.js`'s `readDocx`/`readPptx` and `odf.js`'s `readOdt`/`readOdp` — a thin adapter from a format-specific parse result onto the shared pivot, not a second parser. `lower.ts`'s own top-of-file table maps every construct (headings, emphasis/links/breaks via `inline.ts`, code blocks, blockquotes, lists via `src/shared/list-id.ts`, GFM tables via `table.ts`, images via `image.ts`'s `MarkdownImageResolver` port, raw HTML, front matter via `front-matter.ts`) onto its own `MarkdownDiagnosticCodes` gap.
|
|
115
|
+
- **`src/emit/`** — the `ContentDocument` → markdown text emission stage (`writeMarkdown`'s build-side half), the structural inverse of `src/lower/` construct for construct — `emit.ts`'s own top-of-file table mirrors `lower.ts`'s.
|
|
116
|
+
- **`src/read.ts`** / **`src/write.ts`** / **`src/codec.ts`** — the public `readMarkdown`/`writeMarkdown` entry points and their `z.codec()` pair (`markdownCodec`), matching `pdf-codec`'s own `pdfCodec` convention. `readMarkdown` operates on `document-schema.js`'s full `ContentDocument` envelope directly (`kind`/`formatVersion`/`metadata`/`sections`), not a bare `{metadata, sections}` shape a caller would need to wrap — see `src/read.ts`'s own top-of-file comment for the recorded reconciliation decision, reasoned from `ooxml.js`'s `readXlsxContent`/`buildXlsxPackage` precedent (the more recent design choice in this family, and the structurally closer fit: markdown has no PDF-pivot layout stage of its own, the same position xlsx⇄ods's bridge is in).
|
|
117
|
+
|
|
118
|
+
## Vendored assets
|
|
119
|
+
|
|
120
|
+
`assets/` holds real, unmodified conformance corpora fetched directly from their canonical sources, each with its own `NOTICE.md` recording the exact source URL, commit/version, and confirmed licence. None of this is read at runtime by the shipped package — `assets/html-entities/entities.json` is compiled once into `src/scan/entity-table.ts` (a committed, generated source file) by `scripts/generate-entity-table.mjs`, and the two spec corpora are consumed only by the test suite (`src/test-support/spec-corpus.ts`) — so `package.json`'s `"files": ["dist"]` is correct as is; there is nothing under `assets/` a consumer of the published package ever needs.
|
|
121
|
+
|
|
122
|
+
- **`assets/commonmark/`** — the official CommonMark spec (`spec.txt`) and its machine-readable conformance test corpus (`spec.json`, 652 examples), from the `commonmark/commonmark-spec` project, tag `0.31.2` (CC-BY-SA 4.0).
|
|
123
|
+
- **`assets/gfm/`** — the official GitHub Flavored Markdown Spec (`spec.txt`), from `github/cmark-gfm` (CC-BY-SA 4.0).
|
|
124
|
+
- **`assets/html-entities/`** — the WHATWG HTML5 named character reference table (`entities.json`), from the WHATWG HTML Standard (BSD 3-Clause, per the WHATWG's own "incorporated into source code" licence clause).
|
|
125
|
+
|
|
126
|
+
## Build, test, and lint
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
pnpm build # tsdown -> dist/ (ESM + CJS + .d.ts)
|
|
130
|
+
pnpm typecheck # tsc --noEmit
|
|
131
|
+
pnpm lint # eslint . --max-warnings 0
|
|
132
|
+
pnpm test # vitest run --project unit (includes the CommonMark/GFM conformance suites)
|
|
133
|
+
pnpm test:watch # vitest --project unit
|
|
134
|
+
pnpm test:coverage # vitest run --project unit --coverage
|
|
135
|
+
pnpm test:smoke # rebuilds dist/, then verifies ESM/CJS parity and a real readMarkdown/writeMarkdown round trip from the built CJS bundle
|
|
136
|
+
pnpm test:corpus # optional, gitignored real-world CommonMark/GFM sanity check -- see Fidelity below
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
140
|
+
|
|
141
|
+
## Conventions
|
|
142
|
+
|
|
143
|
+
- **Zod-first schema/type/guard**, matching `pdf-codec`/`documents.js`: every model type is inferred from its Zod schema, never hand-written.
|
|
144
|
+
- **No type assertions anywhere.** Every loosely-typed value is narrowed through a type guard or a Zod parse at the boundary.
|
|
145
|
+
- **No markdown-parsing library dependency**, enforced by an eslint `no-restricted-imports` rule naming every mainstream alternative — this package hand-writes its own scanner, block parser, and inline parser against the CommonMark/GFM specs directly.
|
|
146
|
+
- **`z.codec()` for the one schema-to-schema round trip this package owns**, matching `pdf-codec`'s `pdfCodec`/`documents.js`'s `docxPdfCodec` convention: `markdownCodec` wraps the already-independently-tested `readMarkdown`/`writeMarkdown` pair, adding automatic two-way schema validation, deliberately in the no-options form.
|
|
147
|
+
- **A shrink-only conformance exclusion list.** Any spec example this package's real read → write → reparse → render pipeline does not yet reproduce byte for byte is named individually in `src/test-support/conformance-exclusions.ts`, with its own test asserting every named example genuinely still fails — the list can shrink as gaps close but can never quietly grow to hide a regression.
|
|
148
|
+
- **Conventional commits**, enforced via commitlint + husky, matching the rest of this family.
|
|
149
|
+
|
|
150
|
+
## Gotchas and quirks
|
|
151
|
+
|
|
152
|
+
Every construct either `src/lower` (read) or `src/emit` (write) cannot represent losslessly is a documented, reachable `MarkdownDiagnosticCodes` entry, not a silent approximation:
|
|
153
|
+
|
|
154
|
+
- **`md/invented-page-geometry`** — markdown has no page concept of its own; every lowered document gets one `ContentSection` with A4 + 1in default page geometry (overridable via `ReadMarkdownOptions.pageSize`/`margins`). Fires unconditionally, once per lowered document.
|
|
155
|
+
- **`md/nested-emphasis-flattened`** — emphasis nested inside the identical kind (emphasis-in-emphasis, strong-in-strong) flattens to one run rather than preserving the nesting; `src/emit/inline.ts`'s `pickEmphasisMarker` resolves the common single-boundary re-emission case but has no second fallback delimiter character for a genuine three-or-more-way clash between adjacent spans.
|
|
156
|
+
- **`md/link-title-dropped`** — a link or image's own title attribute (`[text](url "title")`) has no `ContentRun`/`ContentImageBlock` field to survive on.
|
|
157
|
+
- **`md/code-block-info-string-dropped`** — a fenced code block's own info string (the language tag after the opening fence) has no `ContentParagraph` field to survive on.
|
|
158
|
+
- **`md/blockquote-nested-depth`** — a blockquote nested beyond one level is recorded only as an indent depth (`indentLeftPt`), never a genuine container boundary; two independent blockquotes back to back at the same depth are indistinguishable from one that spans both.
|
|
159
|
+
- **`md/list-item-block-unlisted`** — a table or a resolved image directly inside a list item has no way to carry `ContentListMembership`, which lives only on `ContentParagraph`.
|
|
160
|
+
- **`md/list-item-multi-block-flattened`** — a list item containing more than one non-nested-list block loses its own item-boundary identity once lowered; `ContentListMembership` carries only `{numId, level}`, with no field distinguishing "one item, several blocks" from "several items sharing this numId/level".
|
|
161
|
+
- **`md/image-unresolved`** — an image with no `MarkdownImageResolver` supplied (or one that returns `undefined`, or resolved bytes that are neither a readable PNG nor JPEG) degrades to a hyperlinked text run of its own alt text, never an invalid `ContentImageBlock`.
|
|
162
|
+
- **`md/raw-html-preserved-as-text` / `md/raw-html-dropped`** — raw HTML is preserved as literal text by default (styleId `HTMLPreformatted` for block-level HTML) or dropped entirely (`rawHtml: 'drop'`); this package's read side never sanitises or interprets it.
|
|
163
|
+
- **`md/front-matter-key-unmapped`** — a leading YAML front matter block is not parsed by a real YAML/TOML engine; only `key: value` lines (plus one array special case for `keywords`) mapping onto five known `LayoutMetadata` fields are recognised, everything else is reported and dropped.
|
|
164
|
+
- **`md/heading-level-clamped`** — a `ContentDocument` heading styleId beyond `Heading6` (never produced by this package's own read side, but reachable from another format's `ContentDocument` via the shared pivot) clamps to level 6, since neither ATX nor setext syntax spells a deeper level.
|
|
165
|
+
- **`md/adjacent-links-merged`** and **`md/code-span-as-monospace-run`** — a run of adjacent hyperlinks sharing one destination merges into a single markdown link; a monospace-font run without a genuine code-span origin still emits as a code span, since `ContentDocument` has no separate "this was actually a code span" marker.
|
|
166
|
+
- **`md/paragraph-indent-dropped`** — a paragraph carrying `indentLeftPt` with none of the five styleIds this package's own blockquote/code-block/rule/HTML-preformatted convention recognises is a genuine cross-format ambiguity (is it a quote, or another format's own paragraph indentation?) this package cannot resolve; the indent is dropped, the paragraph still renders.
|
|
167
|
+
- **`md/list-numid-fallback`** — a `numId` this package never minted itself (another format's own list-identity scheme) falls back to a plain, tight, non-task bullet list, the documented cross-format contract for `src/shared/list-id.ts`'s opaque grammar.
|
|
168
|
+
- **`md/table-cell-formatting-dropped`** and **`md/table-cell-multi-paragraph-joined`** — a GFM table cell's own run-level formatting beyond plain text, and a cell containing more than one paragraph, are both lossy: GFM's own table-cell grammar has no multi-paragraph or rich-formatting representation to write back to.
|
|
169
|
+
|
|
170
|
+
## Fidelity
|
|
171
|
+
|
|
172
|
+
**Markdown → `ContentDocument` is dominated by target-schema limits, not parsing gaps — the inverse framing from `pdf-codec`'s own Fidelity section, where the *source* format (arbitrary real-world PDF) is what bounds fidelity.** Here, the hand-written parser understands everything CommonMark and GFM define — every construct in both specifications is recognised and structurally parsed correctly. What `ContentDocument` cannot hold is the limiting factor: it is a cross-format pivot shared with docx/pptx/odt/odp/ods/odg, shaped around what THOSE formats can represent, not around markdown's own richer container/precision model (no blockquote container node, no fenced-code-fence-character-choice field, no per-list-item multi-block boundary, no link/image title). Every one of these is a genuine, permanent structural mismatch between markdown's own grammar and the shared pivot's shape, not something a better parser could close.
|
|
173
|
+
|
|
174
|
+
**The real, reported round-trip conformance rate** — measured by `src/conformance.test.ts`/`src/gfm-conformance.test.ts` running the actual public surface (`readMarkdown` → `writeMarkdown` → reparse → render to HTML) against the vendored spec corpora, compared byte for byte against each example's own expected HTML — is:
|
|
175
|
+
|
|
176
|
+
| Corpus | Examples | Passing round trip | Rate |
|
|
177
|
+
| --- | --- | --- | --- |
|
|
178
|
+
| CommonMark 0.31.2 (`assets/commonmark/spec.json`) | 652 | 461 | 70.7% |
|
|
179
|
+
| GFM tagged extensions (table/strikethrough/autolink/task-list, `assets/gfm/spec.txt`) | 23 | 22 | 95.7% |
|
|
180
|
+
| Combined | 675 | 483 | 71.6% |
|
|
181
|
+
|
|
182
|
+
Every one of the 192 examples not yet passing is named individually in `src/test-support/conformance-exclusions.ts`, attributed to one of a small, closed set of named, understood causes (a shrink-only list — see [Conventions](#conventions)): most commonly a soft line break collapsing to a literal space rather than surviving as a literal newline (the single largest reason by count), a dropped link/image title or code-fence info string, a flattened multi-block list item or nested blockquote, or several directly-touching emphasis spans that only leave two delimiter characters to resolve every boundary at once. None of these are "not yet gotten around to" placeholders — each is an architectural limitation of `ContentDocument`'s own shape, re-diagnosed and found reachable through many corpus examples at once, which is why `conformance-exclusions.ts`'s own reason strings are shared, named constants rather than one bespoke sentence per example.
|
|
183
|
+
|
|
184
|
+
This is also why `pdf-codec`'s own permanent "no round-trip-losslessness claim" framing applies here for the identical underlying reason but the opposite direction of blame: `pdf-codec` cannot promise fidelity because arbitrary real-world PDF vastly exceeds what any parser can safely assume about it; `markdown-codec`'s parser is complete, but `ContentDocument` itself is the narrower vessel a full CommonMark+GFM document is being poured into.
|
|
185
|
+
|
|
186
|
+
**Optional real-world corpus.** `test/corpus/` (gitignored, never committed) holds a `pnpm test:corpus` vitest project for a manual sanity check against real, large, table-heavy, fence-heavy markdown a hand-built fixture can't fully stand in for — this family's own sibling repository READMEs (`documents.js`, `pdf-codec`, `odf.js`, `ooxml.js`, `document-schema.js`), read straight from their checkout locations on disk. It asserts only that `readMarkdown`/`writeMarkdown` don't throw and that a reparse still produces real content — not byte-for-byte fidelity, which real-world markdown was never going to hold to anyway. It is not part of `pnpm test` and never gates CI; run it locally before a significant change to `src/lower/`, `src/emit/`, or the scanner/block/inline layers.
|
|
187
|
+
|
|
188
|
+
## Release and publishing
|
|
189
|
+
|
|
190
|
+
`.github/workflows/ci.yml` runs commitlint, lint, typecheck, the unit suite (including the conformance suites), and the smoke test on every push and pull request. On a push to `main` where those all pass, `release.config.ts` drives [semantic-release](https://semantic-release.gitbook.io/semantic-release): commit history since the last tag decides the version bump, `CHANGELOG.md` and `package.json` are committed back to `main`, a GitHub Release is cut, and the package publishes to [npmjs.org](https://www.npmjs.com/package/markdown-codec) via npm's OIDC trusted publishing, so no `NPM_TOKEN` exists anywhere in the pipeline.
|
|
191
|
+
|
|
192
|
+
Whether that release actually published a new version is detected by diffing `package.json`'s version before and after the release step, not by trusting a third-party action's own detection. Three further jobs gate on that: one republishes the same build under the scoped `@exadev/markdown-codec` alias to GitHub Packages (which has no OIDC exchange of its own, so it authenticates with `GITHUB_TOKEN` instead), one republishes under the `mrkdwn.js` alias to npmjs.org via the identical OIDC exchange, and one packs the release into its own directory, generates an SPDX SBOM (`pnpm sbom`), and signs both an SBOM and a build-provenance attestation against that exact tarball — verifiable independently of the registry, and still present if the package is later unpublished.
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
Commits follow Conventional Commits (`feat:`, `fix:`, `test:`, `chore:`, …), enforced by commitlint (`commitlint.config.ts`) via a husky `commit-msg` hook and a CI `commitlint` job — semantic-release's version bump depends on these being well-formed, not just style. A husky `pre-commit` hook runs `lint-staged` (`eslint --fix` on staged `*.ts` files) and `pre-push` runs the test suite. There is a single `main` branch and no open pull request workflow established so far.
|
|
197
|
+
|
|
198
|
+
## References
|
|
199
|
+
|
|
200
|
+
- [document-schema.js](https://github.com/ExaDev/document-schema.js) — the sibling package that owns the shared `ContentDocument` pivot this package reads and writes.
|
|
201
|
+
- [pdf-codec](https://github.com/ExaDev/pdf-codec) — the sibling package this project's own scaffold, tooling, and "hand-write the format" philosophy are modelled on.
|
|
202
|
+
- [documents.js](https://github.com/ExaDev/documents.js) — the consumer package positioned to bridge markdown to docx/pptx/odt/odp/PDF via this package's `ContentDocument` output, the same way it already bridges odt⇄docx and odp⇄pptx.
|
|
203
|
+
- [CommonMark Spec](https://spec.commonmark.org/) — the base specification this package's scanner/block/inline parsers target.
|
|
204
|
+
- [GitHub Flavored Markdown Spec](https://github.github.com/gfm/) — the GFM extensions layered on top of CommonMark.
|
|
205
|
+
- [WHATWG HTML Standard § named character references](https://html.spec.whatwg.org/multipage/named-characters.html) — the entity table `assets/html-entities/` vendors.
|
|
206
|
+
|
|
207
|
+
## npm aliases
|
|
208
|
+
|
|
209
|
+
This package also publishes under the following alternate npm name — the identical build, same version, republished by CI alongside the primary `markdown-codec` package:
|
|
210
|
+
|
|
211
|
+
- [mrkdwn.js](https://www.npmjs.com/package/mrkdwn.js)
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT
|
package/dist/ast/ast.cjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/ast/ast.ts
|
|
3
|
+
const BLOCK_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
4
|
+
"document",
|
|
5
|
+
"paragraph",
|
|
6
|
+
"heading",
|
|
7
|
+
"blockquote",
|
|
8
|
+
"list",
|
|
9
|
+
"listItem",
|
|
10
|
+
"codeBlock",
|
|
11
|
+
"thematicBreak",
|
|
12
|
+
"htmlBlock",
|
|
13
|
+
"table",
|
|
14
|
+
"tableRow",
|
|
15
|
+
"tableCell"
|
|
16
|
+
]);
|
|
17
|
+
function isMarkdownBlockNode(node) {
|
|
18
|
+
return BLOCK_NODE_TYPES.has(node.type);
|
|
19
|
+
}
|
|
20
|
+
function isMarkdownInlineNode(node) {
|
|
21
|
+
return !BLOCK_NODE_TYPES.has(node.type);
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
exports.isMarkdownBlockNode = isMarkdownBlockNode;
|
|
25
|
+
exports.isMarkdownInlineNode = isMarkdownInlineNode;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { A as MarkdownTableCellNode, C as MarkdownParagraphNode, D as MarkdownStrikethroughNode, E as MarkdownSoftBreakNode, F as isMarkdownBlockNode, I as isMarkdownInlineNode, M as MarkdownTableRowNode, N as MarkdownTextNode, O as MarkdownStrongNode, P as MarkdownThematicBreakNode, S as MarkdownOrderedListDelimiter, T as MarkdownRawHtmlNode, _ as MarkdownLinkNode, a as MarkdownCodeBlockNode, b as MarkdownListNode, c as MarkdownEmphasisMarker, d as MarkdownHardBreakNode, f as MarkdownHeadingNode, g as MarkdownInlineNode, h as MarkdownImageNode, i as MarkdownBulletMarker, j as MarkdownTableNode, k as MarkdownTableAlignment, l as MarkdownEmphasisNode, m as MarkdownHtmlBlockNode, n as MarkdownBlockNode, o as MarkdownCodeSpanNode, p as MarkdownHeadingStyle, r as MarkdownBlockquoteNode, s as MarkdownDocumentNode, t as MarkdownAutolinkNode, u as MarkdownEntityNode, v as MarkdownListItemNode, w as MarkdownPosition, x as MarkdownNode, y as MarkdownListMarkerType } from "../ast-BXYwy08e.cjs";
|
|
2
|
+
export { MarkdownAutolinkNode, MarkdownBlockNode, MarkdownBlockquoteNode, MarkdownBulletMarker, MarkdownCodeBlockNode, MarkdownCodeSpanNode, MarkdownDocumentNode, MarkdownEmphasisMarker, MarkdownEmphasisNode, MarkdownEntityNode, MarkdownHardBreakNode, MarkdownHeadingNode, MarkdownHeadingStyle, MarkdownHtmlBlockNode, MarkdownImageNode, MarkdownInlineNode, MarkdownLinkNode, MarkdownListItemNode, MarkdownListMarkerType, MarkdownListNode, MarkdownNode, MarkdownOrderedListDelimiter, MarkdownParagraphNode, MarkdownPosition, MarkdownRawHtmlNode, MarkdownSoftBreakNode, MarkdownStrikethroughNode, MarkdownStrongNode, MarkdownTableAlignment, MarkdownTableCellNode, MarkdownTableNode, MarkdownTableRowNode, MarkdownTextNode, MarkdownThematicBreakNode, isMarkdownBlockNode, isMarkdownInlineNode };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { A as MarkdownTableCellNode, C as MarkdownParagraphNode, D as MarkdownStrikethroughNode, E as MarkdownSoftBreakNode, F as isMarkdownBlockNode, I as isMarkdownInlineNode, M as MarkdownTableRowNode, N as MarkdownTextNode, O as MarkdownStrongNode, P as MarkdownThematicBreakNode, S as MarkdownOrderedListDelimiter, T as MarkdownRawHtmlNode, _ as MarkdownLinkNode, a as MarkdownCodeBlockNode, b as MarkdownListNode, c as MarkdownEmphasisMarker, d as MarkdownHardBreakNode, f as MarkdownHeadingNode, g as MarkdownInlineNode, h as MarkdownImageNode, i as MarkdownBulletMarker, j as MarkdownTableNode, k as MarkdownTableAlignment, l as MarkdownEmphasisNode, m as MarkdownHtmlBlockNode, n as MarkdownBlockNode, o as MarkdownCodeSpanNode, p as MarkdownHeadingStyle, r as MarkdownBlockquoteNode, s as MarkdownDocumentNode, t as MarkdownAutolinkNode, u as MarkdownEntityNode, v as MarkdownListItemNode, w as MarkdownPosition, x as MarkdownNode, y as MarkdownListMarkerType } from "../ast-BXYwy08e.js";
|
|
2
|
+
export { MarkdownAutolinkNode, MarkdownBlockNode, MarkdownBlockquoteNode, MarkdownBulletMarker, MarkdownCodeBlockNode, MarkdownCodeSpanNode, MarkdownDocumentNode, MarkdownEmphasisMarker, MarkdownEmphasisNode, MarkdownEntityNode, MarkdownHardBreakNode, MarkdownHeadingNode, MarkdownHeadingStyle, MarkdownHtmlBlockNode, MarkdownImageNode, MarkdownInlineNode, MarkdownLinkNode, MarkdownListItemNode, MarkdownListMarkerType, MarkdownListNode, MarkdownNode, MarkdownOrderedListDelimiter, MarkdownParagraphNode, MarkdownPosition, MarkdownRawHtmlNode, MarkdownSoftBreakNode, MarkdownStrikethroughNode, MarkdownStrongNode, MarkdownTableAlignment, MarkdownTableCellNode, MarkdownTableNode, MarkdownTableRowNode, MarkdownTextNode, MarkdownThematicBreakNode, isMarkdownBlockNode, isMarkdownInlineNode };
|
package/dist/ast/ast.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/ast/ast.ts
|
|
2
|
+
const BLOCK_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
3
|
+
"document",
|
|
4
|
+
"paragraph",
|
|
5
|
+
"heading",
|
|
6
|
+
"blockquote",
|
|
7
|
+
"list",
|
|
8
|
+
"listItem",
|
|
9
|
+
"codeBlock",
|
|
10
|
+
"thematicBreak",
|
|
11
|
+
"htmlBlock",
|
|
12
|
+
"table",
|
|
13
|
+
"tableRow",
|
|
14
|
+
"tableCell"
|
|
15
|
+
]);
|
|
16
|
+
function isMarkdownBlockNode(node) {
|
|
17
|
+
return BLOCK_NODE_TYPES.has(node.type);
|
|
18
|
+
}
|
|
19
|
+
function isMarkdownInlineNode(node) {
|
|
20
|
+
return !BLOCK_NODE_TYPES.has(node.type);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { isMarkdownBlockNode, isMarkdownInlineNode };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
//#region src/ast/ast.d.ts
|
|
2
|
+
interface MarkdownPosition {
|
|
3
|
+
readonly startLine: number;
|
|
4
|
+
readonly startColumn: number;
|
|
5
|
+
readonly endLine: number;
|
|
6
|
+
readonly endColumn: number;
|
|
7
|
+
}
|
|
8
|
+
type MarkdownNode = MarkdownBlockNode | MarkdownInlineNode;
|
|
9
|
+
type MarkdownBlockNode = MarkdownDocumentNode | MarkdownParagraphNode | MarkdownHeadingNode | MarkdownBlockquoteNode | MarkdownListNode | MarkdownListItemNode | MarkdownCodeBlockNode | MarkdownThematicBreakNode | MarkdownHtmlBlockNode | MarkdownTableNode | MarkdownTableRowNode | MarkdownTableCellNode;
|
|
10
|
+
interface MarkdownDocumentNode {
|
|
11
|
+
readonly type: 'document';
|
|
12
|
+
readonly children: MarkdownBlockNode[];
|
|
13
|
+
readonly position?: MarkdownPosition;
|
|
14
|
+
}
|
|
15
|
+
interface MarkdownParagraphNode {
|
|
16
|
+
readonly type: 'paragraph';
|
|
17
|
+
readonly children: MarkdownInlineNode[];
|
|
18
|
+
readonly position?: MarkdownPosition;
|
|
19
|
+
}
|
|
20
|
+
type MarkdownHeadingStyle = 'atx' | 'setext';
|
|
21
|
+
interface MarkdownHeadingNode {
|
|
22
|
+
readonly type: 'heading';
|
|
23
|
+
readonly level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
24
|
+
readonly style: MarkdownHeadingStyle;
|
|
25
|
+
readonly children: MarkdownInlineNode[];
|
|
26
|
+
readonly position?: MarkdownPosition;
|
|
27
|
+
}
|
|
28
|
+
interface MarkdownBlockquoteNode {
|
|
29
|
+
readonly type: 'blockquote';
|
|
30
|
+
readonly children: MarkdownBlockNode[];
|
|
31
|
+
readonly position?: MarkdownPosition;
|
|
32
|
+
}
|
|
33
|
+
type MarkdownListMarkerType = 'bullet' | 'ordered';
|
|
34
|
+
type MarkdownBulletMarker = '-' | '*' | '+';
|
|
35
|
+
type MarkdownOrderedListDelimiter = '.' | ')';
|
|
36
|
+
interface MarkdownListNode {
|
|
37
|
+
readonly type: 'list';
|
|
38
|
+
readonly markerType: MarkdownListMarkerType;
|
|
39
|
+
readonly bulletMarker?: MarkdownBulletMarker;
|
|
40
|
+
readonly orderedDelimiter?: MarkdownOrderedListDelimiter;
|
|
41
|
+
readonly start?: number;
|
|
42
|
+
readonly tight: boolean;
|
|
43
|
+
readonly children: MarkdownListItemNode[];
|
|
44
|
+
readonly position?: MarkdownPosition;
|
|
45
|
+
}
|
|
46
|
+
interface MarkdownListItemNode {
|
|
47
|
+
readonly type: 'listItem';
|
|
48
|
+
readonly checked?: boolean;
|
|
49
|
+
readonly children: MarkdownBlockNode[];
|
|
50
|
+
readonly position?: MarkdownPosition;
|
|
51
|
+
}
|
|
52
|
+
interface MarkdownCodeBlockNode {
|
|
53
|
+
readonly type: 'codeBlock';
|
|
54
|
+
readonly fenced: boolean;
|
|
55
|
+
readonly fenceChar?: '`' | '~';
|
|
56
|
+
readonly infoString?: string;
|
|
57
|
+
readonly literal: string;
|
|
58
|
+
readonly position?: MarkdownPosition;
|
|
59
|
+
}
|
|
60
|
+
interface MarkdownThematicBreakNode {
|
|
61
|
+
readonly type: 'thematicBreak';
|
|
62
|
+
readonly position?: MarkdownPosition;
|
|
63
|
+
}
|
|
64
|
+
interface MarkdownHtmlBlockNode {
|
|
65
|
+
readonly type: 'htmlBlock';
|
|
66
|
+
readonly literal: string;
|
|
67
|
+
readonly position?: MarkdownPosition;
|
|
68
|
+
}
|
|
69
|
+
type MarkdownTableAlignment = 'left' | 'right' | 'center' | 'none';
|
|
70
|
+
interface MarkdownTableNode {
|
|
71
|
+
readonly type: 'table';
|
|
72
|
+
readonly alignments: MarkdownTableAlignment[];
|
|
73
|
+
readonly children: MarkdownTableRowNode[];
|
|
74
|
+
readonly position?: MarkdownPosition;
|
|
75
|
+
}
|
|
76
|
+
interface MarkdownTableRowNode {
|
|
77
|
+
readonly type: 'tableRow';
|
|
78
|
+
readonly header: boolean;
|
|
79
|
+
readonly children: MarkdownTableCellNode[];
|
|
80
|
+
readonly position?: MarkdownPosition;
|
|
81
|
+
}
|
|
82
|
+
interface MarkdownTableCellNode {
|
|
83
|
+
readonly type: 'tableCell';
|
|
84
|
+
readonly children: MarkdownInlineNode[];
|
|
85
|
+
readonly position?: MarkdownPosition;
|
|
86
|
+
}
|
|
87
|
+
type MarkdownInlineNode = MarkdownTextNode | MarkdownEmphasisNode | MarkdownStrongNode | MarkdownStrikethroughNode | MarkdownCodeSpanNode | MarkdownLinkNode | MarkdownImageNode | MarkdownAutolinkNode | MarkdownHardBreakNode | MarkdownSoftBreakNode | MarkdownRawHtmlNode | MarkdownEntityNode;
|
|
88
|
+
type MarkdownEmphasisMarker = '_' | '*';
|
|
89
|
+
interface MarkdownTextNode {
|
|
90
|
+
readonly type: 'text';
|
|
91
|
+
readonly value: string;
|
|
92
|
+
readonly position?: MarkdownPosition;
|
|
93
|
+
}
|
|
94
|
+
interface MarkdownEmphasisNode {
|
|
95
|
+
readonly type: 'emphasis';
|
|
96
|
+
readonly marker: MarkdownEmphasisMarker;
|
|
97
|
+
readonly children: MarkdownInlineNode[];
|
|
98
|
+
readonly position?: MarkdownPosition;
|
|
99
|
+
}
|
|
100
|
+
interface MarkdownStrongNode {
|
|
101
|
+
readonly type: 'strong';
|
|
102
|
+
readonly marker: MarkdownEmphasisMarker;
|
|
103
|
+
readonly children: MarkdownInlineNode[];
|
|
104
|
+
readonly position?: MarkdownPosition;
|
|
105
|
+
}
|
|
106
|
+
interface MarkdownStrikethroughNode {
|
|
107
|
+
readonly type: 'strikethrough';
|
|
108
|
+
readonly children: MarkdownInlineNode[];
|
|
109
|
+
readonly position?: MarkdownPosition;
|
|
110
|
+
}
|
|
111
|
+
interface MarkdownCodeSpanNode {
|
|
112
|
+
readonly type: 'codeSpan';
|
|
113
|
+
readonly literal: string;
|
|
114
|
+
readonly position?: MarkdownPosition;
|
|
115
|
+
}
|
|
116
|
+
interface MarkdownLinkNode {
|
|
117
|
+
readonly type: 'link';
|
|
118
|
+
readonly destination: string;
|
|
119
|
+
readonly title?: string;
|
|
120
|
+
readonly children: MarkdownInlineNode[];
|
|
121
|
+
readonly position?: MarkdownPosition;
|
|
122
|
+
}
|
|
123
|
+
interface MarkdownImageNode {
|
|
124
|
+
readonly type: 'image';
|
|
125
|
+
readonly destination: string;
|
|
126
|
+
readonly title?: string;
|
|
127
|
+
readonly alt: string;
|
|
128
|
+
readonly widthPx?: number;
|
|
129
|
+
readonly heightPx?: number;
|
|
130
|
+
readonly position?: MarkdownPosition;
|
|
131
|
+
}
|
|
132
|
+
interface MarkdownAutolinkNode {
|
|
133
|
+
readonly type: 'autolink';
|
|
134
|
+
readonly destination: string;
|
|
135
|
+
readonly email: boolean;
|
|
136
|
+
readonly position?: MarkdownPosition;
|
|
137
|
+
}
|
|
138
|
+
interface MarkdownHardBreakNode {
|
|
139
|
+
readonly type: 'hardBreak';
|
|
140
|
+
readonly position?: MarkdownPosition;
|
|
141
|
+
}
|
|
142
|
+
interface MarkdownSoftBreakNode {
|
|
143
|
+
readonly type: 'softBreak';
|
|
144
|
+
readonly position?: MarkdownPosition;
|
|
145
|
+
}
|
|
146
|
+
interface MarkdownRawHtmlNode {
|
|
147
|
+
readonly type: 'rawHtml';
|
|
148
|
+
readonly literal: string;
|
|
149
|
+
readonly position?: MarkdownPosition;
|
|
150
|
+
}
|
|
151
|
+
interface MarkdownEntityNode {
|
|
152
|
+
readonly type: 'entity';
|
|
153
|
+
readonly raw: string;
|
|
154
|
+
readonly value: string;
|
|
155
|
+
readonly position?: MarkdownPosition;
|
|
156
|
+
}
|
|
157
|
+
declare function isMarkdownBlockNode(node: MarkdownNode): node is MarkdownBlockNode;
|
|
158
|
+
declare function isMarkdownInlineNode(node: MarkdownNode): node is MarkdownInlineNode;
|
|
159
|
+
//#endregion
|
|
160
|
+
export { MarkdownTableCellNode as A, MarkdownParagraphNode as C, MarkdownStrikethroughNode as D, MarkdownSoftBreakNode as E, isMarkdownBlockNode as F, isMarkdownInlineNode as I, MarkdownTableRowNode as M, MarkdownTextNode as N, MarkdownStrongNode as O, MarkdownThematicBreakNode as P, MarkdownOrderedListDelimiter as S, MarkdownRawHtmlNode as T, MarkdownLinkNode as _, MarkdownCodeBlockNode as a, MarkdownListNode as b, MarkdownEmphasisMarker as c, MarkdownHardBreakNode as d, MarkdownHeadingNode as f, MarkdownInlineNode as g, MarkdownImageNode as h, MarkdownBulletMarker as i, MarkdownTableNode as j, MarkdownTableAlignment as k, MarkdownEmphasisNode as l, MarkdownHtmlBlockNode as m, MarkdownBlockNode as n, MarkdownCodeSpanNode as o, MarkdownHeadingStyle as p, MarkdownBlockquoteNode as r, MarkdownDocumentNode as s, MarkdownAutolinkNode as t, MarkdownEntityNode as u, MarkdownListItemNode as v, MarkdownPosition as w, MarkdownNode as x, MarkdownListMarkerType as y };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
//#region src/ast/ast.d.ts
|
|
2
|
+
interface MarkdownPosition {
|
|
3
|
+
readonly startLine: number;
|
|
4
|
+
readonly startColumn: number;
|
|
5
|
+
readonly endLine: number;
|
|
6
|
+
readonly endColumn: number;
|
|
7
|
+
}
|
|
8
|
+
type MarkdownNode = MarkdownBlockNode | MarkdownInlineNode;
|
|
9
|
+
type MarkdownBlockNode = MarkdownDocumentNode | MarkdownParagraphNode | MarkdownHeadingNode | MarkdownBlockquoteNode | MarkdownListNode | MarkdownListItemNode | MarkdownCodeBlockNode | MarkdownThematicBreakNode | MarkdownHtmlBlockNode | MarkdownTableNode | MarkdownTableRowNode | MarkdownTableCellNode;
|
|
10
|
+
interface MarkdownDocumentNode {
|
|
11
|
+
readonly type: 'document';
|
|
12
|
+
readonly children: MarkdownBlockNode[];
|
|
13
|
+
readonly position?: MarkdownPosition;
|
|
14
|
+
}
|
|
15
|
+
interface MarkdownParagraphNode {
|
|
16
|
+
readonly type: 'paragraph';
|
|
17
|
+
readonly children: MarkdownInlineNode[];
|
|
18
|
+
readonly position?: MarkdownPosition;
|
|
19
|
+
}
|
|
20
|
+
type MarkdownHeadingStyle = 'atx' | 'setext';
|
|
21
|
+
interface MarkdownHeadingNode {
|
|
22
|
+
readonly type: 'heading';
|
|
23
|
+
readonly level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
24
|
+
readonly style: MarkdownHeadingStyle;
|
|
25
|
+
readonly children: MarkdownInlineNode[];
|
|
26
|
+
readonly position?: MarkdownPosition;
|
|
27
|
+
}
|
|
28
|
+
interface MarkdownBlockquoteNode {
|
|
29
|
+
readonly type: 'blockquote';
|
|
30
|
+
readonly children: MarkdownBlockNode[];
|
|
31
|
+
readonly position?: MarkdownPosition;
|
|
32
|
+
}
|
|
33
|
+
type MarkdownListMarkerType = 'bullet' | 'ordered';
|
|
34
|
+
type MarkdownBulletMarker = '-' | '*' | '+';
|
|
35
|
+
type MarkdownOrderedListDelimiter = '.' | ')';
|
|
36
|
+
interface MarkdownListNode {
|
|
37
|
+
readonly type: 'list';
|
|
38
|
+
readonly markerType: MarkdownListMarkerType;
|
|
39
|
+
readonly bulletMarker?: MarkdownBulletMarker;
|
|
40
|
+
readonly orderedDelimiter?: MarkdownOrderedListDelimiter;
|
|
41
|
+
readonly start?: number;
|
|
42
|
+
readonly tight: boolean;
|
|
43
|
+
readonly children: MarkdownListItemNode[];
|
|
44
|
+
readonly position?: MarkdownPosition;
|
|
45
|
+
}
|
|
46
|
+
interface MarkdownListItemNode {
|
|
47
|
+
readonly type: 'listItem';
|
|
48
|
+
readonly checked?: boolean;
|
|
49
|
+
readonly children: MarkdownBlockNode[];
|
|
50
|
+
readonly position?: MarkdownPosition;
|
|
51
|
+
}
|
|
52
|
+
interface MarkdownCodeBlockNode {
|
|
53
|
+
readonly type: 'codeBlock';
|
|
54
|
+
readonly fenced: boolean;
|
|
55
|
+
readonly fenceChar?: '`' | '~';
|
|
56
|
+
readonly infoString?: string;
|
|
57
|
+
readonly literal: string;
|
|
58
|
+
readonly position?: MarkdownPosition;
|
|
59
|
+
}
|
|
60
|
+
interface MarkdownThematicBreakNode {
|
|
61
|
+
readonly type: 'thematicBreak';
|
|
62
|
+
readonly position?: MarkdownPosition;
|
|
63
|
+
}
|
|
64
|
+
interface MarkdownHtmlBlockNode {
|
|
65
|
+
readonly type: 'htmlBlock';
|
|
66
|
+
readonly literal: string;
|
|
67
|
+
readonly position?: MarkdownPosition;
|
|
68
|
+
}
|
|
69
|
+
type MarkdownTableAlignment = 'left' | 'right' | 'center' | 'none';
|
|
70
|
+
interface MarkdownTableNode {
|
|
71
|
+
readonly type: 'table';
|
|
72
|
+
readonly alignments: MarkdownTableAlignment[];
|
|
73
|
+
readonly children: MarkdownTableRowNode[];
|
|
74
|
+
readonly position?: MarkdownPosition;
|
|
75
|
+
}
|
|
76
|
+
interface MarkdownTableRowNode {
|
|
77
|
+
readonly type: 'tableRow';
|
|
78
|
+
readonly header: boolean;
|
|
79
|
+
readonly children: MarkdownTableCellNode[];
|
|
80
|
+
readonly position?: MarkdownPosition;
|
|
81
|
+
}
|
|
82
|
+
interface MarkdownTableCellNode {
|
|
83
|
+
readonly type: 'tableCell';
|
|
84
|
+
readonly children: MarkdownInlineNode[];
|
|
85
|
+
readonly position?: MarkdownPosition;
|
|
86
|
+
}
|
|
87
|
+
type MarkdownInlineNode = MarkdownTextNode | MarkdownEmphasisNode | MarkdownStrongNode | MarkdownStrikethroughNode | MarkdownCodeSpanNode | MarkdownLinkNode | MarkdownImageNode | MarkdownAutolinkNode | MarkdownHardBreakNode | MarkdownSoftBreakNode | MarkdownRawHtmlNode | MarkdownEntityNode;
|
|
88
|
+
type MarkdownEmphasisMarker = '_' | '*';
|
|
89
|
+
interface MarkdownTextNode {
|
|
90
|
+
readonly type: 'text';
|
|
91
|
+
readonly value: string;
|
|
92
|
+
readonly position?: MarkdownPosition;
|
|
93
|
+
}
|
|
94
|
+
interface MarkdownEmphasisNode {
|
|
95
|
+
readonly type: 'emphasis';
|
|
96
|
+
readonly marker: MarkdownEmphasisMarker;
|
|
97
|
+
readonly children: MarkdownInlineNode[];
|
|
98
|
+
readonly position?: MarkdownPosition;
|
|
99
|
+
}
|
|
100
|
+
interface MarkdownStrongNode {
|
|
101
|
+
readonly type: 'strong';
|
|
102
|
+
readonly marker: MarkdownEmphasisMarker;
|
|
103
|
+
readonly children: MarkdownInlineNode[];
|
|
104
|
+
readonly position?: MarkdownPosition;
|
|
105
|
+
}
|
|
106
|
+
interface MarkdownStrikethroughNode {
|
|
107
|
+
readonly type: 'strikethrough';
|
|
108
|
+
readonly children: MarkdownInlineNode[];
|
|
109
|
+
readonly position?: MarkdownPosition;
|
|
110
|
+
}
|
|
111
|
+
interface MarkdownCodeSpanNode {
|
|
112
|
+
readonly type: 'codeSpan';
|
|
113
|
+
readonly literal: string;
|
|
114
|
+
readonly position?: MarkdownPosition;
|
|
115
|
+
}
|
|
116
|
+
interface MarkdownLinkNode {
|
|
117
|
+
readonly type: 'link';
|
|
118
|
+
readonly destination: string;
|
|
119
|
+
readonly title?: string;
|
|
120
|
+
readonly children: MarkdownInlineNode[];
|
|
121
|
+
readonly position?: MarkdownPosition;
|
|
122
|
+
}
|
|
123
|
+
interface MarkdownImageNode {
|
|
124
|
+
readonly type: 'image';
|
|
125
|
+
readonly destination: string;
|
|
126
|
+
readonly title?: string;
|
|
127
|
+
readonly alt: string;
|
|
128
|
+
readonly widthPx?: number;
|
|
129
|
+
readonly heightPx?: number;
|
|
130
|
+
readonly position?: MarkdownPosition;
|
|
131
|
+
}
|
|
132
|
+
interface MarkdownAutolinkNode {
|
|
133
|
+
readonly type: 'autolink';
|
|
134
|
+
readonly destination: string;
|
|
135
|
+
readonly email: boolean;
|
|
136
|
+
readonly position?: MarkdownPosition;
|
|
137
|
+
}
|
|
138
|
+
interface MarkdownHardBreakNode {
|
|
139
|
+
readonly type: 'hardBreak';
|
|
140
|
+
readonly position?: MarkdownPosition;
|
|
141
|
+
}
|
|
142
|
+
interface MarkdownSoftBreakNode {
|
|
143
|
+
readonly type: 'softBreak';
|
|
144
|
+
readonly position?: MarkdownPosition;
|
|
145
|
+
}
|
|
146
|
+
interface MarkdownRawHtmlNode {
|
|
147
|
+
readonly type: 'rawHtml';
|
|
148
|
+
readonly literal: string;
|
|
149
|
+
readonly position?: MarkdownPosition;
|
|
150
|
+
}
|
|
151
|
+
interface MarkdownEntityNode {
|
|
152
|
+
readonly type: 'entity';
|
|
153
|
+
readonly raw: string;
|
|
154
|
+
readonly value: string;
|
|
155
|
+
readonly position?: MarkdownPosition;
|
|
156
|
+
}
|
|
157
|
+
declare function isMarkdownBlockNode(node: MarkdownNode): node is MarkdownBlockNode;
|
|
158
|
+
declare function isMarkdownInlineNode(node: MarkdownNode): node is MarkdownInlineNode;
|
|
159
|
+
//#endregion
|
|
160
|
+
export { MarkdownTableCellNode as A, MarkdownParagraphNode as C, MarkdownStrikethroughNode as D, MarkdownSoftBreakNode as E, isMarkdownBlockNode as F, isMarkdownInlineNode as I, MarkdownTableRowNode as M, MarkdownTextNode as N, MarkdownStrongNode as O, MarkdownThematicBreakNode as P, MarkdownOrderedListDelimiter as S, MarkdownRawHtmlNode as T, MarkdownLinkNode as _, MarkdownCodeBlockNode as a, MarkdownListNode as b, MarkdownEmphasisMarker as c, MarkdownHardBreakNode as d, MarkdownHeadingNode as f, MarkdownInlineNode as g, MarkdownImageNode as h, MarkdownBulletMarker as i, MarkdownTableNode as j, MarkdownTableAlignment as k, MarkdownEmphasisNode as l, MarkdownHtmlBlockNode as m, MarkdownBlockNode as n, MarkdownCodeSpanNode as o, MarkdownHeadingStyle as p, MarkdownBlockquoteNode as r, MarkdownDocumentNode as s, MarkdownAutolinkNode as t, MarkdownEntityNode as u, MarkdownListItemNode as v, MarkdownPosition as w, MarkdownNode as x, MarkdownListMarkerType as y };
|