astro-eslint-parser 0.0.3 → 0.0.6
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 +10 -6
- package/lib/ast.d.ts +6 -1
- package/lib/astro/index.d.ts +2 -2
- package/lib/astro/index.js +44 -14
- package/lib/parser/astro-parser/parse.js +4 -4
- package/lib/parser/process-template.js +5 -3
- package/lib/visitor-keys.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,10 @@ You can check it on [Online DEMO](https://ota-meshi.github.io/astro-eslint-parse
|
|
|
15
15
|
|
|
16
16
|
This parser is in the ***experimental stages*** of development.
|
|
17
17
|
|
|
18
|
+
⚠ Currently this parser relies heavily on the internal API of [@astrojs/compiler]. It may stop working in a future update of [@astrojs/compiler]. ⚠
|
|
19
|
+
|
|
20
|
+
[@astrojs/compiler]: https://github.com/withastro/compiler
|
|
21
|
+
|
|
18
22
|
<!--
|
|
19
23
|
### ESLint Plugins Using astro-eslint-parser
|
|
20
24
|
|
|
@@ -75,7 +79,7 @@ For example:
|
|
|
75
79
|
|
|
76
80
|
### parserOptions.parser
|
|
77
81
|
|
|
78
|
-
You can use `parserOptions.parser` property to specify a custom parser to parse
|
|
82
|
+
You can use `parserOptions.parser` property to specify a custom parser to parse scripts.
|
|
79
83
|
Other properties than parser would be given to the specified parser.
|
|
80
84
|
For example:
|
|
81
85
|
|
|
@@ -88,7 +92,7 @@ For example:
|
|
|
88
92
|
}
|
|
89
93
|
```
|
|
90
94
|
|
|
91
|
-
For example, if you are using the `"@typescript-eslint/parser"`, and if you want to use TypeScript in
|
|
95
|
+
For example, if you are using the `"@typescript-eslint/parser"`, and if you want to use TypeScript in `.astro`, you need to add more `parserOptions` configuration.
|
|
92
96
|
|
|
93
97
|
```js
|
|
94
98
|
module.exports = {
|
|
@@ -97,13 +101,13 @@ module.exports = {
|
|
|
97
101
|
parserOptions: {
|
|
98
102
|
// ...
|
|
99
103
|
project: "path/to/your/tsconfig.json",
|
|
100
|
-
extraFileExtensions: [".astro"], // This is a required setting in `@typescript-eslint/parser`
|
|
104
|
+
extraFileExtensions: [".astro"], // This is a required setting in `@typescript-eslint/parser` v5.
|
|
101
105
|
},
|
|
102
106
|
overrides: [
|
|
103
107
|
{
|
|
104
108
|
files: ["*.astro"],
|
|
105
109
|
parser: "astro-eslint-parser",
|
|
106
|
-
// Parse the
|
|
110
|
+
// Parse the script in `.astro` as TypeScript by adding the following configuration.
|
|
107
111
|
parserOptions: {
|
|
108
112
|
parser: "@typescript-eslint/parser",
|
|
109
113
|
},
|
|
@@ -136,8 +140,7 @@ Example **.vscode/settings.json**:
|
|
|
136
140
|
|
|
137
141
|
## Compatibility With Existing ESLint Rules
|
|
138
142
|
|
|
139
|
-
Most of the rules in the ESLint core work for the script part, but some rules are incompatible.
|
|
140
|
-
For example, the [semi] rule doesn't work.
|
|
143
|
+
Most of the rules in the ESLint core work for the script part, but some rules are incompatible.
|
|
141
144
|
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.
|
|
142
145
|
For example, the [react/jsx-no-target-blank] rule works fine.
|
|
143
146
|
|
|
@@ -148,6 +151,7 @@ For example, the [react/jsx-no-target-blank] rule works fine.
|
|
|
148
151
|
## Usage for Custom Rules / Plugins
|
|
149
152
|
|
|
150
153
|
- TBA
|
|
154
|
+
- 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.
|
|
151
155
|
|
|
152
156
|
<!-- - [AST.md](./docs/AST.md) is AST specification. You can check it on the [Online DEMO](https://ota-meshi.github.io/astro-eslint-parser/). -->
|
|
153
157
|
<!-- - I have already [implemented some rules] in the [`@ota-meshi/eslint-plugin-astro`]. The source code for these rules will be helpful to you. -->
|
package/lib/ast.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TSESTree } from "@typescript-eslint/types";
|
|
2
|
-
export declare type AstroNode = AstroProgram | AstroRootFragment | AstroHTMLComment | AstroDoctype | AstroShorthandAttribute | AstroTemplateLiteralAttribute;
|
|
2
|
+
export declare type AstroNode = AstroProgram | AstroRootFragment | AstroHTMLComment | AstroDoctype | AstroShorthandAttribute | AstroTemplateLiteralAttribute | AstroRawText;
|
|
3
3
|
/** Node of Astro program root */
|
|
4
4
|
export interface AstroProgram extends Omit<TSESTree.Program, "type" | "body"> {
|
|
5
5
|
type: "Program";
|
|
@@ -40,3 +40,8 @@ export interface AstroTemplateLiteralAttribute extends Omit<TSESTree.JSXAttribut
|
|
|
40
40
|
};
|
|
41
41
|
parent: TSESTree.JSXElement | TSESTree.JSXFragment;
|
|
42
42
|
}
|
|
43
|
+
/** Node of Astro raw text */
|
|
44
|
+
export interface AstroRawText extends Omit<TSESTree.JSXText, "type" | "parent"> {
|
|
45
|
+
type: "AstroRawText";
|
|
46
|
+
parent: AstroRootFragment | TSESTree.JSXElement | TSESTree.JSXFragment;
|
|
47
|
+
}
|
package/lib/astro/index.d.ts
CHANGED
|
@@ -8,9 +8,9 @@ export declare function isTag(node: Node): node is Node & TagLikeNode;
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function isParent(node: Node): node is ParentNode;
|
|
10
10
|
/** walk element nodes */
|
|
11
|
-
export declare function walkElements(parent: ParentNode, cb: (n: Node, parent: ParentNode) => void): void;
|
|
11
|
+
export declare function walkElements(parent: ParentNode, code: string, cb: (n: Node, parent: ParentNode) => void): void;
|
|
12
12
|
/** walk nodes */
|
|
13
|
-
export declare function walk(parent: ParentNode, enter: (n: Node | AttributeNode, parent: ParentNode) => void, leave?: (n: Node | AttributeNode, parent: ParentNode) => void): void;
|
|
13
|
+
export declare function walk(parent: ParentNode, code: string, enter: (n: Node | AttributeNode, parent: ParentNode) => void, leave?: (n: Node | AttributeNode, parent: ParentNode) => void): void;
|
|
14
14
|
/**
|
|
15
15
|
* Get end offset of start tag
|
|
16
16
|
*/
|
package/lib/astro/index.js
CHANGED
|
@@ -19,27 +19,19 @@ function isParent(node) {
|
|
|
19
19
|
}
|
|
20
20
|
exports.isParent = isParent;
|
|
21
21
|
/** walk element nodes */
|
|
22
|
-
function walkElements(parent, cb) {
|
|
23
|
-
|
|
24
|
-
if (parent.type === "root" && children.every((n) => n.position)) {
|
|
25
|
-
// The order of comments and frontmatter may be changed.
|
|
26
|
-
children = [...children].sort((a, b) => a.position.start.offset - b.position.start.offset);
|
|
27
|
-
}
|
|
22
|
+
function walkElements(parent, code, cb) {
|
|
23
|
+
const children = getSortedChildren(parent, code);
|
|
28
24
|
for (const node of children) {
|
|
29
25
|
cb(node, parent);
|
|
30
26
|
if (isParent(node)) {
|
|
31
|
-
walkElements(node, cb);
|
|
27
|
+
walkElements(node, code, cb);
|
|
32
28
|
}
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
31
|
exports.walkElements = walkElements;
|
|
36
32
|
/** walk nodes */
|
|
37
|
-
function walk(parent, enter, leave) {
|
|
38
|
-
|
|
39
|
-
if (parent.type === "root" && children.every((n) => n.position)) {
|
|
40
|
-
// The order of comments and frontmatter may be changed.
|
|
41
|
-
children = [...children].sort((a, b) => a.position.start.offset - b.position.start.offset);
|
|
42
|
-
}
|
|
33
|
+
function walk(parent, code, enter, leave) {
|
|
34
|
+
const children = getSortedChildren(parent, code);
|
|
43
35
|
for (const node of children) {
|
|
44
36
|
enter(node, parent);
|
|
45
37
|
if (isTag(node)) {
|
|
@@ -49,7 +41,7 @@ function walk(parent, enter, leave) {
|
|
|
49
41
|
}
|
|
50
42
|
}
|
|
51
43
|
if (isParent(node)) {
|
|
52
|
-
walk(node, enter, leave);
|
|
44
|
+
walk(node, code, enter, leave);
|
|
53
45
|
}
|
|
54
46
|
leave === null || leave === void 0 ? void 0 : leave(node, parent);
|
|
55
47
|
}
|
|
@@ -186,3 +178,41 @@ function skipSpaces(string, position) {
|
|
|
186
178
|
return position;
|
|
187
179
|
}
|
|
188
180
|
exports.skipSpaces = skipSpaces;
|
|
181
|
+
/**
|
|
182
|
+
* Get children
|
|
183
|
+
*/
|
|
184
|
+
function getSortedChildren(parent, code) {
|
|
185
|
+
var _a;
|
|
186
|
+
if (parent.type === "root" && ((_a = parent.children[0]) === null || _a === void 0 ? void 0 : _a.type) === "frontmatter") {
|
|
187
|
+
// The order of comments and frontmatter may be changed.
|
|
188
|
+
const children = [...parent.children];
|
|
189
|
+
if (children.every((n) => n.position)) {
|
|
190
|
+
return children.sort((a, b) => a.position.start.offset - b.position.start.offset);
|
|
191
|
+
}
|
|
192
|
+
let start = skipSpaces(code, 0);
|
|
193
|
+
if (code.startsWith("<!", start)) {
|
|
194
|
+
const frontmatter = children.shift();
|
|
195
|
+
const before = [];
|
|
196
|
+
let first;
|
|
197
|
+
while ((first = children.shift())) {
|
|
198
|
+
start = skipSpaces(code, start);
|
|
199
|
+
if (first.type === "comment" &&
|
|
200
|
+
code.startsWith("<!--", start)) {
|
|
201
|
+
start = code.indexOf("-->", start + 4) + 3;
|
|
202
|
+
before.push(first);
|
|
203
|
+
}
|
|
204
|
+
else if (first.type === "doctype" &&
|
|
205
|
+
code.startsWith("<!", start)) {
|
|
206
|
+
start = code.indexOf(">", start + 2) + 1;
|
|
207
|
+
before.push(first);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
children.unshift(first);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return [...before, frontmatter, ...children];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return parent.children;
|
|
218
|
+
}
|
|
@@ -41,7 +41,7 @@ exports.parse = parse;
|
|
|
41
41
|
function fixLocations(node, code) {
|
|
42
42
|
// FIXME: Adjust because the parser does not return the correct location.
|
|
43
43
|
let start = 0;
|
|
44
|
-
(0, astro_1.walk)(node, (node) => {
|
|
44
|
+
(0, astro_1.walk)(node, code, (node) => {
|
|
45
45
|
if (node.type === "frontmatter") {
|
|
46
46
|
start = node.position.start.offset = tokenIndex(code, "---", start);
|
|
47
47
|
start = node.position.end.offset =
|
|
@@ -81,6 +81,9 @@ function fixLocations(node, code) {
|
|
|
81
81
|
if (!node.position) {
|
|
82
82
|
node.position = { start: {}, end: {} };
|
|
83
83
|
}
|
|
84
|
+
if (!node.position.end) {
|
|
85
|
+
node.position.end = {};
|
|
86
|
+
}
|
|
84
87
|
start = node.position.start.offset = tokenIndex(code, "<!", start);
|
|
85
88
|
start += 2;
|
|
86
89
|
start = node.position.end.offset =
|
|
@@ -104,9 +107,6 @@ function fixLocations(node, code) {
|
|
|
104
107
|
node.type === "element" ||
|
|
105
108
|
node.type === "component" ||
|
|
106
109
|
node.type === "custom-element") {
|
|
107
|
-
if (!node.position.end) {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
110
|
const closeTagStart = tokenIndexSafe(code, `</${node.name}`, start);
|
|
111
111
|
if (closeTagStart != null) {
|
|
112
112
|
start = closeTagStart + 2 + node.name.length;
|
|
@@ -16,7 +16,7 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
16
16
|
fragmentOpened = true;
|
|
17
17
|
}
|
|
18
18
|
// eslint-disable-next-line complexity -- X(
|
|
19
|
-
(0, astro_1.walkElements)(resultTemplate.ast, (node, parent) => {
|
|
19
|
+
(0, astro_1.walkElements)(resultTemplate.ast, ctx.code, (node, parent) => {
|
|
20
20
|
if (node.type === "frontmatter") {
|
|
21
21
|
const start = node.position.start.offset;
|
|
22
22
|
script.appendOriginal(start);
|
|
@@ -147,8 +147,10 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
147
147
|
script.addRestoreNodeProcess((scriptNode) => {
|
|
148
148
|
if (scriptNode.type === types_1.AST_NODE_TYPES.JSXElement &&
|
|
149
149
|
scriptNode.range[0] === styleNodeStart) {
|
|
150
|
-
const textNode = Object.assign({ type:
|
|
151
|
-
scriptNode.children = [
|
|
150
|
+
const textNode = Object.assign({ type: "AstroRawText", value: text.value, raw: text.value, parent: scriptNode }, ctx.getLocations(start, start + text.value.length));
|
|
151
|
+
scriptNode.children = [
|
|
152
|
+
textNode,
|
|
153
|
+
];
|
|
152
154
|
return true;
|
|
153
155
|
}
|
|
154
156
|
return false;
|
package/lib/visitor-keys.js
CHANGED