@markuplint/markdown-parser 5.0.0-rc.2 → 5.0.0-rc.5
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/CHANGELOG.md +15 -0
- package/lib/markdown-aware-parser.d.ts +8 -115
- package/lib/markdown-aware-parser.js +17 -131
- package/lib/parser.d.ts +6 -0
- package/lib/parser.js +19 -0
- package/package.json +6 -6
- package/src/index.spec.ts +25 -0
- package/src/markdown-aware-parser.ts +17 -131
- package/src/parser.ts +19 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/ARCHITECTURE.md +0 -190
|
@@ -25,31 +25,20 @@ import { Parser, getNamespace } from '@markuplint/parser-utils';
|
|
|
25
25
|
type MdastNode = RootContent;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
28
|
+
* Extends `Parser<MdastNode>` rather than `HtmlParser`: an earlier design
|
|
29
|
+
* extended `HtmlParser` and treated Markdown as opaque psblock nodes, which
|
|
30
|
+
* left Markdown constructs invisible to rules. Converting them to their HTML
|
|
31
|
+
* equivalents (with synthesized attributes such as `src`/`alt` from
|
|
32
|
+
* ``) is what makes Markdown content lintable.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
36
|
-
/**
|
|
37
|
-
* Stores link/image reference definitions (`[id]: url "title"`)
|
|
38
|
-
* extracted during tokenization for resolving linkReference/imageReference nodes.
|
|
39
|
-
*/
|
|
40
36
|
protected definitions = new Map<string, Definition>();
|
|
41
37
|
|
|
42
|
-
/**
|
|
43
|
-
* Offsets of table rows that are header rows (first row of each table).
|
|
44
|
-
* Set by visitTableElement, read by nodeizeMarkdownNode for tableRow dispatch.
|
|
45
|
-
*/
|
|
38
|
+
/** Set by visitTableElement, read by nodeizeMarkdownNode for tableRow dispatch. */
|
|
46
39
|
readonly #headerRowOffsets = new Set<number>();
|
|
47
40
|
|
|
48
|
-
/**
|
|
49
|
-
* Current cell element name ('th' or 'td').
|
|
50
|
-
* Set by tableRow processing, read by tableCell processing.
|
|
51
|
-
* Reset to 'td' after each row.
|
|
52
|
-
*/
|
|
41
|
+
/** Set by tableRow processing, read by tableCell processing; reset to 'td' after each row. */
|
|
53
42
|
#currentCellName: 'th' | 'td' = 'td';
|
|
54
43
|
|
|
55
44
|
constructor(options?: ParserOptions) {
|
|
@@ -57,8 +46,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
57
46
|
}
|
|
58
47
|
|
|
59
48
|
/**
|
|
60
|
-
* Resets mutable state accumulated during a previous `parse()` call.
|
|
61
|
-
*
|
|
62
49
|
* Must be called at the beginning of every `tokenize()` invocation to
|
|
63
50
|
* prevent definitions, header-row offsets, and cell-name state from
|
|
64
51
|
* leaking across successive `parse()` calls on the same parser instance.
|
|
@@ -70,13 +57,8 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
70
57
|
}
|
|
71
58
|
|
|
72
59
|
/**
|
|
73
|
-
* Adjusts the flattened node list for Markdown output.
|
|
74
|
-
*
|
|
75
60
|
* Disables whitespace and invalid-node exposure because Markdown
|
|
76
61
|
* generates only synthetic elements with no real HTML whitespace tokens.
|
|
77
|
-
*
|
|
78
|
-
* @param nodeList - The flattened node tree produced by the base class.
|
|
79
|
-
* @returns The adjusted node list.
|
|
80
62
|
*/
|
|
81
63
|
afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[]) {
|
|
82
64
|
return super.afterFlattenNodes(nodeList, {
|
|
@@ -86,15 +68,8 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
86
68
|
}
|
|
87
69
|
|
|
88
70
|
/**
|
|
89
|
-
* Creates a synthetic HTML attribute token for Markdown-derived elements.
|
|
90
|
-
*
|
|
91
71
|
* The attribute positions point to the element's own token range because
|
|
92
72
|
* Markdown syntax does not have discrete attribute source positions.
|
|
93
|
-
*
|
|
94
|
-
* @param name - The attribute name (e.g., `"href"`, `"alt"`).
|
|
95
|
-
* @param value - The attribute value extracted from Markdown syntax.
|
|
96
|
-
* @param token - The source token whose position is reused for the attribute.
|
|
97
|
-
* @returns A fully-formed HTML attribute node.
|
|
98
73
|
*/
|
|
99
74
|
protected createSyntheticAttr(name: string, value: string, token: Token): MLASTHTMLAttr {
|
|
100
75
|
const emptyToken = this.createToken('', token.offset, token.line, token.col);
|
|
@@ -118,17 +93,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
118
93
|
};
|
|
119
94
|
}
|
|
120
95
|
|
|
121
|
-
/**
|
|
122
|
-
* Builds a generic HTML element node from a Markdown construct.
|
|
123
|
-
*
|
|
124
|
-
* @param token - The source token covering the entire construct.
|
|
125
|
-
* @param nodeName - The HTML element name (e.g., `"p"`, `"h1"`, `"li"`).
|
|
126
|
-
* @param childNodes - The mdast children to recurse into.
|
|
127
|
-
* @param depth - Current nesting depth in the AST.
|
|
128
|
-
* @param parentNode - Parent AST node, or `null` for top-level nodes.
|
|
129
|
-
* @param attributes - Optional pre-built attributes to attach.
|
|
130
|
-
* @returns The element node followed by its descendants.
|
|
131
|
-
*/
|
|
132
96
|
protected visitMarkdownElement(
|
|
133
97
|
token: Token,
|
|
134
98
|
nodeName: string,
|
|
@@ -165,15 +129,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
165
129
|
return [startTag, ...siblings];
|
|
166
130
|
}
|
|
167
131
|
|
|
168
|
-
/**
|
|
169
|
-
* Builds an `<a>` element with `href` (and optionally `title`) attributes.
|
|
170
|
-
*
|
|
171
|
-
* @param originNode - The mdast `link` node.
|
|
172
|
-
* @param token - The source token covering the link.
|
|
173
|
-
* @param depth - Current nesting depth.
|
|
174
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
175
|
-
* @returns The `<a>` element node and its descendants.
|
|
176
|
-
*/
|
|
177
132
|
protected visitLinkElement(
|
|
178
133
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
179
134
|
originNode: Link,
|
|
@@ -190,15 +145,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
190
145
|
return this.visitMarkdownElement(token, 'a', originNode.children, depth, parentNode, attrs);
|
|
191
146
|
}
|
|
192
147
|
|
|
193
|
-
/**
|
|
194
|
-
* Builds an `<img>` element with `src`, `alt`, and optionally `title` attributes.
|
|
195
|
-
*
|
|
196
|
-
* @param originNode - The mdast `image` node.
|
|
197
|
-
* @param token - The source token covering the image.
|
|
198
|
-
* @param depth - Current nesting depth.
|
|
199
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
200
|
-
* @returns The `<img>` element node.
|
|
201
|
-
*/
|
|
202
148
|
protected visitImageElement(
|
|
203
149
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
204
150
|
originNode: Image,
|
|
@@ -218,16 +164,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
218
164
|
return this.visitMarkdownElement(token, 'img', [], depth, parentNode, attrs);
|
|
219
165
|
}
|
|
220
166
|
|
|
221
|
-
/**
|
|
222
|
-
* Builds a `<ul>` or `<ol>` element. Adds a `start` attribute when the
|
|
223
|
-
* ordered list begins at a number other than 1.
|
|
224
|
-
*
|
|
225
|
-
* @param originNode - The mdast `list` node.
|
|
226
|
-
* @param token - The source token covering the list.
|
|
227
|
-
* @param depth - Current nesting depth.
|
|
228
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
229
|
-
* @returns The list element node and its descendants.
|
|
230
|
-
*/
|
|
231
167
|
protected visitListElement(
|
|
232
168
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
233
169
|
originNode: List,
|
|
@@ -245,17 +181,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
245
181
|
return this.visitMarkdownElement(token, nodeName, originNode.children, depth, parentNode, attrs);
|
|
246
182
|
}
|
|
247
183
|
|
|
248
|
-
/**
|
|
249
|
-
* Builds a `<code>` element for inline code spans (backtick-delimited).
|
|
250
|
-
*
|
|
251
|
-
* @param originNode - The mdast `inlineCode` node.
|
|
252
|
-
* @param token - The source token covering the code span.
|
|
253
|
-
* @param offset - Start offset in the original source.
|
|
254
|
-
* @param endOffset - End offset in the original source.
|
|
255
|
-
* @param depth - Current nesting depth.
|
|
256
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
257
|
-
* @returns The `<code>` element node (with a text child when content is found).
|
|
258
|
-
*/
|
|
259
184
|
protected visitInlineCode(
|
|
260
185
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
261
186
|
originNode: InlineCode,
|
|
@@ -312,16 +237,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
312
237
|
return [startTag];
|
|
313
238
|
}
|
|
314
239
|
|
|
315
|
-
/**
|
|
316
|
-
* Builds a `<pre><code>` structure for fenced code blocks.
|
|
317
|
-
* When a language is specified, adds `class="language-{lang}"` to the `<code>` element.
|
|
318
|
-
*
|
|
319
|
-
* @param originNode - The mdast `code` node.
|
|
320
|
-
* @param token - The source token covering the fenced block.
|
|
321
|
-
* @param depth - Current nesting depth.
|
|
322
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
323
|
-
* @returns The `<pre>` and `<code>` element nodes.
|
|
324
|
-
*/
|
|
325
240
|
protected visitCodeBlock(
|
|
326
241
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
327
242
|
originNode: Code,
|
|
@@ -329,7 +244,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
329
244
|
depth: number,
|
|
330
245
|
parentNode: MLASTParentNode | null,
|
|
331
246
|
): readonly MLASTNodeTreeItem[] {
|
|
332
|
-
// Build <pre> element
|
|
333
247
|
const preTag: MLASTElement = {
|
|
334
248
|
...token,
|
|
335
249
|
...this.createToken(token),
|
|
@@ -351,7 +265,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
351
265
|
nodeName: 'pre',
|
|
352
266
|
};
|
|
353
267
|
|
|
354
|
-
// Build <code> element as child of <pre>
|
|
355
268
|
const codeAttrs: MLASTHTMLAttr[] = [];
|
|
356
269
|
if (originNode.lang) {
|
|
357
270
|
codeAttrs.push(this.createSyntheticAttr('class', `language-${originNode.lang}`, token));
|
|
@@ -378,7 +291,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
378
291
|
nodeName: 'code',
|
|
379
292
|
};
|
|
380
293
|
|
|
381
|
-
// Add code content as text node if present
|
|
382
294
|
if (originNode.value.length > 0) {
|
|
383
295
|
const position = originNode.position;
|
|
384
296
|
if (position) {
|
|
@@ -409,16 +321,6 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
409
321
|
return [preTag, codeTag];
|
|
410
322
|
}
|
|
411
323
|
|
|
412
|
-
/**
|
|
413
|
-
* Builds a `<table>` element from a GFM table node.
|
|
414
|
-
* Marks the first row's offset as a header row so that its cells become `<th>`.
|
|
415
|
-
*
|
|
416
|
-
* @param originNode - The mdast `table` node (GFM extension).
|
|
417
|
-
* @param token - The source token covering the table.
|
|
418
|
-
* @param depth - Current nesting depth.
|
|
419
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
420
|
-
* @returns The `<table>` element node and its descendants.
|
|
421
|
-
*/
|
|
422
324
|
protected visitTableElement(
|
|
423
325
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
424
326
|
originNode: Table,
|
|
@@ -435,18 +337,9 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
435
337
|
}
|
|
436
338
|
|
|
437
339
|
/**
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* @param token - The source token covering the node's range.
|
|
442
|
-
* @param offset - Start offset in the original source.
|
|
443
|
-
* @param endOffset - End offset in the original source.
|
|
444
|
-
* @param depth - Current nesting depth.
|
|
445
|
-
* @param parentNode - Parent AST node, or `null` for top-level nodes.
|
|
446
|
-
* @returns An array of AST nodes for recognized Markdown constructs,
|
|
447
|
-
* or `null` when the node type is not handled here (the caller is
|
|
448
|
-
* responsible for handling it — typically `text`, `html`, or
|
|
449
|
-
* parser-specific node types).
|
|
340
|
+
* Returns `null` when the node type is not handled here, signalling that
|
|
341
|
+
* the caller must handle it (typically `text`, `html`, or parser-specific
|
|
342
|
+
* node types).
|
|
450
343
|
*/
|
|
451
344
|
protected nodeizeMarkdownNode(
|
|
452
345
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -484,6 +377,8 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
484
377
|
return this.visitMarkdownElement(token, 'li', originNode.children, depth, parentNode);
|
|
485
378
|
}
|
|
486
379
|
case 'blockquote': {
|
|
380
|
+
// Markdown's `> quote` syntax has no equivalent of the HTML
|
|
381
|
+
// `cite` attribute, so no `cite` attribute is synthesized.
|
|
487
382
|
return this.visitMarkdownElement(token, 'blockquote', originNode.children, depth, parentNode);
|
|
488
383
|
}
|
|
489
384
|
case 'thematicBreak': {
|
|
@@ -559,8 +454,9 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
559
454
|
}
|
|
560
455
|
|
|
561
456
|
/**
|
|
562
|
-
*
|
|
563
|
-
*
|
|
457
|
+
* Note: remark-parse resolves references at parse time when definitions
|
|
458
|
+
* exist, so unresolved references typically appear as plain text rather
|
|
459
|
+
* than `linkReference` nodes; the psblock fallback is a defensive path.
|
|
564
460
|
*/
|
|
565
461
|
#visitLinkReference(
|
|
566
462
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -589,8 +485,8 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
589
485
|
}
|
|
590
486
|
|
|
591
487
|
/**
|
|
592
|
-
*
|
|
593
|
-
*
|
|
488
|
+
* Note: as with linkReference, unresolved references typically appear as
|
|
489
|
+
* plain text in the mdast, so the psblock fallback is a defensive path.
|
|
594
490
|
*/
|
|
595
491
|
#visitImageReference(
|
|
596
492
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -622,13 +518,9 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
622
518
|
}
|
|
623
519
|
|
|
624
520
|
/**
|
|
625
|
-
* Extracts definition nodes from mdast children and populates `this.definitions`.
|
|
626
|
-
*
|
|
627
521
|
* Per CommonMark spec, the first definition for a given identifier takes
|
|
628
522
|
* precedence. remark-parse emits all definition nodes in source order, so
|
|
629
523
|
* we skip duplicates via `Map.has` to honour the first-wins rule.
|
|
630
|
-
*
|
|
631
|
-
* @param children - The root-level mdast children to scan for `definition` nodes.
|
|
632
524
|
*/
|
|
633
525
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
634
526
|
protected collectDefinitions(children: readonly RootContent[]) {
|
|
@@ -641,15 +533,9 @@ export abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
|
641
533
|
}
|
|
642
534
|
|
|
643
535
|
/**
|
|
644
|
-
* Computes the 1-based line number and 1-based column for a given offset.
|
|
645
|
-
*
|
|
646
536
|
* Equivalent to `getPosition()` in `@markuplint/parser-utils`, but that
|
|
647
537
|
* function is not exported from the package. Kept as a standalone utility
|
|
648
538
|
* to avoid coupling to parser-utils internals.
|
|
649
|
-
*
|
|
650
|
-
* @param source - The full source string.
|
|
651
|
-
* @param offset - The 0-based character offset to resolve.
|
|
652
|
-
* @returns An object with 1-based `line` and `col` values.
|
|
653
539
|
*/
|
|
654
540
|
export function getLineAndColumn(source: string, offset: number): { line: number; col: number } {
|
|
655
541
|
let line = 1;
|
package/src/parser.ts
CHANGED
|
@@ -17,6 +17,12 @@ type MdastNode = RootContent;
|
|
|
17
17
|
* Uses remark-parse to produce an mdast, then maps Markdown constructs
|
|
18
18
|
* (headings, paragraphs, lists, links, etc.) to their corresponding HTML
|
|
19
19
|
* element AST nodes. Raw HTML regions are parsed via HtmlParser.
|
|
20
|
+
*
|
|
21
|
+
* remark-parse (the unified ecosystem's de facto standard) was chosen over
|
|
22
|
+
* `@mdx-js/mdx`, which wraps remark-parse internally and adds about 24
|
|
23
|
+
* unnecessary dependencies for JS compilation that linting does not need,
|
|
24
|
+
* and over driving the lower-level tokenizer directly, which would require
|
|
25
|
+
* hand-building the mdast conversion with no practical benefit.
|
|
20
26
|
*/
|
|
21
27
|
class MarkdownParser extends MarkdownAwareParser {
|
|
22
28
|
readonly #htmlParser = new HtmlParser();
|
|
@@ -101,7 +107,20 @@ class MarkdownParser extends MarkdownAwareParser {
|
|
|
101
107
|
offsetOffset: offset,
|
|
102
108
|
offsetLine: line,
|
|
103
109
|
offsetColumn: col,
|
|
110
|
+
// HTML embedded inside Markdown is always a partial — never a
|
|
111
|
+
// full document — so force fragment parsing to keep parse5 from
|
|
112
|
+
// emitting `missing-doctype` / `misplaced-doctype` on every
|
|
113
|
+
// inline HTML block. Users cannot meaningfully override this
|
|
114
|
+
// because there is no Markdown construct that wraps a complete
|
|
115
|
+
// HTML document.
|
|
116
|
+
documentMode: 'fragment',
|
|
104
117
|
});
|
|
118
|
+
// Surface tokenizer-level parse errors (e.g. `duplicate-attribute`)
|
|
119
|
+
// collected by the embedded HtmlParser. Without this, every parse
|
|
120
|
+
// error inside an inline HTML block would be silently dropped on
|
|
121
|
+
// the way back from `#htmlParser.parse()` even though the user has
|
|
122
|
+
// opted in via `severity.parseError`.
|
|
123
|
+
this.accumulateParseErrors(doc.parseErrors);
|
|
105
124
|
return [...doc.nodeList];
|
|
106
125
|
}
|
|
107
126
|
}
|