astro-eslint-parser 0.1.0 → 0.2.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 +21 -8
- package/lib/ast/astro.d.ts +1 -1
- package/lib/ast/jsx.d.ts +2 -2
- package/lib/context/resolve-parser/espree.js +2 -17
- package/lib/parser/astro-parser/parse.js +12 -6
- package/lib/parser/script.js +11 -13
- package/lib/parser/ts-patch.d.ts +8 -0
- package/lib/parser/ts-patch.js +58 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -21,14 +21,17 @@ At least it works fine with a [fork of the `astro.build` repository](https://git
|
|
|
21
21
|
|
|
22
22
|
[@astrojs/compiler]: https://github.com/withastro/compiler
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
## :checkered_flag: Motivation
|
|
25
|
+
|
|
26
|
+
This parser allows us to lint the script of `.astro` files.
|
|
27
|
+
|
|
28
|
+
> Note that this parser alone will not lint the scripts inside the `<script>` tag. Use [eslint-plugin-astro] to lint the script inside the `<script>` tag as well.
|
|
29
|
+
|
|
25
30
|
### ESLint Plugins Using astro-eslint-parser
|
|
26
31
|
|
|
27
|
-
#### [
|
|
32
|
+
#### [eslint-plugin-astro]
|
|
28
33
|
|
|
29
34
|
ESLint plugin for Astro component.
|
|
30
|
-
It provides many unique check rules by using the template AST.
|
|
31
|
-
-->
|
|
32
35
|
|
|
33
36
|
## 💿 Installation
|
|
34
37
|
|
|
@@ -60,7 +63,7 @@ npm install --save-dev eslint astro-eslint-parser
|
|
|
60
63
|
$ eslint src --ext .js,.astro
|
|
61
64
|
```
|
|
62
65
|
|
|
63
|
-
The commit diff [here](https://github.com/
|
|
66
|
+
The commit diff [here](https://github.com/withastro/astro.build/compare/main...ota-meshi:eslint) is an example of introducing this parser to the `astro.build` repository.
|
|
64
67
|
|
|
65
68
|
## 🔧 Options
|
|
66
69
|
|
|
@@ -152,13 +155,21 @@ For example, the [react/jsx-no-target-blank] rule works fine.
|
|
|
152
155
|
[eslint-plugin-react]: https://github.com/jsx-eslint/eslint-plugin-react/
|
|
153
156
|
[react/jsx-no-target-blank]: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-target-blank.md
|
|
154
157
|
|
|
158
|
+
## :ghost: Limitations
|
|
159
|
+
|
|
160
|
+
If this parser is used with `@typescript-eslint/parser` and `parserOptions.project` is set, it will temporarily create a `.tsx` file to parse the `.astro` file.
|
|
161
|
+
This parser works by converting the `.astro` file to JSX and letting the JavaScript parser parse it.
|
|
162
|
+
Since `@typescript-eslint/parser` can only parse files with the extension `.tsx` as JSX, it is necessary to temporarily create a `.tsx` file. Temporarily created files will try to be deleted after parses, but if the parsing takes a long time, the files may be visible to you.
|
|
163
|
+
|
|
164
|
+
See also [`@typescript-eslint/parser` readme](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser#parseroptionsecmafeaturesjsx).
|
|
165
|
+
|
|
155
166
|
## :hammer_and_wrench: Usage for Custom Rules / Plugins
|
|
156
167
|
|
|
168
|
+
<!-- - [AST.md](./docs/AST.md) is AST specification. You can check it on the [Online DEMO](https://ota-meshi.github.io/astro-eslint-parser/). -->
|
|
169
|
+
|
|
157
170
|
- TBA
|
|
158
171
|
- 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.
|
|
159
|
-
|
|
160
|
-
<!-- - [AST.md](./docs/AST.md) is AST specification. You can check it on the [Online DEMO](https://ota-meshi.github.io/astro-eslint-parser/). -->
|
|
161
|
-
<!-- - I have already [implemented some rules] in the [`@ota-meshi/eslint-plugin-astro`]. The source code for these rules will be helpful to you. -->
|
|
172
|
+
- I have already [implemented some rules] in the [eslint-plugin-astro]. The source code for these rules will be helpful to you.
|
|
162
173
|
|
|
163
174
|
## :beers: Contributing
|
|
164
175
|
|
|
@@ -178,3 +189,5 @@ See the [LICENSE](LICENSE) file for license rights and limitations (MIT).
|
|
|
178
189
|
|
|
179
190
|
[Astro]: https://astro.build/
|
|
180
191
|
[ESLint]: https://eslint.org/
|
|
192
|
+
[eslint-plugin-astro]: https://ota-meshi.github.io/eslint-plugin-astro/
|
|
193
|
+
[implemented some rules]: https://ota-meshi.github.io/eslint-plugin-astro/rules/
|
package/lib/ast/astro.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { JSXAttribute, JSXElement, JSXExpression, JSXExpressionContainer, J
|
|
|
2
2
|
import type { TSESTree as ES } from "@typescript-eslint/types";
|
|
3
3
|
import type { BaseNode } from "./base";
|
|
4
4
|
export declare type AstroNode = AstroProgram | AstroFragment | AstroHTMLComment | AstroDoctype | AstroShorthandAttribute | AstroTemplateLiteralAttribute | AstroRawText;
|
|
5
|
-
export declare type AstroChild = JSXElement | JSXFragment | JSXExpression | JSXText | AstroHTMLComment;
|
|
5
|
+
export declare type AstroChild = JSXElement | JSXFragment | JSXExpression | JSXText | AstroHTMLComment | AstroRawText;
|
|
6
6
|
export declare type AstroParentNode = JSXElement | JSXFragment | AstroFragment;
|
|
7
7
|
/** Node of Astro program root */
|
|
8
8
|
export interface AstroProgram extends Omit<ES.Program, "type" | "body"> {
|
package/lib/ast/jsx.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { TSESTree as ES } from "@typescript-eslint/types";
|
|
2
|
-
import type { AstroFragment, AstroHTMLComment, AstroShorthandAttribute, AstroTemplateLiteralAttribute } from "./astro";
|
|
2
|
+
import type { AstroFragment, AstroHTMLComment, AstroRawText, AstroShorthandAttribute, AstroTemplateLiteralAttribute } from "./astro";
|
|
3
3
|
import type { BaseNode } from "./base";
|
|
4
4
|
export declare type JSXNode = JSXAttribute | JSXClosingElement | JSXClosingFragment | JSXElement | JSXEmptyExpression | JSXExpressionContainer | JSXFragment | JSXIdentifier | JSXMemberExpression | JSXNamespacedName | JSXOpeningElement | JSXOpeningFragment | JSXSpreadAttribute | JSXSpreadChild | JSXText;
|
|
5
|
-
export declare type JSXChild = JSXElement | JSXFragment | JSXExpression | JSXText | AstroHTMLComment;
|
|
5
|
+
export declare type JSXChild = JSXElement | JSXFragment | JSXExpression | JSXText | AstroHTMLComment | AstroRawText;
|
|
6
6
|
export declare type JSXParentNode = JSXElement | JSXFragment | AstroFragment;
|
|
7
7
|
export interface JSXElement extends BaseNode {
|
|
8
8
|
type: "JSXElement";
|
|
@@ -4,23 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getEspree = void 0;
|
|
7
|
-
const module_1 =
|
|
7
|
+
const module_1 = require("module");
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const createRequire =
|
|
10
|
-
// Added in v12.2.0
|
|
11
|
-
module_1.default.createRequire ||
|
|
12
|
-
// Added in v10.12.0, but deprecated in v12.2.0.
|
|
13
|
-
// @ts-expect-error -- old type
|
|
14
|
-
module_1.default.createRequireFromPath ||
|
|
15
|
-
// Polyfill - This is not executed on the tests on node@>=10.
|
|
16
|
-
/* istanbul ignore next */
|
|
17
|
-
((modName) => {
|
|
18
|
-
const mod = new module_1.default(modName);
|
|
19
|
-
mod.filename = modName;
|
|
20
|
-
mod.paths = module_1.default._nodeModulePaths(path_1.default.dirname(modName));
|
|
21
|
-
mod._compile("module.exports = require;", modName);
|
|
22
|
-
return mod.exports;
|
|
23
|
-
});
|
|
24
9
|
let espreeCache = null;
|
|
25
10
|
/** Checks if given path is linter path */
|
|
26
11
|
function isLinterPath(p) {
|
|
@@ -40,7 +25,7 @@ function getEspree() {
|
|
|
40
25
|
const linterPath = Object.keys(require.cache || {}).find(isLinterPath);
|
|
41
26
|
if (linterPath) {
|
|
42
27
|
try {
|
|
43
|
-
espreeCache = createRequire(linterPath)("espree");
|
|
28
|
+
espreeCache = (0, module_1.createRequire)(linterPath)("espree");
|
|
44
29
|
}
|
|
45
30
|
catch {
|
|
46
31
|
// ignore
|
|
@@ -52,38 +52,44 @@ function adjustHTML(ast, htmlElement, ctx) {
|
|
|
52
52
|
if (htmlEnd == null) {
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
|
+
const hasTokenAfter = Boolean(ctx.code.slice(htmlEnd + 7).trim());
|
|
55
56
|
const children = [...htmlElement.children];
|
|
56
57
|
for (const child of children) {
|
|
57
58
|
const offset = child.position?.start.offset;
|
|
58
|
-
if (offset != null) {
|
|
59
|
+
if (hasTokenAfter && offset != null) {
|
|
59
60
|
if (htmlEnd <= offset) {
|
|
60
61
|
htmlElement.children.splice(htmlElement.children.indexOf(child), 1);
|
|
61
62
|
ast.children.push(child);
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
if (child.type === "element" && child.name === "body") {
|
|
65
|
-
adjustHTMLBody(ast, htmlElement, htmlEnd, child, ctx);
|
|
66
|
+
adjustHTMLBody(ast, htmlElement, htmlEnd, hasTokenAfter, child, ctx);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
70
71
|
* Adjust <body> element node
|
|
71
72
|
*/
|
|
72
|
-
function adjustHTMLBody(ast, htmlElement, htmlEnd, bodyElement, ctx) {
|
|
73
|
+
function adjustHTMLBody(ast, htmlElement, htmlEnd, hasTokenAfterHtmlEnd, bodyElement, ctx) {
|
|
73
74
|
const bodyEnd = ctx.code.indexOf("</body");
|
|
74
75
|
if (bodyEnd == null) {
|
|
75
76
|
return;
|
|
76
77
|
}
|
|
78
|
+
const hasTokenAfter = Boolean(ctx.code.slice(bodyEnd + 7, htmlEnd).trim());
|
|
79
|
+
if (!hasTokenAfter && !hasTokenAfterHtmlEnd) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
77
82
|
const children = [...bodyElement.children];
|
|
78
83
|
for (const child of children) {
|
|
79
84
|
const offset = child.position?.start.offset;
|
|
80
85
|
if (offset != null) {
|
|
81
86
|
if (bodyEnd <= offset) {
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
if (hasTokenAfterHtmlEnd && htmlEnd <= offset) {
|
|
88
|
+
bodyElement.children.splice(bodyElement.children.indexOf(child), 1);
|
|
84
89
|
ast.children.push(child);
|
|
85
90
|
}
|
|
86
|
-
else {
|
|
91
|
+
else if (hasTokenAfter) {
|
|
92
|
+
bodyElement.children.splice(bodyElement.children.indexOf(child), 1);
|
|
87
93
|
htmlElement.children.push(child);
|
|
88
94
|
}
|
|
89
95
|
}
|
package/lib/parser/script.js
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.parseScript = void 0;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
4
|
const debug_1 = require("../debug");
|
|
5
|
+
const ts_patch_1 = require("./ts-patch");
|
|
9
6
|
/**
|
|
10
7
|
* Parse for script
|
|
11
8
|
*/
|
|
12
9
|
function parseScript(code, _ctx, parserOptions) {
|
|
13
10
|
const parser = parserOptions.getParser();
|
|
14
|
-
let
|
|
11
|
+
let patchResult;
|
|
15
12
|
try {
|
|
16
|
-
const scriptParserOptions = {
|
|
13
|
+
const scriptParserOptions = {
|
|
14
|
+
...parserOptions.parserOptions,
|
|
15
|
+
};
|
|
16
|
+
scriptParserOptions.ecmaFeatures = {
|
|
17
|
+
...(scriptParserOptions.ecmaFeatures || {}),
|
|
18
|
+
jsx: true,
|
|
19
|
+
};
|
|
17
20
|
if (parserOptions.isTypeScript() &&
|
|
18
21
|
scriptParserOptions.filePath &&
|
|
19
22
|
scriptParserOptions.project) {
|
|
20
|
-
|
|
21
|
-
if (!fs_1.default.existsSync(scriptParserOptions.filePath)) {
|
|
22
|
-
fs_1.default.writeFileSync(scriptParserOptions.filePath, "/* temp for astro-eslint-parser */");
|
|
23
|
-
removeFile = scriptParserOptions.filePath;
|
|
24
|
-
}
|
|
23
|
+
patchResult = (0, ts_patch_1.tsPatch)(scriptParserOptions);
|
|
25
24
|
}
|
|
26
25
|
const result = parser.parseForESLint?.(code, scriptParserOptions) ??
|
|
27
26
|
parser.parse?.(code, scriptParserOptions);
|
|
@@ -37,8 +36,7 @@ ${code}`);
|
|
|
37
36
|
throw e;
|
|
38
37
|
}
|
|
39
38
|
finally {
|
|
40
|
-
|
|
41
|
-
fs_1.default.unlinkSync(removeFile);
|
|
39
|
+
patchResult?.terminate();
|
|
42
40
|
}
|
|
43
41
|
}
|
|
44
42
|
exports.parseScript = parseScript;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ParserOptions } from "@typescript-eslint/types";
|
|
2
|
+
export declare type PatchTerminate = {
|
|
3
|
+
terminate: () => void;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Apply a patch to parse .astro files as TSX.
|
|
7
|
+
*/
|
|
8
|
+
export declare function tsPatch(scriptParserOptions: ParserOptions): PatchTerminate | null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.tsPatch = void 0;
|
|
7
|
+
const module_1 = require("module");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
/**
|
|
11
|
+
* Apply a patch to parse .astro files as TSX.
|
|
12
|
+
*/
|
|
13
|
+
function tsPatch(scriptParserOptions) {
|
|
14
|
+
try {
|
|
15
|
+
// Apply a patch to parse .astro files as TSX.
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const relativeTo = path_1.default.join(cwd, "__placeholder__.js");
|
|
18
|
+
const ts = (0, module_1.createRequire)(relativeTo)("typescript");
|
|
19
|
+
const { ensureScriptKind, getScriptKindFromFileName } = ts;
|
|
20
|
+
if (typeof ensureScriptKind === "function" &&
|
|
21
|
+
typeof getScriptKindFromFileName === "function") {
|
|
22
|
+
ts.ensureScriptKind = function (fileName, ...args) {
|
|
23
|
+
if (fileName.endsWith(".astro")) {
|
|
24
|
+
return ts.ScriptKind.TSX;
|
|
25
|
+
}
|
|
26
|
+
return ensureScriptKind.call(this, fileName, ...args);
|
|
27
|
+
};
|
|
28
|
+
ts.getScriptKindFromFileName = function (fileName, ...args) {
|
|
29
|
+
if (fileName.endsWith(".astro")) {
|
|
30
|
+
return ts.ScriptKind.TSX;
|
|
31
|
+
}
|
|
32
|
+
return getScriptKindFromFileName.call(this, fileName, ...args);
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
terminate() {
|
|
36
|
+
ts.ensureScriptKind = ensureScriptKind;
|
|
37
|
+
ts.getScriptKindFromFileName = getScriptKindFromFileName;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// ignore
|
|
44
|
+
}
|
|
45
|
+
// If the patch cannot be applied, create a tsx file and parse it.
|
|
46
|
+
const tsxFilePath = `${scriptParserOptions.filePath}.tsx`;
|
|
47
|
+
scriptParserOptions.filePath = tsxFilePath;
|
|
48
|
+
if (!fs_1.default.existsSync(tsxFilePath)) {
|
|
49
|
+
fs_1.default.writeFileSync(tsxFilePath, "/* temp for astro-eslint-parser */");
|
|
50
|
+
return {
|
|
51
|
+
terminate() {
|
|
52
|
+
fs_1.default.unlinkSync(tsxFilePath);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
exports.tsPatch = tsPatch;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-eslint-parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Astro component parser for ESLint",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"homepage": "https://github.com/ota-meshi/astro-eslint-parser#readme",
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@astrojs/compiler": "^0.
|
|
45
|
+
"@astrojs/compiler": "^0.15.2",
|
|
46
46
|
"@typescript-eslint/types": "^5.25.0",
|
|
47
47
|
"debug": "^4.3.4",
|
|
48
48
|
"eslint-visitor-keys": "^3.0.0",
|
|
@@ -63,13 +63,14 @@
|
|
|
63
63
|
"@types/semver": "^7.3.9",
|
|
64
64
|
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
|
65
65
|
"@typescript-eslint/parser": "^5.4.0",
|
|
66
|
-
"astro-eslint-parser": ">=0.0
|
|
66
|
+
"astro-eslint-parser": ">=0.1.0",
|
|
67
67
|
"benchmark": "^2.1.4",
|
|
68
68
|
"chai": "^4.3.4",
|
|
69
69
|
"code-red": "^0.2.3",
|
|
70
70
|
"eslint": "^8.15.0",
|
|
71
71
|
"eslint-config-prettier": "^8.3.0",
|
|
72
72
|
"eslint-formatter-codeframe": "^7.32.1",
|
|
73
|
+
"eslint-plugin-astro": "^0.8.0",
|
|
73
74
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
74
75
|
"eslint-plugin-json-schema-validator": "^3.0.0",
|
|
75
76
|
"eslint-plugin-jsonc": "^2.0.0",
|
|
@@ -91,6 +92,6 @@
|
|
|
91
92
|
"string-replace-loader": "^3.0.3",
|
|
92
93
|
"svelte": "^3.48.0",
|
|
93
94
|
"ts-node": "^10.4.0",
|
|
94
|
-
"typescript": "~4.
|
|
95
|
+
"typescript": "~4.7.0"
|
|
95
96
|
}
|
|
96
97
|
}
|