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 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
+ [![sponsors](https://img.shields.io/badge/-Sponsor-fafbfc?logo=GitHub%20Sponsors)](https://github.com/sponsors/ota-meshi)
7
+
6
8
  [![NPM license](https://img.shields.io/npm/l/astro-eslint-parser.svg)](https://www.npmjs.com/package/astro-eslint-parser)
7
9
  [![NPM version](https://img.shields.io/npm/v/astro-eslint-parser.svg)](https://www.npmjs.com/package/astro-eslint-parser)
8
10
  [![NPM downloads](https://img.shields.io/badge/dynamic/json.svg?label=downloads&colorB=green&suffix=/day&query=$.downloads&uri=https://api.npmjs.org//downloads/point/last-day/astro-eslint-parser&maxAge=3600)](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
+ [![sponsors](https://img.shields.io/badge/-Sponsor-fafbfc?logo=GitHub%20Sponsors)](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 | AstroRootFragment | AstroHTMLComment | AstroDoctype | AstroShorthandAttribute | AstroTemplateLiteralAttribute | AstroRawText | AstroFragment;
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] | AstroRootFragment | AstroHTMLComment)[];
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 root */
13
- export interface AstroRootFragment extends Omit<TSESTree.BaseNode, "type" | "parent"> {
14
- type: "AstroRootFragment";
15
- children: TSESTree.JSXFragment["children"];
16
- parent: AstroProgram;
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: AstroRootFragment | TSESTree.JSXElement | TSESTree.JSXFragment;
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: AstroRootFragment;
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: AstroRootFragment | TSESTree.JSXElement | TSESTree.JSXFragment;
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
  }
@@ -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
- const last = result.ast.body[result.ast.body.length - 1];
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 first = result.ast.body[0];
114
- if (first.range[0] < result.ast.range[0]) {
115
- result.ast.range[0] = first.range[0];
116
- result.ast.loc.start = this.ctx.getLocFromIndex(result.ast.range[0]);
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
- if (!frontmatter) {
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
- let targetType;
214
- if (fragmentOpened) {
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 === targetType) {
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
- if (targetType === types_1.AST_NODE_TYPES.JSXFragment) {
236
- context.addRemoveToken((token) => token.value === "<" &&
237
- token.range[0] === scriptNode.range[0]);
238
- context.addRemoveToken((token) => token.value === ">" &&
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
- let targetType;
256
- if (fragmentOpened) {
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 === targetType) {
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
- if (targetType === types_1.AST_NODE_TYPES.JSXFragment) {
277
- context.addRemoveToken((token) => token.value === "<" &&
278
- token.range[0] === scriptNode.range[0]);
279
- context.addRemoveToken((token) => token.value === ">" &&
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
@@ -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
- AstroRootFragment: ["children"],
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.16",
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": "^8.0.1"
91
+ "vue-eslint-parser": "^9.0.0"
92
92
  }
93
93
  }