@markuplint/mdx-parser 5.2.0 → 5.3.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/CHANGELOG.md +14 -0
- package/lib/parser.d.ts +22 -2
- package/lib/parser.js +38 -3
- package/package.json +7 -7
- package/src/component-name.spec.ts +28 -0
- package/src/index.spec.ts +216 -125
- package/src/parser.ts +41 -3
- package/src/tag-name-start.spec.ts +25 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
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.3.0](https://github.com/markuplint/markuplint/compare/v5.2.0...v5.3.0) (2026-10-21)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* make published packages loadable from require() and put types first ([723106f](https://github.com/markuplint/markuplint/commit/723106f51aac4e60dc02db2c1a894617368b0253)), closes [#4198](https://github.com/markuplint/markuplint/issues/4198)
|
|
11
|
+
* **mdx-parser:** read a tag whose name starts with `$`, `_` or a non-ASCII letter ([4ecfe45](https://github.com/markuplint/markuplint/commit/4ecfe455a83cb4f0c9ef496964d91c817e03e5c1))
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **mdx-parser:** answer what a component name is by the rule of JSX ([c29c5af](https://github.com/markuplint/markuplint/commit/c29c5af5455d3253bee0915075f09a49a68b2ebe))
|
|
16
|
+
* **mdx-parser:** declare attribute names as case-sensitive ([f5a6f21](https://github.com/markuplint/markuplint/commit/f5a6f214bb229cdb306f0bc49b394b1b32aea283)), closes [#4229](https://github.com/markuplint/markuplint/issues/4229)
|
|
17
|
+
* **parser-utils:** let a parser say how an attribute name gets into the DOM ([abd81e5](https://github.com/markuplint/markuplint/commit/abd81e5d20f4ffeb0ba878e77fd4122c1a803a9f))
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# [5.2.0](https://github.com/markuplint/markuplint/compare/v5.1.1...v5.2.0) (2026-10-13)
|
|
7
21
|
|
|
8
22
|
### Bug Fixes
|
package/lib/parser.d.ts
CHANGED
|
@@ -66,12 +66,32 @@ declare class MDXParser extends MarkdownAwareParser {
|
|
|
66
66
|
__rightText?: string;
|
|
67
67
|
});
|
|
68
68
|
/**
|
|
69
|
-
* MDX
|
|
69
|
+
* An MDX element is `authored` when its name is a component's (`isComponentName` below: a name that
|
|
70
|
+
* does not start with a lowercase ASCII letter, or a member expression), a web component when it
|
|
71
|
+
* is a custom element name, and `html` otherwise.
|
|
70
72
|
*
|
|
71
73
|
* @param nodeName - The element name to classify.
|
|
72
|
-
* @returns The element type (`'html'` or `'authored'`).
|
|
74
|
+
* @returns The element type (`'html'`, `'web-component'` or `'authored'`).
|
|
73
75
|
*/
|
|
74
76
|
detectElementType(nodeName: string): import("@markuplint/ml-ast").ElementType;
|
|
77
|
+
/**
|
|
78
|
+
* An MDX element name starts with an ECMAScript `IdentifierStart` as a JSX one does (see
|
|
79
|
+
* `JSXParser#isTagNameStart` in `@markuplint/jsx-parser`): keep the two in step.
|
|
80
|
+
*
|
|
81
|
+
* @param char - The character that follows `<`
|
|
82
|
+
* @returns Whether it starts a tag name
|
|
83
|
+
*/
|
|
84
|
+
isTagNameStart(char: string): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* An MDX component is a JSX element, so a name is a component unless it starts with a lowercase
|
|
87
|
+
* ASCII letter or has a `-`; a `.` makes it a member expression. The rule is the one of
|
|
88
|
+
* `@markuplint/jsx-parser` (see `JSXParser#isComponentName` there for the grounds), which this
|
|
89
|
+
* package does not depend on: keep the two in step.
|
|
90
|
+
*
|
|
91
|
+
* @param name - A tag name as written, case kept
|
|
92
|
+
* @returns Whether the name is a component's
|
|
93
|
+
*/
|
|
94
|
+
isComponentName(name: string): boolean;
|
|
75
95
|
}
|
|
76
96
|
export declare const parser: MDXParser;
|
|
77
97
|
export {};
|
package/lib/parser.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { MarkdownAwareParser } from '@markuplint/markdown-parser';
|
|
2
|
+
import { isPotentialCustomElementName } from '@markuplint/parser-utils';
|
|
2
3
|
import remarkFrontmatter from 'remark-frontmatter';
|
|
3
4
|
import remarkGfm from 'remark-gfm';
|
|
4
5
|
import remarkMdx from 'remark-mdx';
|
|
5
6
|
import remarkParse from 'remark-parse';
|
|
6
7
|
import { unified } from 'unified';
|
|
8
|
+
/**
|
|
9
|
+
* ECMAScript `IdentifierStart`, as `JSXIdentifier` starts (https://facebook.github.io/jsx/#prod-JSXIdentifier).
|
|
10
|
+
* `@markuplint/jsx-parser` has the same constant: keep the two in step.
|
|
11
|
+
*/
|
|
12
|
+
const JSX_IDENTIFIER_START = /^[\p{ID_Start}$_]/u;
|
|
7
13
|
/**
|
|
8
14
|
* Parser for MDX files that maps JSX elements to markuplint's AST.
|
|
9
15
|
*
|
|
@@ -30,6 +36,8 @@ class MDXParser extends MarkdownAwareParser {
|
|
|
30
36
|
endTagType: 'xml',
|
|
31
37
|
booleanish: true,
|
|
32
38
|
tagNameCaseSensitive: true,
|
|
39
|
+
attrNameCaseSensitive: true,
|
|
40
|
+
attrNameRoute: 'dom-api',
|
|
33
41
|
});
|
|
34
42
|
}
|
|
35
43
|
/**
|
|
@@ -158,13 +166,40 @@ class MDXParser extends MarkdownAwareParser {
|
|
|
158
166
|
return attr;
|
|
159
167
|
}
|
|
160
168
|
/**
|
|
161
|
-
* MDX
|
|
169
|
+
* An MDX element is `authored` when its name is a component's (`isComponentName` below: a name that
|
|
170
|
+
* does not start with a lowercase ASCII letter, or a member expression), a web component when it
|
|
171
|
+
* is a custom element name, and `html` otherwise.
|
|
162
172
|
*
|
|
163
173
|
* @param nodeName - The element name to classify.
|
|
164
|
-
* @returns The element type (`'html'` or `'authored'`).
|
|
174
|
+
* @returns The element type (`'html'`, `'web-component'` or `'authored'`).
|
|
165
175
|
*/
|
|
166
176
|
detectElementType(nodeName) {
|
|
167
|
-
return super.detectElementType(nodeName,
|
|
177
|
+
return super.detectElementType(nodeName, (name) => this.isComponentName(name));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* An MDX element name starts with an ECMAScript `IdentifierStart` as a JSX one does (see
|
|
181
|
+
* `JSXParser#isTagNameStart` in `@markuplint/jsx-parser`): keep the two in step.
|
|
182
|
+
*
|
|
183
|
+
* @param char - The character that follows `<`
|
|
184
|
+
* @returns Whether it starts a tag name
|
|
185
|
+
*/
|
|
186
|
+
isTagNameStart(char) {
|
|
187
|
+
return JSX_IDENTIFIER_START.test(char);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* An MDX component is a JSX element, so a name is a component unless it starts with a lowercase
|
|
191
|
+
* ASCII letter or has a `-`; a `.` makes it a member expression. The rule is the one of
|
|
192
|
+
* `@markuplint/jsx-parser` (see `JSXParser#isComponentName` there for the grounds), which this
|
|
193
|
+
* package does not depend on: keep the two in step.
|
|
194
|
+
*
|
|
195
|
+
* @param name - A tag name as written, case kept
|
|
196
|
+
* @returns Whether the name is a component's
|
|
197
|
+
*/
|
|
198
|
+
isComponentName(name) {
|
|
199
|
+
if (isPotentialCustomElementName(name)) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
return name.includes('.') || (JSX_IDENTIFIER_START.test(name) && !/^[a-z]/.test(name) && !name.includes('-'));
|
|
168
203
|
}
|
|
169
204
|
#visitJsxElement(
|
|
170
205
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/mdx-parser",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "MDX parser for markuplint",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"types": "./lib/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"publishConfig": {
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@markuplint/markdown-parser": "5.
|
|
32
|
-
"@markuplint/ml-ast": "5.
|
|
33
|
-
"@markuplint/parser-utils": "5.
|
|
31
|
+
"@markuplint/markdown-parser": "5.3.0",
|
|
32
|
+
"@markuplint/ml-ast": "5.3.0",
|
|
33
|
+
"@markuplint/parser-utils": "5.3.0",
|
|
34
34
|
"remark-frontmatter": "^5.0.0",
|
|
35
35
|
"remark-gfm": "^4.0.1",
|
|
36
36
|
"remark-mdx": "^3.1.1",
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/mdast": "4.0.4"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "f587cf0206c8426e57369a48d6d5fb22306eafde"
|
|
44
44
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { parser } from './parser.js';
|
|
4
|
+
|
|
5
|
+
// An MDX component is a JSX element: the same rule as `@markuplint/jsx-parser`, which names it a
|
|
6
|
+
// component unless it starts with a lowercase ASCII letter (a `-` makes it an intrinsic element).
|
|
7
|
+
describe('isComponentName', () => {
|
|
8
|
+
test.each(['Foo', 'RouterLink', 'Tabs.Root', 'ns.Item', 'motion.div', '_Layout', '$Ui', 'Ärger', 'Éa'])(
|
|
9
|
+
'"%s" is a component name',
|
|
10
|
+
name => {
|
|
11
|
+
expect(parser.isComponentName(name)).toBe(true);
|
|
12
|
+
},
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
test.each([
|
|
16
|
+
'div',
|
|
17
|
+
'svg',
|
|
18
|
+
'foreignObject',
|
|
19
|
+
'x-foo',
|
|
20
|
+
'my-widget.v2',
|
|
21
|
+
'annotation-xml',
|
|
22
|
+
'Foo-bar',
|
|
23
|
+
'#jsx-fragment',
|
|
24
|
+
'',
|
|
25
|
+
])('"%s" is not a component name', name => {
|
|
26
|
+
expect(parser.isComponentName(name)).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
});
|