astro-eslint-parser 0.0.1 → 0.0.4
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 +20 -4
- package/lib/ast.d.ts +6 -1
- package/lib/context/script.js +5 -5
- package/lib/parser/astro-parser/parse.js +21 -18
- package/lib/parser/process-template.js +62 -8
- package/lib/visitor-keys.js +1 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -11,9 +11,14 @@ You can check it on [Online DEMO](https://ota-meshi.github.io/astro-eslint-parse
|
|
|
11
11
|
[](http://www.npmtrends.com/astro-eslint-parser)
|
|
12
12
|
[](http://www.npmtrends.com/astro-eslint-parser)
|
|
13
13
|
[](https://github.com/ota-meshi/astro-eslint-parser/actions?query=workflow%3ACI)
|
|
14
|
+
[](https://coveralls.io/github/ota-meshi/astro-eslint-parser?branch=main)
|
|
14
15
|
|
|
15
16
|
This parser is in the ***experimental stages*** of development.
|
|
16
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
|
+
|
|
17
22
|
<!--
|
|
18
23
|
### ESLint Plugins Using astro-eslint-parser
|
|
19
24
|
|
|
@@ -74,7 +79,7 @@ For example:
|
|
|
74
79
|
|
|
75
80
|
### parserOptions.parser
|
|
76
81
|
|
|
77
|
-
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.
|
|
78
83
|
Other properties than parser would be given to the specified parser.
|
|
79
84
|
For example:
|
|
80
85
|
|
|
@@ -87,7 +92,7 @@ For example:
|
|
|
87
92
|
}
|
|
88
93
|
```
|
|
89
94
|
|
|
90
|
-
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.
|
|
91
96
|
|
|
92
97
|
```js
|
|
93
98
|
module.exports = {
|
|
@@ -96,13 +101,13 @@ module.exports = {
|
|
|
96
101
|
parserOptions: {
|
|
97
102
|
// ...
|
|
98
103
|
project: "path/to/your/tsconfig.json",
|
|
99
|
-
extraFileExtensions: [".astro"], // This is a required setting in `@typescript-eslint/parser`
|
|
104
|
+
extraFileExtensions: [".astro"], // This is a required setting in `@typescript-eslint/parser` v5.
|
|
100
105
|
},
|
|
101
106
|
overrides: [
|
|
102
107
|
{
|
|
103
108
|
files: ["*.astro"],
|
|
104
109
|
parser: "astro-eslint-parser",
|
|
105
|
-
// Parse the
|
|
110
|
+
// Parse the script in `.astro` as TypeScript by adding the following configuration.
|
|
106
111
|
parserOptions: {
|
|
107
112
|
parser: "@typescript-eslint/parser",
|
|
108
113
|
},
|
|
@@ -133,9 +138,20 @@ Example **.vscode/settings.json**:
|
|
|
133
138
|
}
|
|
134
139
|
```
|
|
135
140
|
|
|
141
|
+
## Compatibility With Existing ESLint Rules
|
|
142
|
+
|
|
143
|
+
Most of the rules in the ESLint core work for the script part, but some rules are incompatible.
|
|
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.
|
|
145
|
+
For example, the [react/jsx-no-target-blank] rule works fine.
|
|
146
|
+
|
|
147
|
+
[semi]: https://eslint.org/docs/rules/semi
|
|
148
|
+
[eslint-plugin-react]: https://github.com/jsx-eslint/eslint-plugin-react/
|
|
149
|
+
[react/jsx-no-target-blank]: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-target-blank.md
|
|
150
|
+
|
|
136
151
|
## Usage for Custom Rules / Plugins
|
|
137
152
|
|
|
138
153
|
- TBA
|
|
154
|
+
- You can check it on the [Online DEMO](https://ota-meshi.github.io/astro-eslint-parser/). However, AST is subject to major changes in the future.
|
|
139
155
|
|
|
140
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/). -->
|
|
141
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/context/script.js
CHANGED
|
@@ -46,6 +46,11 @@ class ScriptContext {
|
|
|
46
46
|
if (last.expression.type !== "JSXFragment") {
|
|
47
47
|
throw new errors_1.ParseError("Unknown state error: Expected JSXFragment", last.expression.range[0], this.ctx);
|
|
48
48
|
}
|
|
49
|
+
// Process for Astro
|
|
50
|
+
const rootFragment = (result.ast.body[result.ast.body.length - 1] = last.expression);
|
|
51
|
+
delete rootFragment.closingFragment;
|
|
52
|
+
delete rootFragment.openingFragment;
|
|
53
|
+
rootFragment.type = "AstroRootFragment";
|
|
49
54
|
// remap locations
|
|
50
55
|
const traversed = new Set();
|
|
51
56
|
(0, traverse_1.traverseNodes)(result.ast, {
|
|
@@ -72,11 +77,6 @@ class ScriptContext {
|
|
|
72
77
|
for (const token of result.ast.comments || []) {
|
|
73
78
|
this.remapLocation(token);
|
|
74
79
|
}
|
|
75
|
-
// Process for Astro
|
|
76
|
-
delete last.expression.closingFragment;
|
|
77
|
-
delete last.expression.openingFragment;
|
|
78
|
-
last.expression.type =
|
|
79
|
-
"AstroRootFragment";
|
|
80
80
|
let restoreNodeProcesses = this.restoreNodeProcesses;
|
|
81
81
|
for (const node of traversed) {
|
|
82
82
|
restoreNodeProcesses = restoreNodeProcesses.filter((proc) => !proc(node, result));
|
|
@@ -41,9 +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,
|
|
45
|
-
// eslint-disable-next-line complexity -- ignore
|
|
46
|
-
(node) => {
|
|
44
|
+
(0, astro_1.walk)(node, (node) => {
|
|
47
45
|
if (node.type === "frontmatter") {
|
|
48
46
|
start = node.position.start.offset = tokenIndex(code, "---", start);
|
|
49
47
|
start = node.position.end.offset =
|
|
@@ -58,11 +56,7 @@ function fixLocations(node, code) {
|
|
|
58
56
|
}
|
|
59
57
|
start = node.position.start.offset = tokenIndex(code, "<", start);
|
|
60
58
|
start += 1;
|
|
61
|
-
|
|
62
|
-
node.type === "component" ||
|
|
63
|
-
node.type === "custom-element") {
|
|
64
|
-
start += node.name.length;
|
|
65
|
-
}
|
|
59
|
+
start += node.name.length;
|
|
66
60
|
if (!node.attributes.length) {
|
|
67
61
|
start = (0, astro_1.getStartTagEndOffset)(node, code);
|
|
68
62
|
}
|
|
@@ -106,20 +100,18 @@ function fixLocations(node, code) {
|
|
|
106
100
|
if (node.type === "expression") {
|
|
107
101
|
start = tokenIndex(code, "}", start) + 1;
|
|
108
102
|
}
|
|
109
|
-
else if (node.type === "fragment"
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
else if (node.type === "element" ||
|
|
103
|
+
else if (node.type === "fragment" ||
|
|
104
|
+
node.type === "element" ||
|
|
113
105
|
node.type === "component" ||
|
|
114
106
|
node.type === "custom-element") {
|
|
115
107
|
if (!node.position.end) {
|
|
116
108
|
return;
|
|
117
109
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
110
|
+
const closeTagStart = tokenIndexSafe(code, `</${node.name}`, start);
|
|
111
|
+
if (closeTagStart != null) {
|
|
112
|
+
start = closeTagStart + 2 + node.name.length;
|
|
113
|
+
start = tokenIndex(code, ">", start) + 1;
|
|
114
|
+
}
|
|
123
115
|
}
|
|
124
116
|
else {
|
|
125
117
|
return;
|
|
@@ -159,9 +151,20 @@ function fixLocationForAttr(node, code, start) {
|
|
|
159
151
|
* Get token index
|
|
160
152
|
*/
|
|
161
153
|
function tokenIndex(string, token, position) {
|
|
154
|
+
const index = tokenIndexSafe(string, token, position);
|
|
155
|
+
if (index == null) {
|
|
156
|
+
const start = token.trim() === token ? (0, astro_1.skipSpaces)(string, position) : position;
|
|
157
|
+
throw new Error(`Unknown token at ${start}, expected: ${JSON.stringify(token)}, actual: ${JSON.stringify(string.slice(start, start + 10))}`);
|
|
158
|
+
}
|
|
159
|
+
return index;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get token index
|
|
163
|
+
*/
|
|
164
|
+
function tokenIndexSafe(string, token, position) {
|
|
162
165
|
const index = token.trim() === token ? (0, astro_1.skipSpaces)(string, position) : position;
|
|
163
166
|
if (string.startsWith(token, index)) {
|
|
164
167
|
return index;
|
|
165
168
|
}
|
|
166
|
-
|
|
169
|
+
return null;
|
|
167
170
|
}
|
|
@@ -15,6 +15,7 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
15
15
|
script.appendScript("<>");
|
|
16
16
|
fragmentOpened = true;
|
|
17
17
|
}
|
|
18
|
+
// eslint-disable-next-line complexity -- X(
|
|
18
19
|
(0, astro_1.walkElements)(resultTemplate.ast, (node, parent) => {
|
|
19
20
|
if (node.type === "frontmatter") {
|
|
20
21
|
const start = node.position.start.offset;
|
|
@@ -45,6 +46,56 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
45
46
|
}
|
|
46
47
|
else if ((0, astro_1.isTag)(node)) {
|
|
47
48
|
for (const attr of node.attributes) {
|
|
49
|
+
if ((node.type === "component" || node.type === "fragment") &&
|
|
50
|
+
(attr.kind === "quoted" ||
|
|
51
|
+
attr.kind === "empty" ||
|
|
52
|
+
attr.kind === "expression" ||
|
|
53
|
+
attr.kind === "template-literal")) {
|
|
54
|
+
const colonIndex = attr.name.indexOf(":");
|
|
55
|
+
if (colonIndex >= 0) {
|
|
56
|
+
const start = attr.position.start.offset;
|
|
57
|
+
script.appendOriginal(start + colonIndex);
|
|
58
|
+
script.skipOriginalOffset(1);
|
|
59
|
+
script.appendScript(`_`);
|
|
60
|
+
script.addToken(types_1.AST_TOKEN_TYPES.JSXIdentifier, [
|
|
61
|
+
start,
|
|
62
|
+
start + colonIndex,
|
|
63
|
+
]);
|
|
64
|
+
script.addToken(types_1.AST_TOKEN_TYPES.Punctuator, [
|
|
65
|
+
start + colonIndex,
|
|
66
|
+
start + colonIndex + 1,
|
|
67
|
+
]);
|
|
68
|
+
script.addToken(types_1.AST_TOKEN_TYPES.JSXIdentifier, [
|
|
69
|
+
start + colonIndex + 1,
|
|
70
|
+
start + attr.name.length,
|
|
71
|
+
]);
|
|
72
|
+
script.addRestoreNodeProcess((scriptNode, result) => {
|
|
73
|
+
if (scriptNode.type ===
|
|
74
|
+
types_1.AST_NODE_TYPES.JSXAttribute &&
|
|
75
|
+
scriptNode.range[0] === start) {
|
|
76
|
+
const baseNameNode = scriptNode.name;
|
|
77
|
+
const nsn = Object.assign(Object.assign({}, baseNameNode), { type: types_1.AST_NODE_TYPES.JSXNamespacedName, namespace: Object.assign({ type: types_1.AST_NODE_TYPES.JSXIdentifier, name: attr.name.slice(0, colonIndex) }, ctx.getLocations(baseNameNode.range[0], baseNameNode.range[0] + colonIndex)), name: Object.assign({ type: types_1.AST_NODE_TYPES.JSXIdentifier, name: attr.name.slice(colonIndex + 1) }, ctx.getLocations(baseNameNode.range[0] +
|
|
78
|
+
colonIndex +
|
|
79
|
+
1, baseNameNode.range[1])) });
|
|
80
|
+
scriptNode.name = nsn;
|
|
81
|
+
nsn.namespace.parent = nsn;
|
|
82
|
+
nsn.name.parent = nsn;
|
|
83
|
+
const tokens = result.ast.tokens || [];
|
|
84
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
85
|
+
const token = tokens[index];
|
|
86
|
+
if (token.range[0] ===
|
|
87
|
+
baseNameNode.range[0] &&
|
|
88
|
+
token.range[1] === baseNameNode.range[1]) {
|
|
89
|
+
tokens.splice(index, 1);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
48
99
|
if (attr.kind === "shorthand") {
|
|
49
100
|
const start = attr.position.start.offset;
|
|
50
101
|
script.appendOriginal(start);
|
|
@@ -96,8 +147,10 @@ function processTemplate(ctx, resultTemplate) {
|
|
|
96
147
|
script.addRestoreNodeProcess((scriptNode) => {
|
|
97
148
|
if (scriptNode.type === types_1.AST_NODE_TYPES.JSXElement &&
|
|
98
149
|
scriptNode.range[0] === styleNodeStart) {
|
|
99
|
-
const textNode = Object.assign({ type:
|
|
100
|
-
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
|
+
];
|
|
101
154
|
return true;
|
|
102
155
|
}
|
|
103
156
|
return false;
|
|
@@ -181,10 +234,9 @@ exports.processTemplate = processTemplate;
|
|
|
181
234
|
* If the given tag is a void tag, get the self-closing tag.
|
|
182
235
|
*/
|
|
183
236
|
function getVoidSelfClosingTag(node, parent, ctx) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (node.children.length > 0) {
|
|
237
|
+
var _a;
|
|
238
|
+
const children = node.children.filter((c) => c.type !== "text" || c.value.trim());
|
|
239
|
+
if (children.length > 0) {
|
|
188
240
|
return false;
|
|
189
241
|
}
|
|
190
242
|
const code = ctx.code;
|
|
@@ -192,8 +244,10 @@ function getVoidSelfClosingTag(node, parent, ctx) {
|
|
|
192
244
|
const childIndex = parent.children.indexOf(node);
|
|
193
245
|
if (childIndex === parent.children.length - 1) {
|
|
194
246
|
// last
|
|
195
|
-
|
|
196
|
-
|
|
247
|
+
if ((_a = parent.position) === null || _a === void 0 ? void 0 : _a.end) {
|
|
248
|
+
nextElementIndex = parent.position.end.offset;
|
|
249
|
+
nextElementIndex = code.lastIndexOf("</", nextElementIndex);
|
|
250
|
+
}
|
|
197
251
|
}
|
|
198
252
|
else {
|
|
199
253
|
const next = parent.children[childIndex + 1];
|
package/lib/visitor-keys.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-eslint-parser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Astro parser for ESLint",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"benchmark": "^2.1.4",
|
|
63
63
|
"chai": "^4.3.4",
|
|
64
64
|
"code-red": "^0.2.3",
|
|
65
|
-
"eslint": "^8.
|
|
65
|
+
"eslint": "^8.14.0",
|
|
66
66
|
"eslint-config-prettier": "^8.3.0",
|
|
67
67
|
"eslint-formatter-codeframe": "^7.32.1",
|
|
68
68
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
@@ -71,12 +71,13 @@
|
|
|
71
71
|
"eslint-plugin-node": "^11.1.0",
|
|
72
72
|
"eslint-plugin-node-dependencies": "^0.8.0",
|
|
73
73
|
"eslint-plugin-prettier": "^4.0.0",
|
|
74
|
+
"eslint-plugin-react": "^7.29.4",
|
|
74
75
|
"eslint-plugin-regexp": "^1.5.0",
|
|
75
76
|
"eslint-plugin-vue": "^8.0.3",
|
|
76
77
|
"estree-walker": "^3.0.0",
|
|
77
78
|
"locate-character": "^2.0.5",
|
|
78
79
|
"magic-string": "^0.26.0",
|
|
79
|
-
"mocha": "^
|
|
80
|
+
"mocha": "^10.0.0",
|
|
80
81
|
"mocha-chai-jest-snapshot": "^1.1.3",
|
|
81
82
|
"nyc": "^15.1.0",
|
|
82
83
|
"prettier": "^2.0.5",
|