eslint-plugin-templ 0.0.1 → 0.0.2
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/README.md +2 -2
- package/dist/configs/recommended.d.ts +57 -0
- package/dist/configs/recommended.d.ts.map +1 -0
- package/dist/configs/recommended.js +49 -0
- package/dist/configs/recommended.js.map +1 -0
- package/dist/html-source-code.d.ts +65 -39
- package/dist/html-source-code.d.ts.map +1 -1
- package/dist/html-source-code.js +112 -60
- package/dist/html-source-code.js.map +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/parse-for-eslint.d.ts.map +1 -1
- package/dist/parse-for-eslint.js +163 -25
- package/dist/parse-for-eslint.js.map +1 -1
- package/dist/run-templ-ast-to-json-binary.js +5 -3
- package/dist/run-templ-ast-to-json-binary.js.map +1 -1
- package/dist/templ-ast-to-eslint-ast.d.ts +16 -1
- package/dist/templ-ast-to-eslint-ast.d.ts.map +1 -1
- package/dist/templ-ast-to-eslint-ast.js +475 -117
- package/dist/templ-ast-to-eslint-ast.js.map +1 -1
- package/dist/templ-ast.d.ts +1564 -384
- package/dist/templ-ast.d.ts.map +1 -1
- package/dist/templ-ast.js +146 -48
- package/dist/templ-ast.js.map +1 -1
- package/dist/templ-language.d.ts +16 -3
- package/dist/templ-language.d.ts.map +1 -1
- package/dist/templ-language.js +9 -3
- package/dist/templ-language.js.map +1 -1
- package/package.json +27 -23
|
@@ -2,18 +2,39 @@
|
|
|
2
2
|
* Conversion utilities for transforming templ AST to ESLint-compatible HTML AST
|
|
3
3
|
*/
|
|
4
4
|
import { NodeTypes } from "es-html-parser";
|
|
5
|
+
/**
|
|
6
|
+
* Converts a templ AST to an ESLint-compatible HTML AST (DocumentNode).
|
|
7
|
+
*
|
|
8
|
+
* This function is a pure converter — it consumes only the templ AST and does
|
|
9
|
+
* not accept or use source text. All information needed for the conversion must
|
|
10
|
+
* be present in the AST. If something is missing, the fix belongs in the templ
|
|
11
|
+
* parser (github.com/AdamVig/templ), not here.
|
|
12
|
+
*/
|
|
5
13
|
export function convertToESLintAST(templAST) {
|
|
6
14
|
const children = [];
|
|
7
15
|
for (const node of templAST) {
|
|
8
|
-
// Only process HTMLTemplate nodes which have Children
|
|
9
|
-
// Skip FileGoExpression, CSSTemplate, and ScriptTemplate as they don't contain HTML
|
|
10
|
-
if (
|
|
11
|
-
|
|
16
|
+
// Only process HTMLTemplate nodes which have Children.
|
|
17
|
+
// Skip FileGoExpression, CSSTemplate, and ScriptTemplate as they don't contain HTML.
|
|
18
|
+
if (node.type !== "HTMLTemplate") {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
for (const child of node.Children) {
|
|
22
|
+
try {
|
|
12
23
|
const converted = convertChild(child);
|
|
13
24
|
if (converted) {
|
|
14
25
|
children.push(converted);
|
|
26
|
+
// Only Element nodes have TrailingSpace
|
|
27
|
+
if (child.type === "Element" && child.TrailingSpace) {
|
|
28
|
+
const trailingSpace = createTrailingSpaceNode(child);
|
|
29
|
+
if (trailingSpace) {
|
|
30
|
+
children.push(trailingSpace);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
15
33
|
}
|
|
16
34
|
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new TemplASTConversionError(`Failed to convert Templ AST node to ESLint AST${error instanceof Error ? `: ${error.message}` : ""}`, error instanceof TemplASTConversionError ? error.node : child);
|
|
37
|
+
}
|
|
17
38
|
}
|
|
18
39
|
}
|
|
19
40
|
// Calculate overall document range
|
|
@@ -33,94 +54,341 @@ export function convertToESLintAST(templAST) {
|
|
|
33
54
|
};
|
|
34
55
|
}
|
|
35
56
|
function convertChild(child) {
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
try {
|
|
58
|
+
switch (child.type) {
|
|
59
|
+
case "ScriptElement":
|
|
60
|
+
return convertScriptElement(child);
|
|
61
|
+
case "RawElement":
|
|
62
|
+
return convertRawElement(child);
|
|
63
|
+
case "Element":
|
|
64
|
+
return convertElement(child);
|
|
65
|
+
case "Whitespace":
|
|
66
|
+
case "Text":
|
|
67
|
+
return {
|
|
68
|
+
type: NodeTypes.Text,
|
|
69
|
+
value: child.Value,
|
|
70
|
+
range: convertRange(child.Range),
|
|
71
|
+
loc: convertLoc(child.Range),
|
|
72
|
+
parts: [],
|
|
73
|
+
};
|
|
74
|
+
case "DocType":
|
|
75
|
+
return convertDocType(child);
|
|
76
|
+
case "StringExpression":
|
|
77
|
+
return {
|
|
78
|
+
type: NodeTypes.Text,
|
|
79
|
+
value: child.Expression.Value,
|
|
80
|
+
range: convertRange(child.Expression.Range),
|
|
81
|
+
loc: convertLoc(child.Expression.Range),
|
|
82
|
+
parts: [],
|
|
83
|
+
};
|
|
84
|
+
default:
|
|
85
|
+
// Ignore Go expressions, call expressions, control flow, etc.
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
38
88
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
89
|
+
catch (error) {
|
|
90
|
+
throw new TemplASTConversionError(`Failed to convert ${child.type} node${error instanceof Error ? `: ${error.message}` : ""}`, error instanceof TemplASTConversionError ? error.node : child);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Creates a text node for an element's trailing space.
|
|
95
|
+
* Elements have a TrailingSpace field that contains whitespace after the closing tag.
|
|
96
|
+
* This whitespace should be represented as a text node sibling.
|
|
97
|
+
*/
|
|
98
|
+
function createTrailingSpaceNode(node) {
|
|
99
|
+
if (!node.TrailingSpace) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const elementEnd = getElementEndPosition(node);
|
|
103
|
+
// The trailing space starts right after the serialized element ends.
|
|
104
|
+
const startIndex = elementEnd.Index;
|
|
105
|
+
const endIndex = startIndex + node.TrailingSpace.length;
|
|
106
|
+
// Calculate the end position accounting for newlines in the trailing space
|
|
107
|
+
const lines = node.TrailingSpace.split("\n");
|
|
108
|
+
const endLine = elementEnd.Line + lines.length;
|
|
109
|
+
const endCol = lines.length > 1
|
|
110
|
+
? lines[lines.length - 1].length
|
|
111
|
+
: elementEnd.Col + node.TrailingSpace.length;
|
|
112
|
+
return {
|
|
113
|
+
type: NodeTypes.Text,
|
|
114
|
+
value: node.TrailingSpace,
|
|
115
|
+
range: [startIndex, endIndex],
|
|
116
|
+
loc: {
|
|
117
|
+
start: convertPosition(elementEnd),
|
|
118
|
+
end: {
|
|
119
|
+
line: endLine,
|
|
120
|
+
column: endCol,
|
|
49
121
|
},
|
|
50
|
-
|
|
51
|
-
|
|
122
|
+
},
|
|
123
|
+
parts: [],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function getElementEndPosition(node) {
|
|
127
|
+
return node.CloseTagRange ? node.CloseTagRange.To : node.OpenTagEndRange.To;
|
|
128
|
+
}
|
|
129
|
+
function getElementRange(node) {
|
|
130
|
+
return {
|
|
131
|
+
From: node.OpenTagRange.From,
|
|
132
|
+
To: getElementEndPosition(node),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function convertDocType(node) {
|
|
136
|
+
// templ fmt always writes uppercase <!DOCTYPE, so we hardcode it here.
|
|
137
|
+
// The html/lowercase rule is disabled in the recommended config to match.
|
|
138
|
+
return {
|
|
139
|
+
type: NodeTypes.Doctype,
|
|
140
|
+
range: convertRange(node.Range),
|
|
141
|
+
loc: convertLoc(node.Range),
|
|
142
|
+
open: {
|
|
143
|
+
type: NodeTypes.DoctypeOpen,
|
|
144
|
+
value: "<!DOCTYPE",
|
|
145
|
+
range: convertRange(node.OpenRange),
|
|
146
|
+
loc: convertLoc(node.OpenRange),
|
|
147
|
+
},
|
|
148
|
+
attributes: [
|
|
149
|
+
{
|
|
150
|
+
type: NodeTypes.DoctypeAttribute,
|
|
151
|
+
range: convertRange(node.ValueRange),
|
|
152
|
+
loc: convertLoc(node.ValueRange),
|
|
153
|
+
value: {
|
|
154
|
+
type: NodeTypes.DoctypeAttributeValue,
|
|
155
|
+
value: node.Value,
|
|
156
|
+
range: convertRange(node.ValueRange),
|
|
157
|
+
loc: convertLoc(node.ValueRange),
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
close: {
|
|
162
|
+
type: NodeTypes.DoctypeClose,
|
|
163
|
+
value: ">",
|
|
164
|
+
range: convertRange(node.CloseRange),
|
|
165
|
+
loc: convertLoc(node.CloseRange),
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function convertElement(node) {
|
|
170
|
+
const elementRange = getElementRange(node);
|
|
171
|
+
const range = convertRange(elementRange);
|
|
172
|
+
const loc = convertLoc(elementRange);
|
|
173
|
+
const openStart = {
|
|
174
|
+
type: NodeTypes.OpenTagStart,
|
|
175
|
+
value: `<${node.Name}`,
|
|
176
|
+
range: convertRange({
|
|
177
|
+
From: node.OpenTagRange.From,
|
|
178
|
+
To: node.NameRange.To,
|
|
179
|
+
}),
|
|
180
|
+
loc: convertLoc({
|
|
181
|
+
From: node.OpenTagRange.From,
|
|
182
|
+
To: node.NameRange.To,
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
const openEnd = {
|
|
186
|
+
type: NodeTypes.OpenTagEnd,
|
|
187
|
+
value: node.SelfClosing ? "/>" : ">",
|
|
188
|
+
range: convertRange(node.OpenTagEndRange),
|
|
189
|
+
loc: convertLoc(node.OpenTagEndRange),
|
|
190
|
+
};
|
|
191
|
+
let closeRange;
|
|
192
|
+
let closeLoc;
|
|
193
|
+
if (node.CloseTagRange) {
|
|
194
|
+
closeRange = convertRange(node.CloseTagRange);
|
|
195
|
+
closeLoc = convertLoc(node.CloseTagRange);
|
|
52
196
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
197
|
+
else {
|
|
198
|
+
const elementEnd = getElementEndPosition(node);
|
|
199
|
+
closeRange = [elementEnd.Index, elementEnd.Index];
|
|
200
|
+
const closePosition = convertPosition(elementEnd);
|
|
201
|
+
closeLoc = {
|
|
202
|
+
start: closePosition,
|
|
203
|
+
end: closePosition,
|
|
60
204
|
};
|
|
61
205
|
}
|
|
62
|
-
|
|
63
|
-
|
|
206
|
+
const close = {
|
|
207
|
+
type: NodeTypes.CloseTag,
|
|
208
|
+
value: `</${node.Name}>`,
|
|
209
|
+
range: closeRange,
|
|
210
|
+
loc: closeLoc,
|
|
211
|
+
};
|
|
212
|
+
let attributes;
|
|
213
|
+
try {
|
|
214
|
+
attributes = convertAttributes(node.Attributes);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
throw new TemplASTConversionError(`Failed to convert attributes${error instanceof Error ? `: ${error.message}` : ""}`, error instanceof TemplASTConversionError ? error.node : node);
|
|
218
|
+
}
|
|
219
|
+
const children = [];
|
|
220
|
+
try {
|
|
221
|
+
if (node.Children) {
|
|
222
|
+
for (const child of node.Children) {
|
|
223
|
+
const converted = convertChild(child);
|
|
224
|
+
if (converted && converted.type !== NodeTypes.Doctype) {
|
|
225
|
+
children.push(converted);
|
|
226
|
+
if (child.type === "Element" && child.TrailingSpace) {
|
|
227
|
+
const trailingSpace = createTrailingSpaceNode(child);
|
|
228
|
+
if (trailingSpace) {
|
|
229
|
+
children.push(trailingSpace);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
throw new TemplASTConversionError(`Failed to convert element children${error instanceof Error ? `: ${error.message}` : ""}`, error instanceof TemplASTConversionError ? error.node : node);
|
|
238
|
+
}
|
|
239
|
+
// Determine the appropriate node type based on element name
|
|
240
|
+
const lowerName = node.Name.toLowerCase();
|
|
241
|
+
const nodeType = lowerName === "script"
|
|
242
|
+
? NodeTypes.ScriptTag
|
|
243
|
+
: lowerName === "style"
|
|
244
|
+
? NodeTypes.StyleTag
|
|
245
|
+
: NodeTypes.Tag;
|
|
246
|
+
// ScriptTagNode and StyleTagNode are separate types in es-html-parser
|
|
247
|
+
// but html-eslint rules accept them interchangeably with TagNode.
|
|
248
|
+
return {
|
|
249
|
+
type: nodeType,
|
|
250
|
+
name: node.Name,
|
|
251
|
+
selfClosing: node.SelfClosing,
|
|
252
|
+
openStart,
|
|
253
|
+
openEnd,
|
|
254
|
+
close,
|
|
255
|
+
attributes,
|
|
256
|
+
children,
|
|
257
|
+
range,
|
|
258
|
+
loc,
|
|
259
|
+
};
|
|
64
260
|
}
|
|
65
|
-
function
|
|
261
|
+
function getContentRange(openTagEndRange, closeTagRange) {
|
|
262
|
+
return {
|
|
263
|
+
From: openTagEndRange.To,
|
|
264
|
+
To: closeTagRange.From,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function convertScriptElement(node) {
|
|
66
268
|
const range = convertRange(node.Range);
|
|
67
269
|
const loc = convertLoc(node.Range);
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
270
|
+
const scriptOpenStartRange = getLiteralRange(node.OpenTagRange.From, "<script");
|
|
271
|
+
const contentRange = getContentRange(node.OpenTagEndRange, node.CloseTagRange);
|
|
272
|
+
// Extract text content from script element
|
|
273
|
+
const scriptContents = node.Contents ?? [];
|
|
274
|
+
const textContent = scriptContents
|
|
275
|
+
.map((content) => content.Value ?? "")
|
|
276
|
+
.join("");
|
|
277
|
+
const openStart = {
|
|
278
|
+
type: NodeTypes.OpenScriptTagStart,
|
|
279
|
+
value: `<script`,
|
|
280
|
+
range: convertRange(scriptOpenStartRange),
|
|
281
|
+
loc: convertLoc(scriptOpenStartRange),
|
|
282
|
+
};
|
|
283
|
+
const openEnd = {
|
|
284
|
+
type: NodeTypes.OpenScriptTagEnd,
|
|
285
|
+
value: ">",
|
|
286
|
+
range: convertRange(node.OpenTagEndRange),
|
|
287
|
+
loc: convertLoc(node.OpenTagEndRange),
|
|
288
|
+
};
|
|
289
|
+
const close = {
|
|
290
|
+
type: NodeTypes.CloseScriptTag,
|
|
291
|
+
value: `</script>`,
|
|
292
|
+
range: convertRange(node.CloseTagRange),
|
|
293
|
+
loc: convertLoc(node.CloseTagRange),
|
|
294
|
+
};
|
|
295
|
+
const attributes = convertAttributes(node.Attributes);
|
|
296
|
+
const value = textContent
|
|
297
|
+
? {
|
|
298
|
+
type: NodeTypes.ScriptTagContent,
|
|
299
|
+
value: textContent,
|
|
300
|
+
range: convertRange(contentRange),
|
|
301
|
+
loc: convertLoc(contentRange),
|
|
302
|
+
parts: [],
|
|
303
|
+
}
|
|
304
|
+
: undefined;
|
|
305
|
+
return {
|
|
306
|
+
type: NodeTypes.ScriptTag,
|
|
307
|
+
openStart,
|
|
308
|
+
openEnd,
|
|
309
|
+
close,
|
|
310
|
+
attributes,
|
|
311
|
+
...(value && { value }),
|
|
312
|
+
range,
|
|
313
|
+
loc,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function convertRawElement(node) {
|
|
317
|
+
const range = convertRange(node.Range);
|
|
318
|
+
const loc = convertLoc(node.Range);
|
|
319
|
+
const lowerName = node.Name.toLowerCase();
|
|
320
|
+
const contentRange = getContentRange(node.OpenTagEndRange, node.CloseTagRange);
|
|
321
|
+
if (lowerName === "style") {
|
|
322
|
+
const openStart = {
|
|
323
|
+
type: NodeTypes.OpenStyleTagStart,
|
|
324
|
+
value: `<${node.Name}`,
|
|
325
|
+
range: convertRange({
|
|
326
|
+
From: node.OpenTagRange.From,
|
|
327
|
+
To: node.NameRange.To,
|
|
328
|
+
}),
|
|
329
|
+
loc: convertLoc({
|
|
330
|
+
From: node.OpenTagRange.From,
|
|
331
|
+
To: node.NameRange.To,
|
|
332
|
+
}),
|
|
333
|
+
};
|
|
334
|
+
const openEnd = {
|
|
335
|
+
type: NodeTypes.OpenStyleTagEnd,
|
|
336
|
+
value: ">",
|
|
337
|
+
range: convertRange(node.OpenTagEndRange),
|
|
338
|
+
loc: convertLoc(node.OpenTagEndRange),
|
|
339
|
+
};
|
|
340
|
+
const close = {
|
|
341
|
+
type: NodeTypes.CloseStyleTag,
|
|
342
|
+
value: `</${node.Name}>`,
|
|
343
|
+
range: convertRange(node.CloseTagRange),
|
|
344
|
+
loc: convertLoc(node.CloseTagRange),
|
|
345
|
+
};
|
|
346
|
+
const attributes = convertAttributes(node.Attributes);
|
|
347
|
+
const value = node.Contents
|
|
348
|
+
? {
|
|
349
|
+
type: NodeTypes.StyleTagContent,
|
|
350
|
+
value: node.Contents,
|
|
351
|
+
range: convertRange(contentRange),
|
|
352
|
+
loc: convertLoc(contentRange),
|
|
353
|
+
parts: [],
|
|
354
|
+
}
|
|
355
|
+
: undefined;
|
|
356
|
+
return {
|
|
357
|
+
type: NodeTypes.StyleTag,
|
|
358
|
+
openStart,
|
|
359
|
+
openEnd,
|
|
360
|
+
close,
|
|
361
|
+
attributes,
|
|
362
|
+
...(value && { value }),
|
|
363
|
+
range,
|
|
364
|
+
loc,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
72
367
|
const openStart = {
|
|
73
368
|
type: NodeTypes.OpenTagStart,
|
|
74
369
|
value: `<${node.Name}`,
|
|
75
|
-
range:
|
|
370
|
+
range: convertRange({
|
|
371
|
+
From: node.OpenTagRange.From,
|
|
372
|
+
To: node.NameRange.To,
|
|
373
|
+
}),
|
|
76
374
|
loc: convertLoc({
|
|
77
|
-
From: node.
|
|
375
|
+
From: node.OpenTagRange.From,
|
|
78
376
|
To: node.NameRange.To,
|
|
79
377
|
}),
|
|
80
378
|
};
|
|
81
|
-
// Estimate the position of the closing > of the opening tag
|
|
82
|
-
const lastAttr = node.Attributes && node.Attributes.length > 0
|
|
83
|
-
? node.Attributes[node.Attributes.length - 1]
|
|
84
|
-
: null;
|
|
85
|
-
const openEndStart = lastAttr
|
|
86
|
-
? lastAttr.Key.NameRange.To.Index +
|
|
87
|
-
2 +
|
|
88
|
-
(lastAttr.Value?.length ?? lastAttr.Expression?.Value.length ?? 0) +
|
|
89
|
-
1
|
|
90
|
-
: node.NameRange.To.Index;
|
|
91
379
|
const openEnd = {
|
|
92
380
|
type: NodeTypes.OpenTagEnd,
|
|
93
381
|
value: ">",
|
|
94
|
-
range:
|
|
95
|
-
loc:
|
|
96
|
-
start: {
|
|
97
|
-
line: node.NameRange.To.Line + 1,
|
|
98
|
-
column: node.NameRange.To.Col,
|
|
99
|
-
},
|
|
100
|
-
end: {
|
|
101
|
-
line: node.NameRange.To.Line + 1,
|
|
102
|
-
column: node.NameRange.To.Col + 1,
|
|
103
|
-
},
|
|
104
|
-
},
|
|
382
|
+
range: convertRange(node.OpenTagEndRange),
|
|
383
|
+
loc: convertLoc(node.OpenTagEndRange),
|
|
105
384
|
};
|
|
106
|
-
// Estimate close tag position (at the end of the element range)
|
|
107
|
-
const closeTagStart = node.Range.To.Index - (node.Name.length + 3); // -3 for </>
|
|
108
385
|
const close = {
|
|
109
386
|
type: NodeTypes.CloseTag,
|
|
110
387
|
value: `</${node.Name}>`,
|
|
111
|
-
range:
|
|
112
|
-
loc:
|
|
113
|
-
start: {
|
|
114
|
-
line: node.Range.To.Line + 1,
|
|
115
|
-
column: Math.max(0, node.Range.To.Col - (node.Name.length + 3)),
|
|
116
|
-
},
|
|
117
|
-
end: convertPosition(node.Range.To),
|
|
118
|
-
},
|
|
388
|
+
range: convertRange(node.CloseTagRange),
|
|
389
|
+
loc: convertLoc(node.CloseTagRange),
|
|
119
390
|
};
|
|
120
|
-
const attributes = node.Attributes
|
|
121
|
-
? node.Attributes.map(convertAttribute)
|
|
122
|
-
: [];
|
|
123
|
-
const children = node.Children.map(convertChild).filter((child) => child !== null);
|
|
391
|
+
const attributes = convertAttributes(node.Attributes);
|
|
124
392
|
return {
|
|
125
393
|
type: NodeTypes.Tag,
|
|
126
394
|
name: node.Name,
|
|
@@ -129,12 +397,50 @@ function convertElement(node) {
|
|
|
129
397
|
openEnd,
|
|
130
398
|
close,
|
|
131
399
|
attributes,
|
|
132
|
-
children,
|
|
400
|
+
children: [],
|
|
133
401
|
range,
|
|
134
402
|
loc,
|
|
135
403
|
};
|
|
136
404
|
}
|
|
405
|
+
function convertAttributes(attrs) {
|
|
406
|
+
if (!attrs) {
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
const converted = [];
|
|
410
|
+
for (const attr of attrs) {
|
|
411
|
+
switch (attr.type) {
|
|
412
|
+
case "ConditionalAttribute":
|
|
413
|
+
converted.push(...convertAttributes(attr.Then));
|
|
414
|
+
if (attr.Else) {
|
|
415
|
+
converted.push(...convertAttributes(attr.Else));
|
|
416
|
+
}
|
|
417
|
+
break;
|
|
418
|
+
case "SpreadAttributes":
|
|
419
|
+
// Spread attributes cannot be represented in the ESLint HTML AST.
|
|
420
|
+
break;
|
|
421
|
+
case "BoolConstantAttribute":
|
|
422
|
+
case "ConstantAttribute":
|
|
423
|
+
case "ExpressionAttribute":
|
|
424
|
+
if (attr.Key.type !== "ConstantAttributeKey") {
|
|
425
|
+
throw new Error("Expression-based attribute keys are not supported in ESLint conversion");
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
converted.push(convertAttribute(attr));
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
throw new TemplASTConversionError(`Failed to convert attribute${error instanceof Error ? `: ${error.message}` : ""}`, error instanceof TemplASTConversionError ? error.node : attr);
|
|
432
|
+
}
|
|
433
|
+
break;
|
|
434
|
+
default:
|
|
435
|
+
throw new Error("Unsupported attribute structure");
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return converted;
|
|
439
|
+
}
|
|
137
440
|
function convertAttribute(attr) {
|
|
441
|
+
if (attr.Key.type !== "ConstantAttributeKey") {
|
|
442
|
+
throw new Error("Expression-based attribute keys are not supported in ESLint conversion");
|
|
443
|
+
}
|
|
138
444
|
const keyRange = convertRange(attr.Key.NameRange);
|
|
139
445
|
const keyLoc = convertLoc(attr.Key.NameRange);
|
|
140
446
|
const key = {
|
|
@@ -144,44 +450,74 @@ function convertAttribute(attr) {
|
|
|
144
450
|
loc: keyLoc,
|
|
145
451
|
parts: [],
|
|
146
452
|
};
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
453
|
+
let value;
|
|
454
|
+
let startWrapper;
|
|
455
|
+
let endWrapper;
|
|
456
|
+
switch (attr.type) {
|
|
457
|
+
case "ConstantAttribute":
|
|
458
|
+
value = {
|
|
459
|
+
type: NodeTypes.AttributeValue,
|
|
460
|
+
value: attr.Value,
|
|
461
|
+
range: convertRange(attr.ValueRange),
|
|
462
|
+
loc: convertLoc(attr.ValueRange),
|
|
463
|
+
parts: [],
|
|
464
|
+
};
|
|
465
|
+
({ startWrapper, endWrapper } = createAttributeValueWrappers(attr));
|
|
466
|
+
break;
|
|
467
|
+
case "ExpressionAttribute":
|
|
468
|
+
value = {
|
|
469
|
+
type: NodeTypes.AttributeValue,
|
|
470
|
+
value: "",
|
|
471
|
+
range: convertRange(attr.InitializerRange),
|
|
472
|
+
loc: convertLoc(attr.InitializerRange),
|
|
473
|
+
parts: [],
|
|
474
|
+
};
|
|
475
|
+
break;
|
|
476
|
+
case "BoolConstantAttribute":
|
|
477
|
+
// Boolean attributes have no value
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
const hasValue = value !== undefined;
|
|
481
|
+
const attrRange = hasValue
|
|
482
|
+
? convertRange(attr.Range)
|
|
483
|
+
: keyRange;
|
|
174
484
|
return {
|
|
175
485
|
type: NodeTypes.Attribute,
|
|
176
486
|
key,
|
|
177
|
-
value,
|
|
487
|
+
...(value && { value }), // Only include value if it exists
|
|
488
|
+
...(startWrapper && { startWrapper }),
|
|
489
|
+
...(endWrapper && { endWrapper }),
|
|
178
490
|
range: attrRange,
|
|
179
|
-
loc:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
491
|
+
loc: hasValue ? convertLoc(attr.Range) : keyLoc,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function createAttributeValueWrappers(attr) {
|
|
495
|
+
const attrRange = convertRange(attr.Range);
|
|
496
|
+
const valueRange = convertRange(attr.ValueRange);
|
|
497
|
+
if (valueRange[0] - 1 < attrRange[0] || valueRange[1] + 1 > attrRange[1]) {
|
|
498
|
+
return {};
|
|
499
|
+
}
|
|
500
|
+
const wrapperValue = attr.SingleQuote ? "'" : '"';
|
|
501
|
+
const startRange = {
|
|
502
|
+
From: offsetPosition(attr.ValueRange.From, -1),
|
|
503
|
+
To: attr.ValueRange.From,
|
|
504
|
+
};
|
|
505
|
+
const endRange = {
|
|
506
|
+
From: attr.ValueRange.To,
|
|
507
|
+
To: offsetPosition(attr.ValueRange.To, 1),
|
|
508
|
+
};
|
|
509
|
+
return {
|
|
510
|
+
startWrapper: {
|
|
511
|
+
type: NodeTypes.AttributeValueWrapperStart,
|
|
512
|
+
value: wrapperValue,
|
|
513
|
+
range: convertRange(startRange),
|
|
514
|
+
loc: convertLoc(startRange),
|
|
515
|
+
},
|
|
516
|
+
endWrapper: {
|
|
517
|
+
type: NodeTypes.AttributeValueWrapperEnd,
|
|
518
|
+
value: wrapperValue,
|
|
519
|
+
range: convertRange(endRange),
|
|
520
|
+
loc: convertLoc(endRange),
|
|
185
521
|
},
|
|
186
522
|
};
|
|
187
523
|
}
|
|
@@ -191,6 +527,13 @@ function convertLoc(range) {
|
|
|
191
527
|
end: convertPosition(range.To),
|
|
192
528
|
};
|
|
193
529
|
}
|
|
530
|
+
function offsetPosition(position, offset) {
|
|
531
|
+
return {
|
|
532
|
+
...position,
|
|
533
|
+
Index: position.Index + offset,
|
|
534
|
+
Col: position.Col + offset,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
194
537
|
function convertRange(range) {
|
|
195
538
|
return [range.From.Index, range.To.Index];
|
|
196
539
|
}
|
|
@@ -200,19 +543,34 @@ function convertPosition(pos) {
|
|
|
200
543
|
column: pos.Col,
|
|
201
544
|
};
|
|
202
545
|
}
|
|
203
|
-
function
|
|
204
|
-
return
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
546
|
+
function getLiteralRange(from, value) {
|
|
547
|
+
return {
|
|
548
|
+
From: from,
|
|
549
|
+
To: {
|
|
550
|
+
Index: from.Index + value.length,
|
|
551
|
+
Line: from.Line,
|
|
552
|
+
Col: from.Col + value.length,
|
|
553
|
+
},
|
|
554
|
+
};
|
|
211
555
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
556
|
+
export class TemplASTConversionError extends SyntaxError {
|
|
557
|
+
name = "TemplASTConversionError";
|
|
558
|
+
line;
|
|
559
|
+
column;
|
|
560
|
+
node;
|
|
561
|
+
constructor(message, node, options) {
|
|
562
|
+
super(message, options);
|
|
563
|
+
this.node = node;
|
|
564
|
+
// Extract position from node if it has a Range property, otherwise default to line 1, column 0
|
|
565
|
+
if ("Range" in node && node.Range) {
|
|
566
|
+
const position = convertPosition(node.Range.From);
|
|
567
|
+
this.line = position.line;
|
|
568
|
+
this.column = position.column;
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
this.line = 1;
|
|
572
|
+
this.column = 0;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
217
575
|
}
|
|
218
576
|
//# sourceMappingURL=templ-ast-to-eslint-ast.js.map
|