astro-eslint-parser 0.0.16 → 0.0.17
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 +11 -4
- package/lib/ast.d.ts +11 -16
- package/lib/context/script.js +17 -18
- package/lib/parser/astro-parser/parse.js +4 -0
- package/lib/parser/process-template.js +63 -43
- package/lib/visitor-keys.js +1 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# astro-eslint-parser
|
|
2
2
|
|
|
3
|
-
[Astro] parser for [ESLint].
|
|
3
|
+
[Astro] component parser for [ESLint].
|
|
4
4
|
You can check it on [Online DEMO](https://ota-meshi.github.io/astro-eslint-parser/playground).
|
|
5
5
|
|
|
6
|
+
[](https://github.com/sponsors/ota-meshi)
|
|
7
|
+
|
|
6
8
|
[](https://www.npmjs.com/package/astro-eslint-parser)
|
|
7
9
|
[](https://www.npmjs.com/package/astro-eslint-parser)
|
|
8
10
|
[](http://www.npmtrends.com/astro-eslint-parser)
|
|
@@ -143,17 +145,16 @@ Example **.vscode/settings.json**:
|
|
|
143
145
|
}
|
|
144
146
|
```
|
|
145
147
|
|
|
146
|
-
## Compatibility With Existing ESLint Rules
|
|
148
|
+
## :two_hearts: Compatibility With Existing ESLint Rules
|
|
147
149
|
|
|
148
150
|
Most of the rules in the ESLint core work for the script part, but some rules are incompatible.
|
|
149
151
|
This parser will generate a JSX compatible AST for most of the HTML part of the Astro component. Therefore, some rules of [eslint-plugin-react] may work.
|
|
150
152
|
For example, the [react/jsx-no-target-blank] rule works fine.
|
|
151
153
|
|
|
152
|
-
[semi]: https://eslint.org/docs/rules/semi
|
|
153
154
|
[eslint-plugin-react]: https://github.com/jsx-eslint/eslint-plugin-react/
|
|
154
155
|
[react/jsx-no-target-blank]: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-target-blank.md
|
|
155
156
|
|
|
156
|
-
## Usage for Custom Rules / Plugins
|
|
157
|
+
## :hammer: Usage for Custom Rules / Plugins
|
|
157
158
|
|
|
158
159
|
- TBA
|
|
159
160
|
- You can check the AST in the [Online DEMO](https://ota-meshi.github.io/astro-eslint-parser/). However, AST is subject to major changes in the future.
|
|
@@ -167,6 +168,12 @@ Welcome contributing!
|
|
|
167
168
|
|
|
168
169
|
Please use GitHub's Issues/PRs.
|
|
169
170
|
|
|
171
|
+
## :heart: Supporting
|
|
172
|
+
|
|
173
|
+
If you are willing to see that this package continues to be maintained, please consider sponsoring me.
|
|
174
|
+
|
|
175
|
+
[](https://github.com/sponsors/ota-meshi)
|
|
176
|
+
|
|
170
177
|
## :lock: License
|
|
171
178
|
|
|
172
179
|
See the [LICENSE](LICENSE) file for license rights and limitations (MIT).
|
package/lib/ast.d.ts
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
import type { TSESTree } from "@typescript-eslint/types";
|
|
2
|
-
export declare type AstroNode = AstroProgram |
|
|
2
|
+
export declare type AstroNode = AstroProgram | AstroFragment | AstroHTMLComment | AstroDoctype | AstroShorthandAttribute | AstroTemplateLiteralAttribute | AstroRawText;
|
|
3
|
+
export declare type AstroChild = TSESTree.JSXFragment["children"][number] | AstroHTMLComment;
|
|
3
4
|
/** Node of Astro program root */
|
|
4
5
|
export interface AstroProgram extends Omit<TSESTree.Program, "type" | "body"> {
|
|
5
6
|
type: "Program";
|
|
6
|
-
body: (TSESTree.Program["body"][number] |
|
|
7
|
+
body: (TSESTree.Program["body"][number] | AstroFragment)[];
|
|
7
8
|
sourceType: "script" | "module";
|
|
8
9
|
comments: TSESTree.Comment[];
|
|
9
10
|
tokens: TSESTree.Token[];
|
|
10
11
|
parent: undefined;
|
|
11
12
|
}
|
|
12
|
-
/** Node of Astro fragment
|
|
13
|
-
export interface
|
|
14
|
-
type: "
|
|
15
|
-
children:
|
|
16
|
-
parent:
|
|
13
|
+
/** Node of Astro fragment */
|
|
14
|
+
export interface AstroFragment extends Omit<TSESTree.BaseNode, "type" | "parent"> {
|
|
15
|
+
type: "AstroFragment";
|
|
16
|
+
children: AstroChild[];
|
|
17
|
+
parent: TSESTree.JSXFragment["parent"];
|
|
17
18
|
}
|
|
18
19
|
/** Node of Astro html comment */
|
|
19
20
|
export interface AstroHTMLComment extends Omit<TSESTree.BaseNode, "type" | "parent"> {
|
|
20
21
|
type: "AstroHTMLComment";
|
|
21
22
|
value: string;
|
|
22
|
-
parent:
|
|
23
|
+
parent: AstroFragment | AstroFragment | TSESTree.JSXElement | TSESTree.JSXFragment;
|
|
23
24
|
}
|
|
24
25
|
/** Node of Astro doctype */
|
|
25
26
|
export interface AstroDoctype extends Omit<TSESTree.BaseNode, "type" | "parent"> {
|
|
26
27
|
type: "AstroDoctype";
|
|
27
|
-
parent:
|
|
28
|
+
parent: AstroFragment | AstroFragment;
|
|
28
29
|
}
|
|
29
30
|
/** Node of Astro shorthand attribute */
|
|
30
31
|
export interface AstroShorthandAttribute extends Omit<TSESTree.JSXAttribute, "type" | "parent"> {
|
|
@@ -43,11 +44,5 @@ export interface AstroTemplateLiteralAttribute extends Omit<TSESTree.JSXAttribut
|
|
|
43
44
|
/** Node of Astro raw text */
|
|
44
45
|
export interface AstroRawText extends Omit<TSESTree.JSXText, "type" | "parent"> {
|
|
45
46
|
type: "AstroRawText";
|
|
46
|
-
parent:
|
|
47
|
-
}
|
|
48
|
-
/** Node of Astro fragment expression */
|
|
49
|
-
export interface AstroFragment extends Omit<TSESTree.BaseNode, "type" | "parent"> {
|
|
50
|
-
type: "AstroFragment";
|
|
51
|
-
children: TSESTree.JSXFragment["children"];
|
|
52
|
-
parent: TSESTree.JSXFragment["parent"];
|
|
47
|
+
parent: AstroFragment | TSESTree.JSXElement | TSESTree.JSXFragment;
|
|
53
48
|
}
|
package/lib/context/script.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ScriptContext = void 0;
|
|
4
4
|
const traverse_1 = require("../traverse");
|
|
5
|
-
const errors_1 = require("../errors");
|
|
6
5
|
class RestoreNodeProcessContext {
|
|
7
6
|
constructor(result) {
|
|
8
7
|
this.removeTokens = new Set();
|
|
@@ -26,6 +25,9 @@ class ScriptContext {
|
|
|
26
25
|
this.consumedIndex += offset;
|
|
27
26
|
}
|
|
28
27
|
appendOriginal(index) {
|
|
28
|
+
if (this.consumedIndex >= index) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
29
31
|
this.offsets.push({
|
|
30
32
|
original: this.consumedIndex,
|
|
31
33
|
script: this.script.length,
|
|
@@ -47,20 +49,8 @@ class ScriptContext {
|
|
|
47
49
|
/**
|
|
48
50
|
* Restore AST nodes
|
|
49
51
|
*/
|
|
50
|
-
// eslint-disable-next-line complexity -- X(
|
|
51
52
|
restore(result) {
|
|
52
|
-
|
|
53
|
-
if (last.type !== "ExpressionStatement") {
|
|
54
|
-
throw new errors_1.ParseError("Unknown state error: Expected ExpressionStatement", last.range[0], this.ctx);
|
|
55
|
-
}
|
|
56
|
-
if (last.expression.type !== "JSXFragment") {
|
|
57
|
-
throw new errors_1.ParseError("Unknown state error: Expected JSXFragment", last.expression.range[0], this.ctx);
|
|
58
|
-
}
|
|
59
|
-
// Process for Astro
|
|
60
|
-
const rootFragment = (result.ast.body[result.ast.body.length - 1] = last.expression);
|
|
61
|
-
delete rootFragment.closingFragment;
|
|
62
|
-
delete rootFragment.openingFragment;
|
|
63
|
-
rootFragment.type = "AstroRootFragment";
|
|
53
|
+
var _a, _b;
|
|
64
54
|
// remap locations
|
|
65
55
|
const traversed = new Map();
|
|
66
56
|
(0, traverse_1.traverseNodes)(result.ast, {
|
|
@@ -110,10 +100,16 @@ class ScriptContext {
|
|
|
110
100
|
}
|
|
111
101
|
}
|
|
112
102
|
// Adjust program node location
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
result.ast.
|
|
116
|
-
|
|
103
|
+
const firstOffset = Math.min(...[
|
|
104
|
+
result.ast.body[0],
|
|
105
|
+
(_a = result.ast.tokens) === null || _a === void 0 ? void 0 : _a[0],
|
|
106
|
+
(_b = result.ast.comments) === null || _b === void 0 ? void 0 : _b[0],
|
|
107
|
+
]
|
|
108
|
+
.filter(Boolean)
|
|
109
|
+
.map((t) => t.range[0]));
|
|
110
|
+
if (firstOffset < result.ast.range[0]) {
|
|
111
|
+
result.ast.range[0] = firstOffset;
|
|
112
|
+
result.ast.loc.start = this.ctx.getLocFromIndex(firstOffset);
|
|
117
113
|
}
|
|
118
114
|
}
|
|
119
115
|
remapLocation(node) {
|
|
@@ -142,6 +138,9 @@ class ScriptContext {
|
|
|
142
138
|
}
|
|
143
139
|
}
|
|
144
140
|
getRemapRange(start, end) {
|
|
141
|
+
if (!this.offsets.length) {
|
|
142
|
+
return [start, end];
|
|
143
|
+
}
|
|
145
144
|
let lastStart = this.offsets[0];
|
|
146
145
|
let lastEnd = this.offsets[0];
|
|
147
146
|
for (const offset of this.offsets) {
|
|
@@ -32,6 +32,10 @@ const errors_1 = require("../../errors");
|
|
|
32
32
|
*/
|
|
33
33
|
function parse(code, ctx) {
|
|
34
34
|
const ast = parseByService(code, ctx).ast;
|
|
35
|
+
if (!ast.children) {
|
|
36
|
+
// If the source code is empty, the children property may not be available.
|
|
37
|
+
ast.children = [];
|
|
38
|
+
}
|
|
35
39
|
const htmlElement = ast.children.find((n) => n.type === "element" && n.name === "html");
|
|
36
40
|
if (htmlElement) {
|
|
37
41
|
adjustHTML(ast, htmlElement, ctx);
|
|
@@ -11,30 +11,47 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
11
11
|
let uniqueIdSeq = 0;
|
|
12
12
|
const usedUniqueIds = new Set();
|
|
13
13
|
const script = new script_1.ScriptContext(ctx);
|
|
14
|
-
const frontmatter = resultTemplate.ast.children.find((n) => n.type === "frontmatter");
|
|
15
14
|
let fragmentOpened = false;
|
|
16
|
-
|
|
15
|
+
/** Open astro root fragment */
|
|
16
|
+
function openRootFragment(startOffset) {
|
|
17
17
|
script.appendScript("<>");
|
|
18
18
|
fragmentOpened = true;
|
|
19
|
+
script.addRestoreNodeProcess((scriptNode, { result }) => {
|
|
20
|
+
if (scriptNode.type === types_1.AST_NODE_TYPES.ExpressionStatement &&
|
|
21
|
+
scriptNode.expression.type === types_1.AST_NODE_TYPES.JSXFragment &&
|
|
22
|
+
scriptNode.range[0] === startOffset &&
|
|
23
|
+
result.ast.body.includes(scriptNode)) {
|
|
24
|
+
const index = result.ast.body.indexOf(scriptNode);
|
|
25
|
+
const rootFragment = (result.ast.body[index] =
|
|
26
|
+
scriptNode.expression);
|
|
27
|
+
delete rootFragment.closingFragment;
|
|
28
|
+
delete rootFragment.openingFragment;
|
|
29
|
+
rootFragment.type = "AstroFragment";
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
});
|
|
19
34
|
}
|
|
20
35
|
(0, astro_1.walkElements)(resultTemplate.ast, ctx.code,
|
|
21
36
|
// eslint-disable-next-line complexity -- X(
|
|
22
37
|
(node, [parent]) => {
|
|
23
38
|
if (node.type === "frontmatter") {
|
|
24
39
|
const start = node.position.start.offset;
|
|
40
|
+
if (fragmentOpened) {
|
|
41
|
+
script.appendScript("</>;");
|
|
42
|
+
fragmentOpened = false;
|
|
43
|
+
}
|
|
25
44
|
script.appendOriginal(start);
|
|
26
45
|
script.skipOriginalOffset(3);
|
|
27
46
|
const end = (0, astro_1.getEndOffset)(node, ctx);
|
|
28
47
|
script.appendOriginal(end - 3);
|
|
29
|
-
script.appendScript("
|
|
30
|
-
fragmentOpened = true;
|
|
48
|
+
script.appendScript(";");
|
|
31
49
|
script.skipOriginalOffset(3);
|
|
32
50
|
script.addRestoreNodeProcess((_scriptNode, { result }) => {
|
|
33
51
|
for (let index = 0; index < result.ast.body.length; index++) {
|
|
34
52
|
const st = result.ast.body[index];
|
|
35
53
|
if (st.type === types_1.AST_NODE_TYPES.EmptyStatement) {
|
|
36
|
-
if (st.range[0] === end - 3 &&
|
|
37
|
-
st.range[1] === end) {
|
|
54
|
+
if (st.range[0] === end - 3 && st.range[1] <= end) {
|
|
38
55
|
result.ast.body.splice(index, 1);
|
|
39
56
|
break;
|
|
40
57
|
}
|
|
@@ -81,6 +98,11 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
81
98
|
}
|
|
82
99
|
}
|
|
83
100
|
}
|
|
101
|
+
const start = node.position.start.offset;
|
|
102
|
+
script.appendOriginal(start);
|
|
103
|
+
if (!fragmentOpened) {
|
|
104
|
+
openRootFragment(start);
|
|
105
|
+
}
|
|
84
106
|
// Process for attributes
|
|
85
107
|
for (const attr of node.attributes) {
|
|
86
108
|
if ((node.type === "component" ||
|
|
@@ -210,21 +232,16 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
210
232
|
const end = (0, astro_1.getEndOffset)(node, ctx);
|
|
211
233
|
const length = end - start;
|
|
212
234
|
script.appendOriginal(start);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
script.appendOriginal(start + 1);
|
|
216
|
-
script.appendScript(`></`);
|
|
217
|
-
script.skipOriginalOffset(length - 2);
|
|
218
|
-
targetType = types_1.AST_NODE_TYPES.JSXFragment;
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
script.appendScript(`0;`);
|
|
222
|
-
targetType = types_1.AST_NODE_TYPES.ExpressionStatement;
|
|
223
|
-
script.skipOriginalOffset(length);
|
|
235
|
+
if (!fragmentOpened) {
|
|
236
|
+
openRootFragment(start);
|
|
224
237
|
}
|
|
238
|
+
script.appendOriginal(start + 1);
|
|
239
|
+
script.appendScript(`></`);
|
|
240
|
+
script.skipOriginalOffset(length - 2);
|
|
241
|
+
script.appendOriginal(end);
|
|
225
242
|
script.addRestoreNodeProcess((scriptNode, context) => {
|
|
226
243
|
if (scriptNode.range[0] === start &&
|
|
227
|
-
scriptNode.type ===
|
|
244
|
+
scriptNode.type === types_1.AST_NODE_TYPES.JSXFragment) {
|
|
228
245
|
delete scriptNode.children;
|
|
229
246
|
delete scriptNode.openingFragment;
|
|
230
247
|
delete scriptNode.closingFragment;
|
|
@@ -232,12 +249,10 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
232
249
|
const commentNode = scriptNode;
|
|
233
250
|
commentNode.type = "AstroHTMLComment";
|
|
234
251
|
commentNode.value = node.value;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
token.range[1] === scriptNode.range[1]);
|
|
240
|
-
}
|
|
252
|
+
context.addRemoveToken((token) => token.value === "<" &&
|
|
253
|
+
token.range[0] === scriptNode.range[0]);
|
|
254
|
+
context.addRemoveToken((token) => token.value === ">" &&
|
|
255
|
+
token.range[1] === scriptNode.range[1]);
|
|
241
256
|
return true;
|
|
242
257
|
}
|
|
243
258
|
return false;
|
|
@@ -252,39 +267,39 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
252
267
|
const end = (0, astro_1.getEndOffset)(node, ctx);
|
|
253
268
|
const length = end - start;
|
|
254
269
|
script.appendOriginal(start);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
script.appendOriginal(start + 1);
|
|
258
|
-
script.appendScript(`></`);
|
|
259
|
-
script.skipOriginalOffset(length - 2);
|
|
260
|
-
targetType = types_1.AST_NODE_TYPES.JSXFragment;
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
script.appendScript(`0;`);
|
|
264
|
-
targetType = types_1.AST_NODE_TYPES.ExpressionStatement;
|
|
265
|
-
script.skipOriginalOffset(length);
|
|
270
|
+
if (!fragmentOpened) {
|
|
271
|
+
openRootFragment(start);
|
|
266
272
|
}
|
|
273
|
+
script.appendOriginal(start + 1);
|
|
274
|
+
script.appendScript(`></`);
|
|
275
|
+
script.skipOriginalOffset(length - 2);
|
|
276
|
+
script.appendOriginal(end);
|
|
267
277
|
script.addRestoreNodeProcess((scriptNode, context) => {
|
|
268
278
|
if (scriptNode.range[0] === start &&
|
|
269
|
-
scriptNode.type ===
|
|
279
|
+
scriptNode.type === types_1.AST_NODE_TYPES.JSXFragment) {
|
|
270
280
|
delete scriptNode.children;
|
|
271
281
|
delete scriptNode.openingFragment;
|
|
272
282
|
delete scriptNode.closingFragment;
|
|
273
283
|
delete scriptNode.expression;
|
|
274
284
|
const doctypeNode = scriptNode;
|
|
275
285
|
doctypeNode.type = "AstroDoctype";
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
token.range[1] === scriptNode.range[1]);
|
|
281
|
-
}
|
|
286
|
+
context.addRemoveToken((token) => token.value === "<" &&
|
|
287
|
+
token.range[0] === scriptNode.range[0]);
|
|
288
|
+
context.addRemoveToken((token) => token.value === ">" &&
|
|
289
|
+
token.range[1] === scriptNode.range[1]);
|
|
282
290
|
return true;
|
|
283
291
|
}
|
|
284
292
|
return false;
|
|
285
293
|
});
|
|
286
294
|
script.addToken("HTMLDocType", [start, end]);
|
|
287
295
|
}
|
|
296
|
+
else {
|
|
297
|
+
const start = node.position.start.offset;
|
|
298
|
+
script.appendOriginal(start);
|
|
299
|
+
if (!fragmentOpened) {
|
|
300
|
+
openRootFragment(start);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
288
303
|
}, (node, [parent]) => {
|
|
289
304
|
if ((0, astro_1.isTag)(node)) {
|
|
290
305
|
const closing = (0, astro_1.getSelfClosingTag)(node, ctx);
|
|
@@ -323,8 +338,13 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
323
338
|
}
|
|
324
339
|
}
|
|
325
340
|
});
|
|
341
|
+
if (fragmentOpened) {
|
|
342
|
+
const last = resultTemplate.ast.children[resultTemplate.ast.children.length - 1];
|
|
343
|
+
const end = (0, astro_1.getEndOffset)(last, ctx);
|
|
344
|
+
script.appendOriginal(end);
|
|
345
|
+
script.appendScript("</>");
|
|
346
|
+
}
|
|
326
347
|
script.appendOriginal(ctx.code.length);
|
|
327
|
-
script.appendScript("</>");
|
|
328
348
|
return script;
|
|
329
349
|
/**
|
|
330
350
|
* Generate unique id
|
package/lib/visitor-keys.js
CHANGED
|
@@ -4,12 +4,11 @@ exports.KEYS = void 0;
|
|
|
4
4
|
const eslint_visitor_keys_1 = require("eslint-visitor-keys");
|
|
5
5
|
const astroKeys = {
|
|
6
6
|
Program: ["body"],
|
|
7
|
-
|
|
7
|
+
AstroFragment: ["children"],
|
|
8
8
|
AstroHTMLComment: [],
|
|
9
9
|
AstroDoctype: [],
|
|
10
10
|
AstroShorthandAttribute: ["name", "value"],
|
|
11
11
|
AstroTemplateLiteralAttribute: ["name", "value"],
|
|
12
12
|
AstroRawText: [],
|
|
13
|
-
AstroFragment: ["children"],
|
|
14
13
|
};
|
|
15
14
|
exports.KEYS = (0, eslint_visitor_keys_1.unionWith)(astroKeys);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-eslint-parser",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Astro parser for ESLint",
|
|
3
|
+
"version": "0.0.17",
|
|
4
|
+
"description": "Astro component parser for ESLint",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"lib"
|
|
@@ -88,6 +88,6 @@
|
|
|
88
88
|
"string-replace-loader": "^3.0.3",
|
|
89
89
|
"ts-node": "^10.4.0",
|
|
90
90
|
"typescript": "~4.6.0",
|
|
91
|
-
"vue-eslint-parser": "^
|
|
91
|
+
"vue-eslint-parser": "^9.0.0"
|
|
92
92
|
}
|
|
93
93
|
}
|