@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
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [5.0.0-rc.5](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.4...v5.0.0-rc.5) (2026-08-28)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- **markdown-parser,pug-parser:** force fragment parsing for embedded HTML ([57c4172](https://github.com/markuplint/markuplint/commit/57c4172d5a68adb7e20a4f4e4d8dbe802983e3ca)), closes [#3844](https://github.com/markuplint/markuplint/issues/3844)
|
|
11
|
+
- **markdown-parser,pug-parser:** forward embedded HTML parseErrors to outer document ([9ec7988](https://github.com/markuplint/markuplint/commit/9ec79882840cbedd76c5b8f774ff10c876d2c858)), closes [#3844](https://github.com/markuplint/markuplint/issues/3844)
|
|
12
|
+
|
|
13
|
+
# [5.0.0-rc.4](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.3...v5.0.0-rc.4) (2026-04-19)
|
|
14
|
+
|
|
15
|
+
**Note:** Version bump only for package @markuplint/markdown-parser
|
|
16
|
+
|
|
17
|
+
# [5.0.0-rc.3](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.2...v5.0.0-rc.3) (2026-04-19)
|
|
18
|
+
|
|
19
|
+
**Note:** Version bump only for package @markuplint/markdown-parser
|
|
20
|
+
|
|
6
21
|
# [5.0.0-rc.2](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.1...v5.0.0-rc.2) (2026-04-15)
|
|
7
22
|
|
|
8
23
|
**Note:** Version bump only for package @markuplint/markdown-parser
|
|
@@ -4,163 +4,56 @@ import type { Code, Definition, Image, InlineCode, Link, List, RootContent, Tabl
|
|
|
4
4
|
import { Parser } from '@markuplint/parser-utils';
|
|
5
5
|
type MdastNode = RootContent;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Extends `Parser<MdastNode>` rather than `HtmlParser`: an earlier design
|
|
8
|
+
* extended `HtmlParser` and treated Markdown as opaque psblock nodes, which
|
|
9
|
+
* left Markdown constructs invisible to rules. Converting them to their HTML
|
|
10
|
+
* equivalents (with synthesized attributes such as `src`/`alt` from
|
|
11
|
+
* ``) is what makes Markdown content lintable.
|
|
12
12
|
*/
|
|
13
13
|
export declare abstract class MarkdownAwareParser extends Parser<MdastNode> {
|
|
14
14
|
#private;
|
|
15
|
-
/**
|
|
16
|
-
* Stores link/image reference definitions (`[id]: url "title"`)
|
|
17
|
-
* extracted during tokenization for resolving linkReference/imageReference nodes.
|
|
18
|
-
*/
|
|
19
15
|
protected definitions: Map<string, Definition>;
|
|
20
16
|
constructor(options?: ParserOptions);
|
|
21
17
|
/**
|
|
22
|
-
* Resets mutable state accumulated during a previous `parse()` call.
|
|
23
|
-
*
|
|
24
18
|
* Must be called at the beginning of every `tokenize()` invocation to
|
|
25
19
|
* prevent definitions, header-row offsets, and cell-name state from
|
|
26
20
|
* leaking across successive `parse()` calls on the same parser instance.
|
|
27
21
|
*/
|
|
28
22
|
protected resetMarkdownState(): void;
|
|
29
23
|
/**
|
|
30
|
-
* Adjusts the flattened node list for Markdown output.
|
|
31
|
-
*
|
|
32
24
|
* Disables whitespace and invalid-node exposure because Markdown
|
|
33
25
|
* generates only synthetic elements with no real HTML whitespace tokens.
|
|
34
|
-
*
|
|
35
|
-
* @param nodeList - The flattened node tree produced by the base class.
|
|
36
|
-
* @returns The adjusted node list.
|
|
37
26
|
*/
|
|
38
27
|
afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[];
|
|
39
28
|
/**
|
|
40
|
-
* Creates a synthetic HTML attribute token for Markdown-derived elements.
|
|
41
|
-
*
|
|
42
29
|
* The attribute positions point to the element's own token range because
|
|
43
30
|
* Markdown syntax does not have discrete attribute source positions.
|
|
44
|
-
*
|
|
45
|
-
* @param name - The attribute name (e.g., `"href"`, `"alt"`).
|
|
46
|
-
* @param value - The attribute value extracted from Markdown syntax.
|
|
47
|
-
* @param token - The source token whose position is reused for the attribute.
|
|
48
|
-
* @returns A fully-formed HTML attribute node.
|
|
49
31
|
*/
|
|
50
32
|
protected createSyntheticAttr(name: string, value: string, token: Token): MLASTHTMLAttr;
|
|
51
|
-
/**
|
|
52
|
-
* Builds a generic HTML element node from a Markdown construct.
|
|
53
|
-
*
|
|
54
|
-
* @param token - The source token covering the entire construct.
|
|
55
|
-
* @param nodeName - The HTML element name (e.g., `"p"`, `"h1"`, `"li"`).
|
|
56
|
-
* @param childNodes - The mdast children to recurse into.
|
|
57
|
-
* @param depth - Current nesting depth in the AST.
|
|
58
|
-
* @param parentNode - Parent AST node, or `null` for top-level nodes.
|
|
59
|
-
* @param attributes - Optional pre-built attributes to attach.
|
|
60
|
-
* @returns The element node followed by its descendants.
|
|
61
|
-
*/
|
|
62
33
|
protected visitMarkdownElement(token: Token, nodeName: string, childNodes: readonly MdastNode[], depth: number, parentNode: MLASTParentNode | null, attributes?: readonly MLASTAttr[]): readonly MLASTNodeTreeItem[];
|
|
63
|
-
/**
|
|
64
|
-
* Builds an `<a>` element with `href` (and optionally `title`) attributes.
|
|
65
|
-
*
|
|
66
|
-
* @param originNode - The mdast `link` node.
|
|
67
|
-
* @param token - The source token covering the link.
|
|
68
|
-
* @param depth - Current nesting depth.
|
|
69
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
70
|
-
* @returns The `<a>` element node and its descendants.
|
|
71
|
-
*/
|
|
72
34
|
protected visitLinkElement(originNode: Link, token: Token, depth: number, parentNode: MLASTParentNode | null): readonly MLASTNodeTreeItem[];
|
|
73
|
-
/**
|
|
74
|
-
* Builds an `<img>` element with `src`, `alt`, and optionally `title` attributes.
|
|
75
|
-
*
|
|
76
|
-
* @param originNode - The mdast `image` node.
|
|
77
|
-
* @param token - The source token covering the image.
|
|
78
|
-
* @param depth - Current nesting depth.
|
|
79
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
80
|
-
* @returns The `<img>` element node.
|
|
81
|
-
*/
|
|
82
35
|
protected visitImageElement(originNode: Image, token: Token, depth: number, parentNode: MLASTParentNode | null): readonly MLASTNodeTreeItem[];
|
|
83
|
-
/**
|
|
84
|
-
* Builds a `<ul>` or `<ol>` element. Adds a `start` attribute when the
|
|
85
|
-
* ordered list begins at a number other than 1.
|
|
86
|
-
*
|
|
87
|
-
* @param originNode - The mdast `list` node.
|
|
88
|
-
* @param token - The source token covering the list.
|
|
89
|
-
* @param depth - Current nesting depth.
|
|
90
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
91
|
-
* @returns The list element node and its descendants.
|
|
92
|
-
*/
|
|
93
36
|
protected visitListElement(originNode: List, token: Token, depth: number, parentNode: MLASTParentNode | null): readonly MLASTNodeTreeItem[];
|
|
94
|
-
/**
|
|
95
|
-
* Builds a `<code>` element for inline code spans (backtick-delimited).
|
|
96
|
-
*
|
|
97
|
-
* @param originNode - The mdast `inlineCode` node.
|
|
98
|
-
* @param token - The source token covering the code span.
|
|
99
|
-
* @param offset - Start offset in the original source.
|
|
100
|
-
* @param endOffset - End offset in the original source.
|
|
101
|
-
* @param depth - Current nesting depth.
|
|
102
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
103
|
-
* @returns The `<code>` element node (with a text child when content is found).
|
|
104
|
-
*/
|
|
105
37
|
protected visitInlineCode(originNode: InlineCode, token: Token, offset: number, endOffset: number, depth: number, parentNode: MLASTParentNode | null): readonly MLASTNodeTreeItem[];
|
|
106
|
-
/**
|
|
107
|
-
* Builds a `<pre><code>` structure for fenced code blocks.
|
|
108
|
-
* When a language is specified, adds `class="language-{lang}"` to the `<code>` element.
|
|
109
|
-
*
|
|
110
|
-
* @param originNode - The mdast `code` node.
|
|
111
|
-
* @param token - The source token covering the fenced block.
|
|
112
|
-
* @param depth - Current nesting depth.
|
|
113
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
114
|
-
* @returns The `<pre>` and `<code>` element nodes.
|
|
115
|
-
*/
|
|
116
38
|
protected visitCodeBlock(originNode: Code, token: Token, depth: number, parentNode: MLASTParentNode | null): readonly MLASTNodeTreeItem[];
|
|
117
|
-
/**
|
|
118
|
-
* Builds a `<table>` element from a GFM table node.
|
|
119
|
-
* Marks the first row's offset as a header row so that its cells become `<th>`.
|
|
120
|
-
*
|
|
121
|
-
* @param originNode - The mdast `table` node (GFM extension).
|
|
122
|
-
* @param token - The source token covering the table.
|
|
123
|
-
* @param depth - Current nesting depth.
|
|
124
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
125
|
-
* @returns The `<table>` element node and its descendants.
|
|
126
|
-
*/
|
|
127
39
|
protected visitTableElement(originNode: Table, token: Token, depth: number, parentNode: MLASTParentNode | null): readonly MLASTNodeTreeItem[];
|
|
128
40
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* @param token - The source token covering the node's range.
|
|
133
|
-
* @param offset - Start offset in the original source.
|
|
134
|
-
* @param endOffset - End offset in the original source.
|
|
135
|
-
* @param depth - Current nesting depth.
|
|
136
|
-
* @param parentNode - Parent AST node, or `null` for top-level nodes.
|
|
137
|
-
* @returns An array of AST nodes for recognized Markdown constructs,
|
|
138
|
-
* or `null` when the node type is not handled here (the caller is
|
|
139
|
-
* responsible for handling it — typically `text`, `html`, or
|
|
140
|
-
* parser-specific node types).
|
|
41
|
+
* Returns `null` when the node type is not handled here, signalling that
|
|
42
|
+
* the caller must handle it (typically `text`, `html`, or parser-specific
|
|
43
|
+
* node types).
|
|
141
44
|
*/
|
|
142
45
|
protected nodeizeMarkdownNode(originNode: MdastNode, token: Token, offset: number, endOffset: number, depth: number, parentNode: MLASTParentNode | null): readonly MLASTNodeTreeItem[] | null;
|
|
143
46
|
/**
|
|
144
|
-
* Extracts definition nodes from mdast children and populates `this.definitions`.
|
|
145
|
-
*
|
|
146
47
|
* Per CommonMark spec, the first definition for a given identifier takes
|
|
147
48
|
* precedence. remark-parse emits all definition nodes in source order, so
|
|
148
49
|
* we skip duplicates via `Map.has` to honour the first-wins rule.
|
|
149
|
-
*
|
|
150
|
-
* @param children - The root-level mdast children to scan for `definition` nodes.
|
|
151
50
|
*/
|
|
152
51
|
protected collectDefinitions(children: readonly RootContent[]): void;
|
|
153
52
|
}
|
|
154
53
|
/**
|
|
155
|
-
* Computes the 1-based line number and 1-based column for a given offset.
|
|
156
|
-
*
|
|
157
54
|
* Equivalent to `getPosition()` in `@markuplint/parser-utils`, but that
|
|
158
55
|
* function is not exported from the package. Kept as a standalone utility
|
|
159
56
|
* to avoid coupling to parser-utils internals.
|
|
160
|
-
*
|
|
161
|
-
* @param source - The full source string.
|
|
162
|
-
* @param offset - The 0-based character offset to resolve.
|
|
163
|
-
* @returns An object with 1-based `line` and `col` values.
|
|
164
57
|
*/
|
|
165
58
|
export declare function getLineAndColumn(source: string, offset: number): {
|
|
166
59
|
line: number;
|
|
@@ -1,34 +1,21 @@
|
|
|
1
1
|
import { Parser, getNamespace } from '@markuplint/parser-utils';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* Extends `Parser<MdastNode>` rather than `HtmlParser`: an earlier design
|
|
4
|
+
* extended `HtmlParser` and treated Markdown as opaque psblock nodes, which
|
|
5
|
+
* left Markdown constructs invisible to rules. Converting them to their HTML
|
|
6
|
+
* equivalents (with synthesized attributes such as `src`/`alt` from
|
|
7
|
+
* ``) is what makes Markdown content lintable.
|
|
8
8
|
*/
|
|
9
9
|
export class MarkdownAwareParser extends Parser {
|
|
10
|
-
/**
|
|
11
|
-
* Stores link/image reference definitions (`[id]: url "title"`)
|
|
12
|
-
* extracted during tokenization for resolving linkReference/imageReference nodes.
|
|
13
|
-
*/
|
|
14
10
|
definitions = new Map();
|
|
15
|
-
/**
|
|
16
|
-
* Offsets of table rows that are header rows (first row of each table).
|
|
17
|
-
* Set by visitTableElement, read by nodeizeMarkdownNode for tableRow dispatch.
|
|
18
|
-
*/
|
|
11
|
+
/** Set by visitTableElement, read by nodeizeMarkdownNode for tableRow dispatch. */
|
|
19
12
|
#headerRowOffsets = new Set();
|
|
20
|
-
/**
|
|
21
|
-
* Current cell element name ('th' or 'td').
|
|
22
|
-
* Set by tableRow processing, read by tableCell processing.
|
|
23
|
-
* Reset to 'td' after each row.
|
|
24
|
-
*/
|
|
13
|
+
/** Set by tableRow processing, read by tableCell processing; reset to 'td' after each row. */
|
|
25
14
|
#currentCellName = 'td';
|
|
26
15
|
constructor(options) {
|
|
27
16
|
super(options);
|
|
28
17
|
}
|
|
29
18
|
/**
|
|
30
|
-
* Resets mutable state accumulated during a previous `parse()` call.
|
|
31
|
-
*
|
|
32
19
|
* Must be called at the beginning of every `tokenize()` invocation to
|
|
33
20
|
* prevent definitions, header-row offsets, and cell-name state from
|
|
34
21
|
* leaking across successive `parse()` calls on the same parser instance.
|
|
@@ -39,13 +26,8 @@ export class MarkdownAwareParser extends Parser {
|
|
|
39
26
|
this.#currentCellName = 'td';
|
|
40
27
|
}
|
|
41
28
|
/**
|
|
42
|
-
* Adjusts the flattened node list for Markdown output.
|
|
43
|
-
*
|
|
44
29
|
* Disables whitespace and invalid-node exposure because Markdown
|
|
45
30
|
* generates only synthetic elements with no real HTML whitespace tokens.
|
|
46
|
-
*
|
|
47
|
-
* @param nodeList - The flattened node tree produced by the base class.
|
|
48
|
-
* @returns The adjusted node list.
|
|
49
31
|
*/
|
|
50
32
|
afterFlattenNodes(nodeList) {
|
|
51
33
|
return super.afterFlattenNodes(nodeList, {
|
|
@@ -54,15 +36,8 @@ export class MarkdownAwareParser extends Parser {
|
|
|
54
36
|
});
|
|
55
37
|
}
|
|
56
38
|
/**
|
|
57
|
-
* Creates a synthetic HTML attribute token for Markdown-derived elements.
|
|
58
|
-
*
|
|
59
39
|
* The attribute positions point to the element's own token range because
|
|
60
40
|
* Markdown syntax does not have discrete attribute source positions.
|
|
61
|
-
*
|
|
62
|
-
* @param name - The attribute name (e.g., `"href"`, `"alt"`).
|
|
63
|
-
* @param value - The attribute value extracted from Markdown syntax.
|
|
64
|
-
* @param token - The source token whose position is reused for the attribute.
|
|
65
|
-
* @returns A fully-formed HTML attribute node.
|
|
66
41
|
*/
|
|
67
42
|
createSyntheticAttr(name, value, token) {
|
|
68
43
|
const emptyToken = this.createToken('', token.offset, token.line, token.col);
|
|
@@ -84,17 +59,6 @@ export class MarkdownAwareParser extends Parser {
|
|
|
84
59
|
isDuplicatable: false,
|
|
85
60
|
};
|
|
86
61
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Builds a generic HTML element node from a Markdown construct.
|
|
89
|
-
*
|
|
90
|
-
* @param token - The source token covering the entire construct.
|
|
91
|
-
* @param nodeName - The HTML element name (e.g., `"p"`, `"h1"`, `"li"`).
|
|
92
|
-
* @param childNodes - The mdast children to recurse into.
|
|
93
|
-
* @param depth - Current nesting depth in the AST.
|
|
94
|
-
* @param parentNode - Parent AST node, or `null` for top-level nodes.
|
|
95
|
-
* @param attributes - Optional pre-built attributes to attach.
|
|
96
|
-
* @returns The element node followed by its descendants.
|
|
97
|
-
*/
|
|
98
62
|
visitMarkdownElement(token, nodeName,
|
|
99
63
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
100
64
|
childNodes, depth, parentNode, attributes = []) {
|
|
@@ -122,15 +86,6 @@ export class MarkdownAwareParser extends Parser {
|
|
|
122
86
|
const siblings = this.visitChildren([...childNodes], startTag);
|
|
123
87
|
return [startTag, ...siblings];
|
|
124
88
|
}
|
|
125
|
-
/**
|
|
126
|
-
* Builds an `<a>` element with `href` (and optionally `title`) attributes.
|
|
127
|
-
*
|
|
128
|
-
* @param originNode - The mdast `link` node.
|
|
129
|
-
* @param token - The source token covering the link.
|
|
130
|
-
* @param depth - Current nesting depth.
|
|
131
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
132
|
-
* @returns The `<a>` element node and its descendants.
|
|
133
|
-
*/
|
|
134
89
|
visitLinkElement(
|
|
135
90
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
136
91
|
originNode, token, depth, parentNode) {
|
|
@@ -140,15 +95,6 @@ export class MarkdownAwareParser extends Parser {
|
|
|
140
95
|
}
|
|
141
96
|
return this.visitMarkdownElement(token, 'a', originNode.children, depth, parentNode, attrs);
|
|
142
97
|
}
|
|
143
|
-
/**
|
|
144
|
-
* Builds an `<img>` element with `src`, `alt`, and optionally `title` attributes.
|
|
145
|
-
*
|
|
146
|
-
* @param originNode - The mdast `image` node.
|
|
147
|
-
* @param token - The source token covering the image.
|
|
148
|
-
* @param depth - Current nesting depth.
|
|
149
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
150
|
-
* @returns The `<img>` element node.
|
|
151
|
-
*/
|
|
152
98
|
visitImageElement(
|
|
153
99
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
154
100
|
originNode, token, depth, parentNode) {
|
|
@@ -161,16 +107,6 @@ export class MarkdownAwareParser extends Parser {
|
|
|
161
107
|
}
|
|
162
108
|
return this.visitMarkdownElement(token, 'img', [], depth, parentNode, attrs);
|
|
163
109
|
}
|
|
164
|
-
/**
|
|
165
|
-
* Builds a `<ul>` or `<ol>` element. Adds a `start` attribute when the
|
|
166
|
-
* ordered list begins at a number other than 1.
|
|
167
|
-
*
|
|
168
|
-
* @param originNode - The mdast `list` node.
|
|
169
|
-
* @param token - The source token covering the list.
|
|
170
|
-
* @param depth - Current nesting depth.
|
|
171
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
172
|
-
* @returns The list element node and its descendants.
|
|
173
|
-
*/
|
|
174
110
|
visitListElement(
|
|
175
111
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
176
112
|
originNode, token, depth, parentNode) {
|
|
@@ -181,17 +117,6 @@ export class MarkdownAwareParser extends Parser {
|
|
|
181
117
|
}
|
|
182
118
|
return this.visitMarkdownElement(token, nodeName, originNode.children, depth, parentNode, attrs);
|
|
183
119
|
}
|
|
184
|
-
/**
|
|
185
|
-
* Builds a `<code>` element for inline code spans (backtick-delimited).
|
|
186
|
-
*
|
|
187
|
-
* @param originNode - The mdast `inlineCode` node.
|
|
188
|
-
* @param token - The source token covering the code span.
|
|
189
|
-
* @param offset - Start offset in the original source.
|
|
190
|
-
* @param endOffset - End offset in the original source.
|
|
191
|
-
* @param depth - Current nesting depth.
|
|
192
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
193
|
-
* @returns The `<code>` element node (with a text child when content is found).
|
|
194
|
-
*/
|
|
195
120
|
visitInlineCode(
|
|
196
121
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
197
122
|
originNode, token, offset, endOffset, depth, parentNode) {
|
|
@@ -236,20 +161,9 @@ export class MarkdownAwareParser extends Parser {
|
|
|
236
161
|
this.appendChild(startTag, textNode);
|
|
237
162
|
return [startTag];
|
|
238
163
|
}
|
|
239
|
-
/**
|
|
240
|
-
* Builds a `<pre><code>` structure for fenced code blocks.
|
|
241
|
-
* When a language is specified, adds `class="language-{lang}"` to the `<code>` element.
|
|
242
|
-
*
|
|
243
|
-
* @param originNode - The mdast `code` node.
|
|
244
|
-
* @param token - The source token covering the fenced block.
|
|
245
|
-
* @param depth - Current nesting depth.
|
|
246
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
247
|
-
* @returns The `<pre>` and `<code>` element nodes.
|
|
248
|
-
*/
|
|
249
164
|
visitCodeBlock(
|
|
250
165
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
251
166
|
originNode, token, depth, parentNode) {
|
|
252
|
-
// Build <pre> element
|
|
253
167
|
const preTag = {
|
|
254
168
|
...token,
|
|
255
169
|
...this.createToken(token),
|
|
@@ -270,7 +184,6 @@ export class MarkdownAwareParser extends Parser {
|
|
|
270
184
|
isFragment: false,
|
|
271
185
|
nodeName: 'pre',
|
|
272
186
|
};
|
|
273
|
-
// Build <code> element as child of <pre>
|
|
274
187
|
const codeAttrs = [];
|
|
275
188
|
if (originNode.lang) {
|
|
276
189
|
codeAttrs.push(this.createSyntheticAttr('class', `language-${originNode.lang}`, token));
|
|
@@ -295,7 +208,6 @@ export class MarkdownAwareParser extends Parser {
|
|
|
295
208
|
isFragment: false,
|
|
296
209
|
nodeName: 'code',
|
|
297
210
|
};
|
|
298
|
-
// Add code content as text node if present
|
|
299
211
|
if (originNode.value.length > 0) {
|
|
300
212
|
const position = originNode.position;
|
|
301
213
|
if (position) {
|
|
@@ -321,16 +233,6 @@ export class MarkdownAwareParser extends Parser {
|
|
|
321
233
|
this.appendChild(preTag, codeTag);
|
|
322
234
|
return [preTag, codeTag];
|
|
323
235
|
}
|
|
324
|
-
/**
|
|
325
|
-
* Builds a `<table>` element from a GFM table node.
|
|
326
|
-
* Marks the first row's offset as a header row so that its cells become `<th>`.
|
|
327
|
-
*
|
|
328
|
-
* @param originNode - The mdast `table` node (GFM extension).
|
|
329
|
-
* @param token - The source token covering the table.
|
|
330
|
-
* @param depth - Current nesting depth.
|
|
331
|
-
* @param parentNode - Parent AST node, or `null` for top-level.
|
|
332
|
-
* @returns The `<table>` element node and its descendants.
|
|
333
|
-
*/
|
|
334
236
|
visitTableElement(
|
|
335
237
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
336
238
|
originNode, token, depth, parentNode) {
|
|
@@ -341,18 +243,9 @@ export class MarkdownAwareParser extends Parser {
|
|
|
341
243
|
return this.visitMarkdownElement(token, 'table', originNode.children, depth, parentNode);
|
|
342
244
|
}
|
|
343
245
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
* @param token - The source token covering the node's range.
|
|
348
|
-
* @param offset - Start offset in the original source.
|
|
349
|
-
* @param endOffset - End offset in the original source.
|
|
350
|
-
* @param depth - Current nesting depth.
|
|
351
|
-
* @param parentNode - Parent AST node, or `null` for top-level nodes.
|
|
352
|
-
* @returns An array of AST nodes for recognized Markdown constructs,
|
|
353
|
-
* or `null` when the node type is not handled here (the caller is
|
|
354
|
-
* responsible for handling it — typically `text`, `html`, or
|
|
355
|
-
* parser-specific node types).
|
|
246
|
+
* Returns `null` when the node type is not handled here, signalling that
|
|
247
|
+
* the caller must handle it (typically `text`, `html`, or parser-specific
|
|
248
|
+
* node types).
|
|
356
249
|
*/
|
|
357
250
|
nodeizeMarkdownNode(
|
|
358
251
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -384,6 +277,8 @@ export class MarkdownAwareParser extends Parser {
|
|
|
384
277
|
return this.visitMarkdownElement(token, 'li', originNode.children, depth, parentNode);
|
|
385
278
|
}
|
|
386
279
|
case 'blockquote': {
|
|
280
|
+
// Markdown's `> quote` syntax has no equivalent of the HTML
|
|
281
|
+
// `cite` attribute, so no `cite` attribute is synthesized.
|
|
387
282
|
return this.visitMarkdownElement(token, 'blockquote', originNode.children, depth, parentNode);
|
|
388
283
|
}
|
|
389
284
|
case 'thematicBreak': {
|
|
@@ -446,8 +341,9 @@ export class MarkdownAwareParser extends Parser {
|
|
|
446
341
|
}
|
|
447
342
|
}
|
|
448
343
|
/**
|
|
449
|
-
*
|
|
450
|
-
*
|
|
344
|
+
* Note: remark-parse resolves references at parse time when definitions
|
|
345
|
+
* exist, so unresolved references typically appear as plain text rather
|
|
346
|
+
* than `linkReference` nodes; the psblock fallback is a defensive path.
|
|
451
347
|
*/
|
|
452
348
|
#visitLinkReference(
|
|
453
349
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -469,8 +365,8 @@ export class MarkdownAwareParser extends Parser {
|
|
|
469
365
|
return this.visitMarkdownElement(token, 'a', originNode.children, depth, parentNode, attrs);
|
|
470
366
|
}
|
|
471
367
|
/**
|
|
472
|
-
*
|
|
473
|
-
*
|
|
368
|
+
* Note: as with linkReference, unresolved references typically appear as
|
|
369
|
+
* plain text in the mdast, so the psblock fallback is a defensive path.
|
|
474
370
|
*/
|
|
475
371
|
#visitImageReference(
|
|
476
372
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -495,13 +391,9 @@ export class MarkdownAwareParser extends Parser {
|
|
|
495
391
|
return this.visitMarkdownElement(token, 'img', [], depth, parentNode, attrs);
|
|
496
392
|
}
|
|
497
393
|
/**
|
|
498
|
-
* Extracts definition nodes from mdast children and populates `this.definitions`.
|
|
499
|
-
*
|
|
500
394
|
* Per CommonMark spec, the first definition for a given identifier takes
|
|
501
395
|
* precedence. remark-parse emits all definition nodes in source order, so
|
|
502
396
|
* we skip duplicates via `Map.has` to honour the first-wins rule.
|
|
503
|
-
*
|
|
504
|
-
* @param children - The root-level mdast children to scan for `definition` nodes.
|
|
505
397
|
*/
|
|
506
398
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
507
399
|
collectDefinitions(children) {
|
|
@@ -513,15 +405,9 @@ export class MarkdownAwareParser extends Parser {
|
|
|
513
405
|
}
|
|
514
406
|
}
|
|
515
407
|
/**
|
|
516
|
-
* Computes the 1-based line number and 1-based column for a given offset.
|
|
517
|
-
*
|
|
518
408
|
* Equivalent to `getPosition()` in `@markuplint/parser-utils`, but that
|
|
519
409
|
* function is not exported from the package. Kept as a standalone utility
|
|
520
410
|
* to avoid coupling to parser-utils internals.
|
|
521
|
-
*
|
|
522
|
-
* @param source - The full source string.
|
|
523
|
-
* @param offset - The 0-based character offset to resolve.
|
|
524
|
-
* @returns An object with 1-based `line` and `col` values.
|
|
525
411
|
*/
|
|
526
412
|
export function getLineAndColumn(source, offset) {
|
|
527
413
|
let line = 1;
|
package/lib/parser.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ type MdastNode = RootContent;
|
|
|
8
8
|
* Uses remark-parse to produce an mdast, then maps Markdown constructs
|
|
9
9
|
* (headings, paragraphs, lists, links, etc.) to their corresponding HTML
|
|
10
10
|
* element AST nodes. Raw HTML regions are parsed via HtmlParser.
|
|
11
|
+
*
|
|
12
|
+
* remark-parse (the unified ecosystem's de facto standard) was chosen over
|
|
13
|
+
* `@mdx-js/mdx`, which wraps remark-parse internally and adds about 24
|
|
14
|
+
* unnecessary dependencies for JS compilation that linting does not need,
|
|
15
|
+
* and over driving the lower-level tokenizer directly, which would require
|
|
16
|
+
* hand-building the mdast conversion with no practical benefit.
|
|
11
17
|
*/
|
|
12
18
|
declare class MarkdownParser extends MarkdownAwareParser {
|
|
13
19
|
#private;
|
package/lib/parser.js
CHANGED
|
@@ -10,6 +10,12 @@ import { MarkdownAwareParser, getLineAndColumn } from './markdown-aware-parser.j
|
|
|
10
10
|
* Uses remark-parse to produce an mdast, then maps Markdown constructs
|
|
11
11
|
* (headings, paragraphs, lists, links, etc.) to their corresponding HTML
|
|
12
12
|
* element AST nodes. Raw HTML regions are parsed via HtmlParser.
|
|
13
|
+
*
|
|
14
|
+
* remark-parse (the unified ecosystem's de facto standard) was chosen over
|
|
15
|
+
* `@mdx-js/mdx`, which wraps remark-parse internally and adds about 24
|
|
16
|
+
* unnecessary dependencies for JS compilation that linting does not need,
|
|
17
|
+
* and over driving the lower-level tokenizer directly, which would require
|
|
18
|
+
* hand-building the mdast conversion with no practical benefit.
|
|
13
19
|
*/
|
|
14
20
|
class MarkdownParser extends MarkdownAwareParser {
|
|
15
21
|
#htmlParser = new HtmlParser();
|
|
@@ -84,7 +90,20 @@ class MarkdownParser extends MarkdownAwareParser {
|
|
|
84
90
|
offsetOffset: offset,
|
|
85
91
|
offsetLine: line,
|
|
86
92
|
offsetColumn: col,
|
|
93
|
+
// HTML embedded inside Markdown is always a partial — never a
|
|
94
|
+
// full document — so force fragment parsing to keep parse5 from
|
|
95
|
+
// emitting `missing-doctype` / `misplaced-doctype` on every
|
|
96
|
+
// inline HTML block. Users cannot meaningfully override this
|
|
97
|
+
// because there is no Markdown construct that wraps a complete
|
|
98
|
+
// HTML document.
|
|
99
|
+
documentMode: 'fragment',
|
|
87
100
|
});
|
|
101
|
+
// Surface tokenizer-level parse errors (e.g. `duplicate-attribute`)
|
|
102
|
+
// collected by the embedded HtmlParser. Without this, every parse
|
|
103
|
+
// error inside an inline HTML block would be silently dropped on
|
|
104
|
+
// the way back from `#htmlParser.parse()` even though the user has
|
|
105
|
+
// opted in via `severity.parseError`.
|
|
106
|
+
this.accumulateParseErrors(doc.parseErrors);
|
|
88
107
|
return [...doc.nodeList];
|
|
89
108
|
}
|
|
90
109
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/markdown-parser",
|
|
3
|
-
"version": "5.0.0-rc.
|
|
3
|
+
"version": "5.0.0-rc.5",
|
|
4
4
|
"description": "Markdown parser for markuplint",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"engines": {
|
|
13
|
-
"node": ">=
|
|
13
|
+
"node": ">=24"
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@markuplint/html-parser": "5.0.0-rc.
|
|
32
|
-
"@markuplint/ml-ast": "5.0.0-rc.
|
|
33
|
-
"@markuplint/parser-utils": "5.0.0-rc.
|
|
31
|
+
"@markuplint/html-parser": "5.0.0-rc.5",
|
|
32
|
+
"@markuplint/ml-ast": "5.0.0-rc.5",
|
|
33
|
+
"@markuplint/parser-utils": "5.0.0-rc.5",
|
|
34
34
|
"remark-frontmatter": "5.0.0",
|
|
35
35
|
"remark-gfm": "4.0.1",
|
|
36
36
|
"remark-parse": "11.0.0",
|
|
@@ -39,5 +39,5 @@
|
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/mdast": "4.0.4"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "8d87463af2ff3f1b83fb28da20f1819362cf3555"
|
|
43
43
|
}
|
package/src/index.spec.ts
CHANGED
|
@@ -751,6 +751,31 @@ describe('MarkdownParser', () => {
|
|
|
751
751
|
});
|
|
752
752
|
});
|
|
753
753
|
|
|
754
|
+
describe('Embedded HTML — parseErrors propagation (#3844)', () => {
|
|
755
|
+
test('emits tokenizer-level parse5 events for malformed embedded HTML', () => {
|
|
756
|
+
// `<div a a>` triggers parse5 `duplicate-attribute`. Even though the
|
|
757
|
+
// HTML lives inside Markdown, the tokenizer-level error must surface
|
|
758
|
+
// on `MLASTDocument.parseErrors`.
|
|
759
|
+
const doc = parse('# heading\n\n<div a a></div>\n');
|
|
760
|
+
const codes = (doc.parseErrors ?? []).map(e => e.code);
|
|
761
|
+
expect(codes).toContain('duplicate-attribute');
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
test('forces fragment parsing so document-level parse5 events do NOT leak from embedded HTML', () => {
|
|
765
|
+
// Without `documentMode: 'fragment'` forcing, a bare `<head>` in an
|
|
766
|
+
// inline HTML block would trip parse5's `missing-doctype` (since the
|
|
767
|
+
// internal HtmlParser would auto-detect document mode and complain
|
|
768
|
+
// about the missing `<!doctype html>`). Forcing fragment mode in
|
|
769
|
+
// `markdown-parser` is what prevents that leak — this test pins that
|
|
770
|
+
// contract.
|
|
771
|
+
const doc = parse('# heading\n\n<head><meta charset="utf-8"></head>\n');
|
|
772
|
+
const codes = (doc.parseErrors ?? []).map(e => e.code);
|
|
773
|
+
expect(codes).not.toContain('missing-doctype');
|
|
774
|
+
expect(codes).not.toContain('misplaced-doctype');
|
|
775
|
+
expect(codes).not.toContain('non-conforming-doctype');
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
|
|
754
779
|
describe('getLineAndColumn', () => {
|
|
755
780
|
test('returns line 1, col 1 for offset 0', () => {
|
|
756
781
|
expect(getLineAndColumn('hello', 0)).toStrictEqual({ line: 1, col: 1 });
|