@markuplint/alpine-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/CHANGELOG.md +7 -0
- package/README.md +2 -2
- package/lib/parser.d.ts +15 -19
- package/lib/parser.js +37 -195
- package/package.json +7 -9
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
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
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- **alpine-parser:** support loop blocks ([d92c53c](https://github.com/markuplint/markuplint/commit/d92c53ce7337a2b39f78cbc43edbe1aba2232bae))
|
|
11
|
+
- delete htmx-parser, simplify alpine-parser, add migration guide and tests ([f8dbb09](https://github.com/markuplint/markuplint/commit/f8dbb090707d8cfbf3d859a9b868b2087064f89b))
|
|
12
|
+
|
|
6
13
|
## [4.6.23](https://github.com/markuplint/markuplint/compare/@markuplint/alpine-parser@4.6.22...@markuplint/alpine-parser@4.6.23) (2026-02-10)
|
|
7
14
|
|
|
8
15
|
**Note:** Version bump only for package @markuplint/alpine-parser
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ $ yarn add -D @markuplint/alpine-parser
|
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
|
-
Add `parser` and `
|
|
17
|
+
Add `parser` and `specs` option to your [configuration](https://markuplint.dev/configuration/#properties/parser).
|
|
18
18
|
|
|
19
19
|
```json
|
|
20
20
|
{
|
|
@@ -22,7 +22,7 @@ Add `parser` and `spec` option to your [configuration](https://markuplint.dev/co
|
|
|
22
22
|
"\\.html$": "@markuplint/alpine-parser"
|
|
23
23
|
},
|
|
24
24
|
"specs": {
|
|
25
|
-
"\\.html$": "@markuplint/alpine-
|
|
25
|
+
"\\.html$": "@markuplint/alpine-spec"
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
```
|
package/lib/parser.d.ts
CHANGED
|
@@ -1,31 +1,27 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MLASTNodeTreeItem } from '@markuplint/ml-ast';
|
|
2
2
|
import { HtmlParser } from '@markuplint/html-parser';
|
|
3
3
|
/**
|
|
4
4
|
* Parser for Alpine.js templates that extends the standard HTML parser.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Converts `<template x-for="...">` elements into preprocessor-specific
|
|
7
|
+
* blocks so that markuplint understands the iteration semantics.
|
|
8
|
+
*
|
|
9
|
+
* Attribute-level directive resolution (x-bind, x-on, @, :, etc.) is
|
|
10
|
+
* handled declaratively via `directivePatterns` in `@markuplint/alpine-spec`.
|
|
9
11
|
*/
|
|
10
12
|
declare class AlpineParser extends HtmlParser {
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* (e.g., `@click`, `x-on:submit`), and returns the attribute with
|
|
17
|
-
* appropriate metadata such as `potentialName`, `isDirective`, and
|
|
18
|
-
* `isDynamicValue`.
|
|
14
|
+
* Overrides the base element visitor to convert `<template x-for="...">` elements
|
|
15
|
+
* into preprocessor-specific blocks with `blockBehavior: { type: 'each' }`.
|
|
16
|
+
* The matching closing tag receives `{ type: 'end' }`. Non-template elements
|
|
17
|
+
* and templates without `x-for` are passed through unchanged.
|
|
19
18
|
*
|
|
20
|
-
* @param token - The
|
|
21
|
-
* @param
|
|
22
|
-
* @
|
|
19
|
+
* @param token - The element token with tag metadata
|
|
20
|
+
* @param childNodes - The child AST nodes within the element
|
|
21
|
+
* @param options - Options forwarded to the base `visitElement`
|
|
22
|
+
* @returns An array of markuplint node tree items, with `x-for` templates replaced by psblock nodes
|
|
23
23
|
*/
|
|
24
|
-
|
|
25
|
-
__rightText?: string;
|
|
26
|
-
}) | (import("packages/@markuplint/ml-ast/lib/types.js").MLASTSpreadAttr & {
|
|
27
|
-
__rightText?: string;
|
|
28
|
-
});
|
|
24
|
+
visitElement(token: Parameters<HtmlParser['visitElement']>[0], childNodes: Parameters<HtmlParser['visitElement']>[1], options: Parameters<HtmlParser['visitElement']>[2]): readonly MLASTNodeTreeItem[];
|
|
29
25
|
}
|
|
30
26
|
/**
|
|
31
27
|
* Singleton Alpine.js parser instance for use by the markuplint engine.
|
package/lib/parser.js
CHANGED
|
@@ -2,207 +2,49 @@ import { HtmlParser } from '@markuplint/html-parser';
|
|
|
2
2
|
/**
|
|
3
3
|
* Parser for Alpine.js templates that extends the standard HTML parser.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Converts `<template x-for="...">` elements into preprocessor-specific
|
|
6
|
+
* blocks so that markuplint understands the iteration semantics.
|
|
7
|
+
*
|
|
8
|
+
* Attribute-level directive resolution (x-bind, x-on, @, :, etc.) is
|
|
9
|
+
* handled declaratively via `directivePatterns` in `@markuplint/alpine-spec`.
|
|
8
10
|
*/
|
|
9
11
|
class AlpineParser extends HtmlParser {
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* (e.g., `@click`, `x-on:submit`), and returns the attribute with
|
|
16
|
-
* appropriate metadata such as `potentialName`, `isDirective`, and
|
|
17
|
-
* `isDynamicValue`.
|
|
13
|
+
* Overrides the base element visitor to convert `<template x-for="...">` elements
|
|
14
|
+
* into preprocessor-specific blocks with `blockBehavior: { type: 'each' }`.
|
|
15
|
+
* The matching closing tag receives `{ type: 'end' }`. Non-template elements
|
|
16
|
+
* and templates without `x-for` are passed through unchanged.
|
|
18
17
|
*
|
|
19
|
-
* @param token - The
|
|
20
|
-
* @param
|
|
21
|
-
* @
|
|
18
|
+
* @param token - The element token with tag metadata
|
|
19
|
+
* @param childNodes - The child AST nodes within the element
|
|
20
|
+
* @param options - Options forwarded to the base `visitElement`
|
|
21
|
+
* @returns An array of markuplint node tree items, with `x-for` templates replaced by psblock nodes
|
|
22
22
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
isDirective: true,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* @see https://alpinejs.dev/directives/show
|
|
50
|
-
*/
|
|
51
|
-
case 'x-show': {
|
|
52
|
-
return {
|
|
53
|
-
...attr,
|
|
54
|
-
isDirective: true,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* @see https://alpinejs.dev/directives/text
|
|
59
|
-
*/
|
|
60
|
-
case 'x-text': {
|
|
61
|
-
return {
|
|
62
|
-
...attr,
|
|
63
|
-
isDirective: true,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* @see https://alpinejs.dev/directives/html
|
|
68
|
-
*/
|
|
69
|
-
case 'x-html': {
|
|
70
|
-
return {
|
|
71
|
-
...attr,
|
|
72
|
-
isDirective: true,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* {@link ./spec.ts} Treat as a normal attribute and allow only in template elements as defined in `spec`.
|
|
77
|
-
*
|
|
78
|
-
* @see https://alpinejs.dev/directives/model
|
|
79
|
-
*/
|
|
80
|
-
case 'x-model': {
|
|
81
|
-
return attr;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* @see https://alpinejs.dev/directives/modelable
|
|
85
|
-
*/
|
|
86
|
-
case 'x-modelable': {
|
|
87
|
-
return {
|
|
88
|
-
...attr,
|
|
89
|
-
isDirective: true,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* {@link ./spec.ts} Treat as a normal attribute and allow only in template elements as defined in `spec`.
|
|
94
|
-
*
|
|
95
|
-
* @see https://alpinejs.dev/directives/for
|
|
96
|
-
*/
|
|
97
|
-
case 'x-for': {
|
|
98
|
-
return attr;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* @see https://alpinejs.dev/directives/effect
|
|
102
|
-
*/
|
|
103
|
-
case 'x-effect': {
|
|
104
|
-
return {
|
|
105
|
-
...attr,
|
|
106
|
-
isDirective: true,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* @see https://alpinejs.dev/directives/ignore
|
|
111
|
-
*/
|
|
112
|
-
case 'x-ignore': {
|
|
113
|
-
return {
|
|
114
|
-
...attr,
|
|
115
|
-
valueType: 'boolean',
|
|
116
|
-
isDirective: true,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* @see https://alpinejs.dev/directives/ref
|
|
121
|
-
*/
|
|
122
|
-
case 'x-ref': {
|
|
123
|
-
return {
|
|
124
|
-
...attr,
|
|
125
|
-
isDirective: true,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* @see https://alpinejs.dev/directives/cloak
|
|
130
|
-
*/
|
|
131
|
-
case 'x-cloak': {
|
|
132
|
-
return {
|
|
133
|
-
...attr,
|
|
134
|
-
valueType: 'boolean',
|
|
135
|
-
isDirective: true,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* {@link ./spec.ts} Treat as a normal attribute and allow only in template elements as defined in `spec`.
|
|
140
|
-
*
|
|
141
|
-
* @see https://alpinejs.dev/directives/teleport
|
|
142
|
-
*/
|
|
143
|
-
case 'x-teleport': {
|
|
144
|
-
return attr;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* {@link ./spec.ts} Treat as a normal attribute and allow only in template elements as defined in `spec`.
|
|
148
|
-
*
|
|
149
|
-
* @see https://alpinejs.dev/directives/if
|
|
150
|
-
*/
|
|
151
|
-
case 'x-if': {
|
|
152
|
-
return attr;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* @see https://alpinejs.dev/directives/id
|
|
156
|
-
*/
|
|
157
|
-
case 'x-id': {
|
|
158
|
-
return {
|
|
159
|
-
...attr,
|
|
160
|
-
isDirective: true,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* @see https://alpinejs.dev/directives/bind
|
|
166
|
-
*/
|
|
167
|
-
if (name.startsWith('x-bind:') || name.startsWith(':')) {
|
|
168
|
-
const potentialName = (attr.name.raw.match(/^(x-bind:|:)([^.]+)(?:\.([^.]+))?$/i) ?? [])[2];
|
|
169
|
-
if (!potentialName) {
|
|
170
|
-
return attr;
|
|
171
|
-
}
|
|
172
|
-
return {
|
|
173
|
-
...attr,
|
|
174
|
-
potentialName,
|
|
175
|
-
valueType: 'code',
|
|
176
|
-
isDuplicatable: ['class', 'style'].includes(potentialName),
|
|
177
|
-
isDynamicValue: true,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* @see https://alpinejs.dev/directives/on
|
|
182
|
-
*/
|
|
183
|
-
if (name.startsWith('x-on:') || name.startsWith('@')) {
|
|
184
|
-
const potentialName = (attr.name.raw.match(/^(x-on:|@)([^.]+)(\..+)?$/i) ?? [])[2];
|
|
185
|
-
if (!potentialName) {
|
|
186
|
-
return attr;
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
...attr,
|
|
190
|
-
potentialName: `on${potentialName.toLowerCase()}`,
|
|
191
|
-
// TODO: Postpone due to inability to distinguish between custom and native events
|
|
192
|
-
isDirective: true,
|
|
193
|
-
isDynamicValue: true,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* @see https://alpinejs.dev/directives/transition
|
|
198
|
-
*/
|
|
199
|
-
if (/^x-transition(?:$|:|\.)/.test(name)) {
|
|
200
|
-
return {
|
|
201
|
-
...attr,
|
|
202
|
-
isDirective: true,
|
|
23
|
+
visitElement(token, childNodes = [], options) {
|
|
24
|
+
return super.visitElement(token, childNodes, options).map(node => {
|
|
25
|
+
if (node.type !== 'starttag' && node.type !== 'endtag') {
|
|
26
|
+
return node;
|
|
27
|
+
}
|
|
28
|
+
if (node.nodeName.toLowerCase() !== 'template') {
|
|
29
|
+
return node;
|
|
30
|
+
}
|
|
31
|
+
const attrs = node.type === 'starttag' ? node.attributes : node.pairNode.attributes;
|
|
32
|
+
if (!attrs.some(attr => attr.nodeName.toLowerCase() === 'x-for')) {
|
|
33
|
+
return node;
|
|
34
|
+
}
|
|
35
|
+
const forBlock = {
|
|
36
|
+
isFragment: false,
|
|
37
|
+
childNodes: [],
|
|
38
|
+
...node,
|
|
39
|
+
type: 'psblock',
|
|
40
|
+
blockBehavior: {
|
|
41
|
+
type: node.type === 'starttag' ? 'each' : 'end',
|
|
42
|
+
expression: node.raw,
|
|
43
|
+
},
|
|
44
|
+
isBogus: false,
|
|
203
45
|
};
|
|
204
|
-
|
|
205
|
-
|
|
46
|
+
return forBlock;
|
|
47
|
+
});
|
|
206
48
|
}
|
|
207
49
|
}
|
|
208
50
|
/**
|
package/package.json
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/alpine-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-alpha.0",
|
|
4
4
|
"description": "Alpine.js 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
|
".": {
|
|
11
14
|
"import": "./lib/index.js",
|
|
12
15
|
"types": "./lib/index.d.ts"
|
|
13
|
-
},
|
|
14
|
-
"./spec": {
|
|
15
|
-
"import": "./lib/spec.js",
|
|
16
|
-
"types": "./lib/spec.d.ts"
|
|
17
16
|
}
|
|
18
17
|
},
|
|
19
18
|
"publishConfig": {
|
|
@@ -25,11 +24,10 @@
|
|
|
25
24
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
26
25
|
},
|
|
27
26
|
"dependencies": {
|
|
28
|
-
"@markuplint/html-parser": "
|
|
27
|
+
"@markuplint/html-parser": "5.0.0-alpha.0"
|
|
29
28
|
},
|
|
30
29
|
"devDependencies": {
|
|
31
|
-
"@markuplint/
|
|
32
|
-
"@markuplint/parser-utils": "4.8.11"
|
|
30
|
+
"@markuplint/parser-utils": "5.0.0-alpha.0"
|
|
33
31
|
},
|
|
34
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "13dcfc84ec83d87360c720e253383b60767e1b56"
|
|
35
33
|
}
|