@markuplint/astro-parser 4.6.23 → 5.0.0-alpha.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/ARCHITECTURE.ja.md +2 -2
- package/ARCHITECTURE.md +2 -2
- package/CHANGELOG.md +24 -0
- package/lib/detect-block-behavior.d.ts +8 -0
- package/lib/detect-block-behavior.js +8 -0
- package/lib/parser.d.ts +8 -16
- package/lib/parser.js +33 -51
- package/package.json +8 -5
package/ARCHITECTURE.ja.md
CHANGED
|
@@ -109,9 +109,9 @@ Astro の式(`{expression}`)は Astro AST で `type: 'expression'` ノード
|
|
|
109
109
|
|
|
110
110
|
式が HTML 要素を含む場合(例: `{list.map(item => <li>{item}</li>)}`)、パーサーは複数のノードに分割します:
|
|
111
111
|
|
|
112
|
-
1. **開始式フラグメント**: `{list.map(item => ` — 子ノードを含む MustacheTag psblock
|
|
112
|
+
1. **開始式フラグメント**: `{list.map(item => ` — 子ノードを含む MustacheTag psblock。式が `.map()` または `.filter()` 呼び出しを含む場合(`detectBlockBehavior()` で検出)、開始フラグメントにはそれぞれ `blockBehavior: { type: 'each' }` または `{ type: 'if' }` が設定される
|
|
113
113
|
2. **ネストされた HTML 要素**: `<li>{item}</li>` — 通常の要素として処理
|
|
114
|
-
3. **終了式フラグメント**: `)}` — `isFragment: false` の別の MustacheTag psblock
|
|
114
|
+
3. **終了式フラグメント**: `)}` — `isFragment: false` の別の MustacheTag psblock。開始フラグメントに `blockBehavior` がある場合、終了フラグメントには `blockBehavior: { type: 'end' }` が設定される
|
|
115
115
|
|
|
116
116
|
分割ロジックは式の children 配列で `firstChild !== lastChild` かどうかを確認します。該当する場合:
|
|
117
117
|
|
package/ARCHITECTURE.md
CHANGED
|
@@ -109,9 +109,9 @@ A simple expression like `{name}` has a single text child. The entire expression
|
|
|
109
109
|
|
|
110
110
|
When an expression contains HTML elements (e.g., `{list.map(item => <li>{item}</li>)}`), the parser splits it into multiple nodes:
|
|
111
111
|
|
|
112
|
-
1. **Opening expression fragment**: `{list.map(item => ` — a MustacheTag psblock containing the child nodes
|
|
112
|
+
1. **Opening expression fragment**: `{list.map(item => ` — a MustacheTag psblock containing the child nodes. If the expression contains a `.map()` or `.filter()` call (detected by `detectBlockBehavior()`), the opening fragment receives `blockBehavior: { type: 'each' }` or `{ type: 'if' }` respectively
|
|
113
113
|
2. **Nested HTML elements**: `<li>{item}</li>` — processed as normal elements
|
|
114
|
-
3. **Closing expression fragment**: `)}` — a separate MustacheTag psblock with `isFragment: false`
|
|
114
|
+
3. **Closing expression fragment**: `)}` — a separate MustacheTag psblock with `isFragment: false`. If the opening fragment had a `blockBehavior`, the closing fragment receives `blockBehavior: { type: 'end' }`
|
|
115
115
|
|
|
116
116
|
The splitting logic checks whether `firstChild !== lastChild` in the expression's children array. If so:
|
|
117
117
|
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
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-alpha.0](https://github.com/markuplint/markuplint/compare/v4.14.1...v5.0.0-alpha.0) (2026-02-20)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **ml-core:** improve detection of namespace ([5b507ad](https://github.com/markuplint/markuplint/commit/5b507ad7c19c5015b8ce587845d901e31dfa6518))
|
|
11
|
+
|
|
12
|
+
- refactor(astro-parser)!: update for simplified AST token properties ([4c05de1](https://github.com/markuplint/markuplint/commit/4c05de151d30233a8d4a184c4cb70c26de19b36b))
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- **astro-parser:** support loop blocks ([ebe2eb6](https://github.com/markuplint/markuplint/commit/ebe2eb6b85aa32ff3f29964e333d058afe99d18b))
|
|
17
|
+
|
|
18
|
+
### BREAKING CHANGES
|
|
19
|
+
|
|
20
|
+
- Adapt to renamed token properties and remove
|
|
21
|
+
selfClosingSolidus test.
|
|
22
|
+
|
|
23
|
+
* Token property access: startOffset -> offset, startLine -> line,
|
|
24
|
+
startCol -> col
|
|
25
|
+
* Replace selfClosingSolidus check with tagCloseChar
|
|
26
|
+
* Remove selfClosingSolidus test case
|
|
27
|
+
|
|
28
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
29
|
+
|
|
6
30
|
## [4.6.23](https://github.com/markuplint/markuplint/compare/@markuplint/astro-parser@4.6.22...@markuplint/astro-parser@4.6.23) (2026-02-10)
|
|
7
31
|
|
|
8
32
|
**Note:** Version bump only for package @markuplint/astro-parser
|
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
import type { MLASTBlockBehavior } from '@markuplint/ml-ast';
|
|
2
|
+
/**
|
|
3
|
+
* Detects the block behavior of an Astro expression by inspecting its raw
|
|
4
|
+
* source for `.map()` or `.filter()` array method calls. Maps `.map()` to
|
|
5
|
+
* `'each'` (iteration) and `.filter()` to `'if'` (conditional filtering).
|
|
6
|
+
*
|
|
7
|
+
* @param raw - The raw source text of the expression to analyze
|
|
8
|
+
* @returns The detected block behavior, or `null` if no recognized pattern is found
|
|
9
|
+
*/
|
|
2
10
|
export declare function detectBlockBehavior(raw: string): MLASTBlockBehavior | null;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects the block behavior of an Astro expression by inspecting its raw
|
|
3
|
+
* source for `.map()` or `.filter()` array method calls. Maps `.map()` to
|
|
4
|
+
* `'each'` (iteration) and `.filter()` to `'if'` (conditional filtering).
|
|
5
|
+
*
|
|
6
|
+
* @param raw - The raw source text of the expression to analyze
|
|
7
|
+
* @returns The detected block behavior, or `null` if no recognized pattern is found
|
|
8
|
+
*/
|
|
1
9
|
export function detectBlockBehavior(raw) {
|
|
2
10
|
const re = /\.+\s*(?<type>map|filter)\s*\((?:function\s*\(.[^\n\r{\u2028\u2029]*\{.*return\s*$|.+=>\s*\(?\s*)/;
|
|
3
11
|
const match = raw.match(re);
|
package/lib/parser.d.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import type { Node } from './astro-parser.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { MLASTNodeTreeItem, MLASTParentNode } from '@markuplint/ml-ast';
|
|
3
3
|
import type { ChildToken, Token } from '@markuplint/parser-utils';
|
|
4
4
|
import { Parser } from '@markuplint/parser-utils';
|
|
5
|
-
type State = {
|
|
6
|
-
scopeNS: string;
|
|
7
|
-
};
|
|
8
5
|
/**
|
|
9
6
|
* Parser implementation for Astro component templates.
|
|
10
7
|
* Extends the base Parser to handle Astro-specific syntax including frontmatter blocks,
|
|
11
8
|
* expression containers (`{}`), component/element/fragment types, Astro directives
|
|
12
|
-
* (e.g., `class:list`, `set:html`), shorthand attributes
|
|
13
|
-
* element resolution (XHTML vs SVG).
|
|
9
|
+
* (e.g., `class:list`, `set:html`), and shorthand attributes.
|
|
14
10
|
*/
|
|
15
|
-
declare class AstroParser extends Parser<Node
|
|
16
|
-
#private;
|
|
11
|
+
declare class AstroParser extends Parser<Node> {
|
|
17
12
|
constructor();
|
|
18
13
|
tokenize(): {
|
|
19
14
|
ast: Node[];
|
|
@@ -22,7 +17,7 @@ declare class AstroParser extends Parser<Node, State> {
|
|
|
22
17
|
/**
|
|
23
18
|
* Converts an Astro AST node into markuplint node tree items.
|
|
24
19
|
* Handles frontmatter, doctype, text, comment, component/element/fragment,
|
|
25
|
-
* and expression nodes.
|
|
20
|
+
* and expression nodes.
|
|
26
21
|
*
|
|
27
22
|
* @param originNode - The Astro AST node to convert
|
|
28
23
|
* @param parentNode - The parent node in the markuplint tree, or null for root nodes
|
|
@@ -34,7 +29,7 @@ declare class AstroParser extends Parser<Node, State> {
|
|
|
34
29
|
/**
|
|
35
30
|
* Visits an element token by first parsing the raw HTML fragment to extract
|
|
36
31
|
* the start tag, then delegating to the base visitElement with Astro-specific
|
|
37
|
-
* options including
|
|
32
|
+
* options including nameless fragment support.
|
|
38
33
|
*
|
|
39
34
|
* @param token - The child token representing the element
|
|
40
35
|
* @param childNodes - The child Astro AST nodes within the element
|
|
@@ -81,12 +76,9 @@ declare class AstroParser extends Parser<Node, State> {
|
|
|
81
76
|
isDuplicatable: boolean;
|
|
82
77
|
uuid: string;
|
|
83
78
|
raw: string;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
endLine: number;
|
|
88
|
-
startCol: number;
|
|
89
|
-
endCol: number;
|
|
79
|
+
offset: number;
|
|
80
|
+
line: number;
|
|
81
|
+
col: number;
|
|
90
82
|
__rightText?: string;
|
|
91
83
|
};
|
|
92
84
|
/**
|
package/lib/parser.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
-
};
|
|
6
|
-
var _AstroParser_instances, _AstroParser_updateScopeNS;
|
|
7
1
|
import { AttrState, Parser, ParserError } from '@markuplint/parser-utils';
|
|
8
2
|
import { astroParse } from './astro-parser.js';
|
|
3
|
+
import { detectBlockBehavior } from './detect-block-behavior.js';
|
|
9
4
|
/**
|
|
10
5
|
* Parser implementation for Astro component templates.
|
|
11
6
|
* Extends the base Parser to handle Astro-specific syntax including frontmatter blocks,
|
|
12
7
|
* expression containers (`{}`), component/element/fragment types, Astro directives
|
|
13
|
-
* (e.g., `class:list`, `set:html`), shorthand attributes
|
|
14
|
-
* element resolution (XHTML vs SVG).
|
|
8
|
+
* (e.g., `class:list`, `set:html`), and shorthand attributes.
|
|
15
9
|
*/
|
|
16
10
|
class AstroParser extends Parser {
|
|
17
11
|
constructor() {
|
|
@@ -19,10 +13,7 @@ class AstroParser extends Parser {
|
|
|
19
13
|
endTagType: 'xml',
|
|
20
14
|
selfCloseType: 'html+xml',
|
|
21
15
|
tagNameCaseSensitive: true,
|
|
22
|
-
}, {
|
|
23
|
-
scopeNS: 'http://www.w3.org/1999/xhtml',
|
|
24
16
|
});
|
|
25
|
-
_AstroParser_instances.add(this);
|
|
26
17
|
}
|
|
27
18
|
tokenize() {
|
|
28
19
|
return {
|
|
@@ -33,7 +24,7 @@ class AstroParser extends Parser {
|
|
|
33
24
|
/**
|
|
34
25
|
* Converts an Astro AST node into markuplint node tree items.
|
|
35
26
|
* Handles frontmatter, doctype, text, comment, component/element/fragment,
|
|
36
|
-
* and expression nodes.
|
|
27
|
+
* and expression nodes.
|
|
37
28
|
*
|
|
38
29
|
* @param originNode - The Astro AST node to convert
|
|
39
30
|
* @param parentNode - The parent node in the markuplint tree, or null for root nodes
|
|
@@ -46,10 +37,9 @@ class AstroParser extends Parser {
|
|
|
46
37
|
if (!originNode.position) {
|
|
47
38
|
throw new TypeError("Node doesn't have position");
|
|
48
39
|
}
|
|
49
|
-
const
|
|
40
|
+
const offset = originNode.position.start.offset;
|
|
50
41
|
const endOffset = originNode.position.end?.offset;
|
|
51
|
-
const token = this.sliceFragment(
|
|
52
|
-
__classPrivateFieldGet(this, _AstroParser_instances, "m", _AstroParser_updateScopeNS).call(this, originNode, parentNode);
|
|
42
|
+
const token = this.sliceFragment(offset, endOffset);
|
|
53
43
|
switch (originNode.type) {
|
|
54
44
|
case 'frontmatter': {
|
|
55
45
|
return this.visitPsBlock({
|
|
@@ -102,34 +92,42 @@ class AstroParser extends Parser {
|
|
|
102
92
|
const firstChild = originNode.children.at(0);
|
|
103
93
|
const lastChild = originNode.children.at(-1);
|
|
104
94
|
let startExpressionRaw = token.raw;
|
|
105
|
-
let startExpressionStartLine = token.
|
|
106
|
-
let startExpressionStartCol = token.
|
|
95
|
+
let startExpressionStartLine = token.line;
|
|
96
|
+
let startExpressionStartCol = token.col;
|
|
107
97
|
const nodes = [];
|
|
98
|
+
let closeExpressionLocation = null;
|
|
108
99
|
if (firstChild && lastChild && firstChild !== lastChild) {
|
|
109
|
-
const startExpressionEndOffset = firstChild.position?.end?.offset ?? endOffset ??
|
|
110
|
-
const startExpressionLocation = this.sliceFragment(
|
|
100
|
+
const startExpressionEndOffset = firstChild.position?.end?.offset ?? endOffset ?? offset;
|
|
101
|
+
const startExpressionLocation = this.sliceFragment(offset, startExpressionEndOffset);
|
|
111
102
|
startExpressionRaw = startExpressionLocation.raw;
|
|
112
|
-
startExpressionStartLine = startExpressionLocation.
|
|
113
|
-
startExpressionStartCol = startExpressionLocation.
|
|
114
|
-
|
|
115
|
-
nodes.push(...this.visitPsBlock({
|
|
116
|
-
...closeExpressionLocation,
|
|
117
|
-
depth,
|
|
118
|
-
parentNode,
|
|
119
|
-
nodeName: 'MustacheTag',
|
|
120
|
-
isFragment: false,
|
|
121
|
-
}));
|
|
103
|
+
startExpressionStartLine = startExpressionLocation.line;
|
|
104
|
+
startExpressionStartCol = startExpressionLocation.col;
|
|
105
|
+
closeExpressionLocation = this.sliceFragment(lastChild.position?.start.offset ?? offset, endOffset);
|
|
122
106
|
}
|
|
107
|
+
const blockBehavior = detectBlockBehavior(startExpressionRaw);
|
|
123
108
|
nodes.push(...this.visitPsBlock({
|
|
124
109
|
raw: startExpressionRaw,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
110
|
+
offset,
|
|
111
|
+
line: startExpressionStartLine,
|
|
112
|
+
col: startExpressionStartCol,
|
|
128
113
|
depth,
|
|
129
114
|
parentNode,
|
|
130
115
|
nodeName: 'MustacheTag',
|
|
131
116
|
isFragment: true,
|
|
132
|
-
}, originNode.children)
|
|
117
|
+
}, originNode.children, blockBehavior), ...(closeExpressionLocation
|
|
118
|
+
? this.visitPsBlock({
|
|
119
|
+
...closeExpressionLocation,
|
|
120
|
+
depth,
|
|
121
|
+
parentNode,
|
|
122
|
+
nodeName: 'MustacheTag',
|
|
123
|
+
isFragment: false,
|
|
124
|
+
}, [], blockBehavior
|
|
125
|
+
? {
|
|
126
|
+
type: 'end',
|
|
127
|
+
expression: closeExpressionLocation.raw,
|
|
128
|
+
}
|
|
129
|
+
: undefined)
|
|
130
|
+
: []));
|
|
133
131
|
return nodes;
|
|
134
132
|
}
|
|
135
133
|
default: {
|
|
@@ -145,7 +143,7 @@ class AstroParser extends Parser {
|
|
|
145
143
|
/**
|
|
146
144
|
* Visits an element token by first parsing the raw HTML fragment to extract
|
|
147
145
|
* the start tag, then delegating to the base visitElement with Astro-specific
|
|
148
|
-
* options including
|
|
146
|
+
* options including nameless fragment support.
|
|
149
147
|
*
|
|
150
148
|
* @param token - The child token representing the element
|
|
151
149
|
* @param childNodes - The child Astro AST nodes within the element
|
|
@@ -165,11 +163,8 @@ class AstroParser extends Parser {
|
|
|
165
163
|
return super.visitElement(startTagNode, childNodes, {
|
|
166
164
|
// https://docs.astro.build/en/basics/astro-syntax/#fragments
|
|
167
165
|
namelessFragment: true,
|
|
168
|
-
overwriteProps: {
|
|
169
|
-
namespace: this.state.scopeNS,
|
|
170
|
-
},
|
|
171
166
|
createEndTagToken: () => {
|
|
172
|
-
if (startTagNode.
|
|
167
|
+
if (startTagNode.tagCloseChar.startsWith('/')) {
|
|
173
168
|
return null;
|
|
174
169
|
}
|
|
175
170
|
const endTagNode = parsedNodes.at(-1);
|
|
@@ -263,17 +258,4 @@ class AstroParser extends Parser {
|
|
|
263
258
|
return super.detectElementType(nodeName, /^[A-Z]/);
|
|
264
259
|
}
|
|
265
260
|
}
|
|
266
|
-
_AstroParser_instances = new WeakSet(), _AstroParser_updateScopeNS = function _AstroParser_updateScopeNS(
|
|
267
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
268
|
-
originNode, parentNode) {
|
|
269
|
-
const parentNS = this.state.scopeNS;
|
|
270
|
-
if (parentNS === 'http://www.w3.org/1999/xhtml' &&
|
|
271
|
-
originNode.type === 'element' &&
|
|
272
|
-
originNode.name?.toLowerCase() === 'svg') {
|
|
273
|
-
this.state.scopeNS = 'http://www.w3.org/2000/svg';
|
|
274
|
-
}
|
|
275
|
-
else if (parentNS === 'http://www.w3.org/2000/svg' && parentNode && parentNode.nodeName === 'foreignObject') {
|
|
276
|
-
this.state.scopeNS = 'http://www.w3.org/1999/xhtml';
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
261
|
export const parser = new AstroParser();
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/astro-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-alpha.0",
|
|
4
4
|
"description": "astro parser for markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22"
|
|
10
|
+
},
|
|
8
11
|
"type": "module",
|
|
9
12
|
"exports": {
|
|
10
13
|
".": {
|
|
@@ -21,12 +24,12 @@
|
|
|
21
24
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
22
25
|
},
|
|
23
26
|
"dependencies": {
|
|
24
|
-
"@markuplint/ml-ast": "
|
|
25
|
-
"@markuplint/parser-utils": "
|
|
26
|
-
"astro-eslint-parser": "1.
|
|
27
|
+
"@markuplint/ml-ast": "5.0.0-alpha.0",
|
|
28
|
+
"@markuplint/parser-utils": "5.0.0-alpha.0",
|
|
29
|
+
"astro-eslint-parser": "1.3.0"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@astrojs/compiler": "2.13.1"
|
|
30
33
|
},
|
|
31
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "13dcfc84ec83d87360c720e253383b60767e1b56"
|
|
32
35
|
}
|