@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 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 `spec` option to your [configuration](https://markuplint.dev/configuration/#properties/parser).
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-parser/spec"
25
+ "\\.html$": "@markuplint/alpine-spec"
26
26
  }
27
27
  }
28
28
  ```
package/lib/parser.d.ts CHANGED
@@ -1,31 +1,27 @@
1
- import type { Token } from '@markuplint/parser-utils';
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
- * Recognizes Alpine.js directives such as `x-data`, `x-bind`, `x-on`, and
7
- * `x-transition`, and classifies them as either directives (opaque to linting)
8
- * or attribute bindings (with potential standard attribute names for validation).
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
- * Visits an attribute token and applies Alpine.js-specific classification.
13
- *
14
- * Determines whether the attribute is an Alpine.js directive, a dynamic
15
- * binding (e.g., `:class`, `x-bind:href`), or an event listener shorthand
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 raw attribute token containing text and position information
21
- * @param options - Controls quoting behavior, value types, and the initial parser state
22
- * @returns The attribute AST node enriched with Alpine.js directive metadata
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
- visitAttr(token: Token, options: Parameters<HtmlParser['visitAttr']>[1]): (import("packages/@markuplint/ml-ast/lib/types.js").MLASTHTMLAttr & {
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
- * Recognizes Alpine.js directives such as `x-data`, `x-bind`, `x-on`, and
6
- * `x-transition`, and classifies them as either directives (opaque to linting)
7
- * or attribute bindings (with potential standard attribute names for validation).
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
- * Visits an attribute token and applies Alpine.js-specific classification.
12
- *
13
- * Determines whether the attribute is an Alpine.js directive, a dynamic
14
- * binding (e.g., `:class`, `x-bind:href`), or an event listener shorthand
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 raw attribute token containing text and position information
20
- * @param options - Controls quoting behavior, value types, and the initial parser state
21
- * @returns The attribute AST node enriched with Alpine.js directive metadata
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
- visitAttr(token, options) {
24
- const attr = super.visitAttr(token, options);
25
- if (attr.type === 'spread') {
26
- return attr;
27
- }
28
- const name = attr.name.raw;
29
- switch (name) {
30
- /**
31
- * @see https://alpinejs.dev/directives/data
32
- */
33
- case 'x-data': {
34
- return {
35
- ...attr,
36
- isDirective: true,
37
- };
38
- }
39
- /**
40
- * @see https://alpinejs.dev/directives/init
41
- */
42
- case 'x-init': {
43
- return {
44
- ...attr,
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
- return attr;
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": "4.6.23",
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": "4.6.23"
27
+ "@markuplint/html-parser": "5.0.0-alpha.0"
29
28
  },
30
29
  "devDependencies": {
31
- "@markuplint/ml-spec": "4.10.2",
32
- "@markuplint/parser-utils": "4.8.11"
30
+ "@markuplint/parser-utils": "5.0.0-alpha.0"
33
31
  },
34
- "gitHead": "193ee7c1262bbed95424e38efdf1a8e56ff049f4"
32
+ "gitHead": "13dcfc84ec83d87360c720e253383b60767e1b56"
35
33
  }