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 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
- #### [@ota-meshi/eslint-plugin-astro](https://ota-meshi.github.io/eslint-plugin-astro/)
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/ota-meshi/astro.build/commit/7f291ac15e6d97cc20a64b8f97dcbd85379759b5) is an example of introducing this parser to the `astro.build` repository.
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/
@@ -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 = __importDefault(require("module"));
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
- bodyElement.children.splice(bodyElement.children.indexOf(child), 1);
83
- if (htmlEnd <= offset) {
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
  }
@@ -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 removeFile = null;
11
+ let patchResult;
15
12
  try {
16
- const scriptParserOptions = { ...parserOptions.parserOptions };
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
- scriptParserOptions.filePath += ".tsx";
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
- if (removeFile)
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.1.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.14.3",
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.15",
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.6.0"
95
+ "typescript": "~4.7.0"
95
96
  }
96
97
  }