astro-eslint-parser 0.0.12 → 0.0.15

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/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 | AstroRawText;
2
+ export declare type AstroNode = AstroProgram | AstroRootFragment | AstroHTMLComment | AstroDoctype | AstroShorthandAttribute | AstroTemplateLiteralAttribute | AstroRawText | AstroFragment;
3
3
  /** Node of Astro program root */
4
4
  export interface AstroProgram extends Omit<TSESTree.Program, "type" | "body"> {
5
5
  type: "Program";
@@ -45,3 +45,9 @@ export interface AstroRawText extends Omit<TSESTree.JSXText, "type" | "parent">
45
45
  type: "AstroRawText";
46
46
  parent: AstroRootFragment | TSESTree.JSXElement | TSESTree.JSXFragment;
47
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"];
53
+ }
@@ -1,4 +1,4 @@
1
- import type { AttributeNode, CommentNode, Node, ParentNode, TagLikeNode } from "@astrojs/compiler/types";
1
+ import type { AttributeNode, CommentNode, ExpressionNode, Node, ParentNode, TagLikeNode } from "@astrojs/compiler/types";
2
2
  import type { Context } from "../context";
3
3
  /**
4
4
  * Checks if the given node is TagLikeNode
@@ -9,13 +9,21 @@ export declare function isTag(node: Node): node is Node & TagLikeNode;
9
9
  */
10
10
  export declare function isParent(node: Node): node is ParentNode;
11
11
  /** walk element nodes */
12
- export declare function walkElements(parent: ParentNode, code: string, cb: (n: Node, parent: ParentNode) => void): void;
12
+ export declare function walkElements(parent: ParentNode, code: string, enter: (n: Node, parents: ParentNode[]) => void, leave: (n: Node, parents: ParentNode[]) => void, parents?: ParentNode[]): void;
13
13
  /** walk nodes */
14
- export declare function walk(parent: ParentNode, code: string, enter: (n: Node | AttributeNode, parent: ParentNode) => void, leave?: (n: Node | AttributeNode, parent: ParentNode) => void): void;
14
+ export declare function walk(parent: ParentNode, code: string, enter: (n: Node | AttributeNode, parents: ParentNode[]) => void, leave: (n: Node | AttributeNode, parents: ParentNode[]) => void): void;
15
15
  /**
16
16
  * Get end offset of start tag
17
17
  */
18
18
  export declare function getStartTagEndOffset(node: TagLikeNode, ctx: Context): number;
19
+ /**
20
+ * Get end offset of tag
21
+ */
22
+ export declare function getTagEndOffset(node: TagLikeNode, ctx: Context): number;
23
+ /**
24
+ * Get end offset of Expression
25
+ */
26
+ export declare function getExpressionEndOffset(node: ExpressionNode, ctx: Context): number;
19
27
  /**
20
28
  * Get end offset of attribute
21
29
  */
@@ -28,6 +36,24 @@ export declare function getAttributeValueStartOffset(node: AttributeNode, ctx: C
28
36
  * Get end offset of comment
29
37
  */
30
38
  export declare function getCommentEndOffset(node: CommentNode, ctx: Context): number;
39
+ /**
40
+ * Get content end offset
41
+ */
42
+ export declare function getContentEndOffset(parent: ParentNode, ctx: Context): number;
43
+ /**
44
+ * If the given tag is a self-close tag, get the self-closing tag.
45
+ */
46
+ export declare function getSelfClosingTag(node: TagLikeNode, parent: ParentNode, ctx: Context): null | {
47
+ offset: number;
48
+ end: "/>" | ">";
49
+ };
50
+ /**
51
+ * If the given tag has a end tag, get the end tag.
52
+ */
53
+ export declare function getEndTag(node: TagLikeNode, ctx: Context): null | {
54
+ offset: number;
55
+ tag: string;
56
+ };
31
57
  /**
32
58
  * Skip spaces
33
59
  */
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.skipSpaces = exports.getCommentEndOffset = exports.getAttributeValueStartOffset = exports.getAttributeEndOffset = exports.getStartTagEndOffset = exports.walk = exports.walkElements = exports.isParent = exports.isTag = void 0;
3
+ exports.skipSpaces = exports.getEndTag = exports.getSelfClosingTag = exports.getContentEndOffset = exports.getCommentEndOffset = exports.getAttributeValueStartOffset = exports.getAttributeEndOffset = exports.getExpressionEndOffset = exports.getTagEndOffset = exports.getStartTagEndOffset = exports.walk = exports.walkElements = exports.isParent = exports.isTag = void 0;
4
4
  const errors_1 = require("../errors");
5
5
  /**
6
6
  * Checks if the given node is TagLikeNode
@@ -20,32 +20,30 @@ function isParent(node) {
20
20
  }
21
21
  exports.isParent = isParent;
22
22
  /** walk element nodes */
23
- function walkElements(parent, code, cb) {
23
+ function walkElements(parent, code, enter, leave, parents = []) {
24
24
  const children = getSortedChildren(parent, code);
25
+ const currParents = [parent, ...parents];
25
26
  for (const node of children) {
26
- cb(node, parent);
27
+ enter(node, currParents);
27
28
  if (isParent(node)) {
28
- walkElements(node, code, cb);
29
+ walkElements(node, code, enter, leave, currParents);
29
30
  }
31
+ leave(node, currParents);
30
32
  }
31
33
  }
32
34
  exports.walkElements = walkElements;
33
35
  /** walk nodes */
34
36
  function walk(parent, code, enter, leave) {
35
- const children = getSortedChildren(parent, code);
36
- for (const node of children) {
37
- enter(node, parent);
37
+ walkElements(parent, code, (node, parents) => {
38
+ enter(node, parents);
38
39
  if (isTag(node)) {
40
+ const attrParents = [node, ...parents];
39
41
  for (const attr of node.attributes) {
40
- enter(attr, node);
41
- leave === null || leave === void 0 ? void 0 : leave(attr, node);
42
+ enter(attr, attrParents);
43
+ leave(attr, attrParents);
42
44
  }
43
45
  }
44
- if (isParent(node)) {
45
- walk(node, code, enter, leave);
46
- }
47
- leave === null || leave === void 0 ? void 0 : leave(node, parent);
48
- }
46
+ }, leave);
49
47
  }
50
48
  exports.walk = walk;
51
49
  /**
@@ -65,6 +63,49 @@ function getStartTagEndOffset(node, ctx) {
65
63
  return info.index + info.match.length;
66
64
  }
67
65
  exports.getStartTagEndOffset = getStartTagEndOffset;
66
+ /**
67
+ * Get end offset of tag
68
+ */
69
+ function getTagEndOffset(node, ctx) {
70
+ var _a;
71
+ if (((_a = node.position.end) === null || _a === void 0 ? void 0 : _a.offset) != null) {
72
+ return node.position.end.offset;
73
+ }
74
+ let beforeIndex;
75
+ if (node.children.length) {
76
+ const lastChild = node.children[node.children.length - 1];
77
+ beforeIndex = getEndOffset(lastChild, ctx);
78
+ }
79
+ else {
80
+ beforeIndex = getStartTagEndOffset(node, ctx);
81
+ }
82
+ beforeIndex = skipSpaces(ctx.code, beforeIndex);
83
+ if (ctx.code.startsWith(`</${node.name}`, beforeIndex)) {
84
+ beforeIndex = beforeIndex + 2 + node.name.length;
85
+ const info = getTokenInfo(ctx, [">"], beforeIndex);
86
+ return info.index + info.match.length;
87
+ }
88
+ return beforeIndex;
89
+ }
90
+ exports.getTagEndOffset = getTagEndOffset;
91
+ /**
92
+ * Get end offset of Expression
93
+ */
94
+ function getExpressionEndOffset(node, ctx) {
95
+ var _a;
96
+ if (((_a = node.position.end) === null || _a === void 0 ? void 0 : _a.offset) != null) {
97
+ return node.position.end.offset;
98
+ }
99
+ if (node.children.length) {
100
+ const lastChild = node.children[node.children.length - 1];
101
+ const beforeIndex = getEndOffset(lastChild, ctx);
102
+ const info = getTokenInfo(ctx, ["}"], beforeIndex);
103
+ return info.index + info.match.length;
104
+ }
105
+ const info = getTokenInfo(ctx, ["{", "}"], node.position.start.offset);
106
+ return info.index + info.match.length;
107
+ }
108
+ exports.getExpressionEndOffset = getExpressionEndOffset;
68
109
  /**
69
110
  * Get end offset of attribute
70
111
  */
@@ -122,6 +163,119 @@ function getCommentEndOffset(node, ctx) {
122
163
  return info.index + info.match.length;
123
164
  }
124
165
  exports.getCommentEndOffset = getCommentEndOffset;
166
+ /**
167
+ * Get content end offset
168
+ */
169
+ function getContentEndOffset(parent, ctx) {
170
+ const code = ctx.code;
171
+ if (isTag(parent)) {
172
+ const end = getTagEndOffset(parent, ctx);
173
+ if (code[end - 1] !== ">") {
174
+ return end;
175
+ }
176
+ const index = code.lastIndexOf("</", end - 1);
177
+ if (index >= 0 &&
178
+ code.slice(index + 2, end - 1).trim() === parent.name) {
179
+ return index;
180
+ }
181
+ return end;
182
+ }
183
+ else if (parent.type === "expression") {
184
+ const end = getExpressionEndOffset(parent, ctx);
185
+ return code.lastIndexOf("}", end);
186
+ }
187
+ else if (parent.type === "root") {
188
+ return code.length;
189
+ }
190
+ throw new Error(`unknown type: ${parent.type}`);
191
+ }
192
+ exports.getContentEndOffset = getContentEndOffset;
193
+ /**
194
+ * If the given tag is a self-close tag, get the self-closing tag.
195
+ */
196
+ function getSelfClosingTag(node, parent, ctx) {
197
+ if (node.children.length > 0) {
198
+ return null;
199
+ }
200
+ const code = ctx.code;
201
+ let nextElementIndex = code.length;
202
+ const childIndex = parent.children.indexOf(node);
203
+ if (childIndex === parent.children.length - 1) {
204
+ // last
205
+ nextElementIndex = getContentEndOffset(parent, ctx);
206
+ }
207
+ else {
208
+ const next = parent.children[childIndex + 1];
209
+ nextElementIndex = next.position.start.offset;
210
+ }
211
+ const endOffset = getStartTagEndOffset(node, ctx);
212
+ if (code.slice(endOffset, nextElementIndex).trim()) {
213
+ // has end tag
214
+ return null;
215
+ }
216
+ return {
217
+ offset: endOffset,
218
+ end: code.slice(endOffset - 2, endOffset) === "/>" ? "/>" : ">",
219
+ };
220
+ }
221
+ exports.getSelfClosingTag = getSelfClosingTag;
222
+ /**
223
+ * If the given tag has a end tag, get the end tag.
224
+ */
225
+ function getEndTag(node, ctx) {
226
+ let beforeIndex;
227
+ if (node.children.length) {
228
+ const lastChild = node.children[node.children.length - 1];
229
+ beforeIndex = getEndOffset(lastChild, ctx);
230
+ }
231
+ else {
232
+ beforeIndex = getStartTagEndOffset(node, ctx);
233
+ }
234
+ beforeIndex = skipSpaces(ctx.code, beforeIndex);
235
+ if (ctx.code.startsWith(`</${node.name}`, beforeIndex)) {
236
+ const offset = beforeIndex;
237
+ beforeIndex = beforeIndex + 2 + node.name.length;
238
+ const info = getTokenInfo(ctx, [">"], beforeIndex);
239
+ const end = info.index + info.match.length;
240
+ return {
241
+ offset,
242
+ tag: ctx.code.slice(offset, end),
243
+ };
244
+ }
245
+ return null;
246
+ }
247
+ exports.getEndTag = getEndTag;
248
+ /**
249
+ * Get end offset of tag
250
+ */
251
+ function getEndOffset(node, ctx) {
252
+ var _a;
253
+ if (((_a = node.position.end) === null || _a === void 0 ? void 0 : _a.offset) != null) {
254
+ return node.position.end.offset;
255
+ }
256
+ if (isTag(node))
257
+ return getTagEndOffset(node, ctx);
258
+ if (node.type === "expression")
259
+ return getExpressionEndOffset(node, ctx);
260
+ if (node.type === "comment")
261
+ return getCommentEndOffset(node, ctx);
262
+ if (node.type === "frontmatter") {
263
+ const start = node.position.start.offset;
264
+ return ctx.code.indexOf("---", start + 3) + 3;
265
+ }
266
+ if (node.type === "doctype") {
267
+ const start = node.position.start.offset;
268
+ return ctx.code.indexOf(">", start) + 1;
269
+ }
270
+ if (node.type === "text") {
271
+ const start = node.position.start.offset;
272
+ return start + node.value.length;
273
+ }
274
+ if (node.type === "root") {
275
+ return ctx.code.length;
276
+ }
277
+ throw new Error(`unknown type: ${node.type}`);
278
+ }
125
279
  /**
126
280
  * Get token info
127
281
  */
@@ -9,7 +9,7 @@ export declare class Context {
9
9
  readonly parserOptions: any;
10
10
  readonly locs: LinesAndColumns;
11
11
  private readonly locsMap;
12
- private state;
12
+ private readonly state;
13
13
  constructor(code: string, parserOptions: any);
14
14
  getLocFromIndex(index: number): {
15
15
  line: number;
@@ -29,6 +29,8 @@ export declare class Context {
29
29
  getText(range: TSESTree.Range): string;
30
30
  isTypeScript(): boolean;
31
31
  remapCR({ ast, visitorKeys }: ESLintExtendedProgram): void;
32
+ get originalAST(): any;
33
+ set originalAST(originalAST: any);
32
34
  }
33
35
  export declare class LinesAndColumns {
34
36
  readonly code: string;
@@ -129,6 +129,12 @@ class Context {
129
129
  comment.range = remapRange(comment.range);
130
130
  }
131
131
  }
132
+ get originalAST() {
133
+ return this.state.originalAST;
134
+ }
135
+ set originalAST(originalAST) {
136
+ this.state.originalAST = originalAST;
137
+ }
132
138
  }
133
139
  exports.Context = Context;
134
140
  class LinesAndColumns {
@@ -14,7 +14,7 @@ export declare class ScriptContext {
14
14
  appendOriginal(index: number): void;
15
15
  appendScript(fragment: string): void;
16
16
  addToken(type: TSESTree.Token["type"], range: TSESTree.Range): void;
17
- addRestoreNodeProcess(process: (node: TSESTree.Node, result: ESLintExtendedProgram) => boolean): void;
17
+ addRestoreNodeProcess(process: (node: TSESTree.Node, result: ESLintExtendedProgram, parent: TSESTree.Node) => boolean): void;
18
18
  /**
19
19
  * Restore AST nodes
20
20
  */
@@ -52,12 +52,12 @@ class ScriptContext {
52
52
  delete rootFragment.openingFragment;
53
53
  rootFragment.type = "AstroRootFragment";
54
54
  // remap locations
55
- const traversed = new Set();
55
+ const traversed = new Map();
56
56
  (0, traverse_1.traverseNodes)(result.ast, {
57
57
  visitorKeys: result.visitorKeys,
58
- enterNode: (node) => {
58
+ enterNode: (node, p) => {
59
59
  if (!traversed.has(node)) {
60
- traversed.add(node);
60
+ traversed.set(node, p);
61
61
  this.remapLocation(node);
62
62
  }
63
63
  },
@@ -78,8 +78,10 @@ class ScriptContext {
78
78
  this.remapLocation(token);
79
79
  }
80
80
  let restoreNodeProcesses = this.restoreNodeProcesses;
81
- for (const node of traversed) {
82
- restoreNodeProcesses = restoreNodeProcesses.filter((proc) => !proc(node, result));
81
+ for (const [node, parent] of traversed) {
82
+ if (!parent)
83
+ continue;
84
+ restoreNodeProcesses = restoreNodeProcesses.filter((proc) => !proc(node, result, parent));
83
85
  }
84
86
  // Adjust program node location
85
87
  const first = result.ast.body[0];
package/lib/errors.d.ts CHANGED
@@ -6,6 +6,7 @@ export declare class ParseError extends SyntaxError {
6
6
  index: number;
7
7
  lineNumber: number;
8
8
  column: number;
9
+ originalAST: any;
9
10
  /**
10
11
  * Initialize this ParseError instance.
11
12
  */
package/lib/errors.js CHANGED
@@ -14,6 +14,7 @@ class ParseError extends SyntaxError {
14
14
  const loc = ctx.getLocFromIndex(offset);
15
15
  this.lineNumber = loc.line;
16
16
  this.column = loc.column;
17
+ this.originalAST = ctx.originalAST;
17
18
  }
18
19
  }
19
20
  exports.ParseError = ParseError;
@@ -31,7 +31,7 @@ const errors_1 = require("../../errors");
31
31
  * Parse code by `@astrojs/compiler`
32
32
  */
33
33
  function parse(code, ctx) {
34
- const ast = parseByService(code).ast;
34
+ const ast = parseByService(code, ctx).ast;
35
35
  const htmlElement = ast.children.find((n) => n.type === "element" && n.name === "html");
36
36
  if (htmlElement) {
37
37
  adjustHTML(ast, htmlElement, ctx);
@@ -43,13 +43,16 @@ exports.parse = parse;
43
43
  /**
44
44
  * Parse code by `@astrojs/compiler`
45
45
  */
46
- function parseByService(code) {
46
+ function parseByService(code, ctx) {
47
47
  const jsonAst = service.parse(code, { position: true }).ast;
48
+ ctx.originalAST = jsonAst;
48
49
  try {
49
50
  const ast = JSON.parse(jsonAst);
51
+ ctx.originalAST = ast;
50
52
  return { ast };
51
53
  }
52
54
  catch (_a) {
55
+ // FIXME: Workaround for escape bugs
53
56
  // Adjust because may get the wrong escape as JSON.
54
57
  const ast = JSON.parse(jsonAst.replace(/\\./gu, (m) => {
55
58
  try {
@@ -60,6 +63,7 @@ function parseByService(code) {
60
63
  return `\\${m}`;
61
64
  }
62
65
  }));
66
+ ctx.originalAST = ast;
63
67
  return { ast };
64
68
  }
65
69
  }
@@ -119,7 +123,7 @@ function fixLocations(node, ctx) {
119
123
  let start = 0;
120
124
  (0, astro_1.walk)(node, ctx.code,
121
125
  // eslint-disable-next-line complexity -- X(
122
- (node, parent) => {
126
+ (node, [parent]) => {
123
127
  if (node.type === "frontmatter") {
124
128
  start = node.position.start.offset = tokenIndex(ctx, "---", start);
125
129
  start = node.position.end.offset =
@@ -155,7 +159,7 @@ function fixLocations(node, ctx) {
155
159
  if (start < 0) {
156
160
  start = ctx.code.length;
157
161
  }
158
- // Workaround for escape bugs
162
+ // FIXME: Workaround for escape bugs
159
163
  node.value = ctx.code.slice(node.position.start.offset, start);
160
164
  }
161
165
  else {
@@ -165,24 +169,25 @@ function fixLocations(node, ctx) {
165
169
  start += node.value.length;
166
170
  }
167
171
  else {
168
- // Workaround for escape bugs
172
+ // FIXME: Workaround for escape bugs
169
173
  node.position.start.offset = start;
170
- for (const token of node.value.split(/\s+/u)) {
171
- const index = tokenIndexSafe(ctx.code, token, start);
174
+ const value = node.value.replace(/\s+/gu, "");
175
+ for (let charIndex = 0; charIndex < value.length; charIndex++) {
176
+ const char = value[charIndex];
177
+ const index = tokenIndexSafe(ctx.code, char, start);
172
178
  if (index != null) {
173
- start = index + token.length;
179
+ start = index + 1;
174
180
  continue;
175
181
  }
176
182
  start = (0, astro_1.skipSpaces)(ctx.code, start);
177
- let t = token;
178
183
  if (ctx.code.startsWith("\\", start)) {
179
- const char = JSON.parse(`"\\${ctx.code[start + 1]}"`);
180
- if (char.trim()) {
181
- t = t.slice(1);
182
- }
184
+ const codeChar = JSON.parse(`"\\${ctx.code[start + 1]}"`);
183
185
  start += 2;
186
+ if (codeChar === char) {
187
+ continue;
188
+ }
184
189
  }
185
- start = tokenIndex(ctx, t, start) + t.length;
190
+ start = tokenIndex(ctx, char, start) + 1;
186
191
  }
187
192
  start = (0, astro_1.skipSpaces)(ctx.code, start);
188
193
  node.value = ctx.code.slice(node.position.start.offset, start);
@@ -208,25 +213,26 @@ function fixLocations(node, ctx) {
208
213
  else if (node.type === "root") {
209
214
  // noop
210
215
  }
211
- }, (node, parent) => {
216
+ }, (node, [parent]) => {
212
217
  if (node.type === "attribute") {
213
218
  const attributes = parent.attributes;
214
219
  if (attributes[attributes.length - 1] === node) {
215
220
  start = (0, astro_1.getStartTagEndOffset)(parent, ctx);
216
221
  }
217
- return;
218
222
  }
219
- if (node.type === "expression") {
223
+ else if (node.type === "expression") {
220
224
  start = tokenIndex(ctx, "}", start) + 1;
221
225
  }
222
226
  else if (node.type === "fragment" ||
223
227
  node.type === "element" ||
224
228
  node.type === "component" ||
225
229
  node.type === "custom-element") {
226
- const closeTagStart = tokenIndexSafe(ctx.code, `</${node.name}`, start);
227
- if (closeTagStart != null) {
228
- start = closeTagStart + 2 + node.name.length;
229
- start = tokenIndex(ctx, ">", start) + 1;
230
+ if (!(0, astro_1.getSelfClosingTag)(node, parent, ctx)) {
231
+ const closeTagStart = tokenIndexSafe(ctx.code, `</${node.name}`, start);
232
+ if (closeTagStart != null) {
233
+ start = closeTagStart + 2 + node.name.length;
234
+ start = tokenIndex(ctx, ">", start) + 1;
235
+ }
230
236
  }
231
237
  }
232
238
  else {
@@ -17,8 +17,9 @@ function processTemplate(ctx, resultTemplate) {
17
17
  script.appendScript("<>");
18
18
  fragmentOpened = true;
19
19
  }
20
+ (0, astro_1.walkElements)(resultTemplate.ast, ctx.code,
20
21
  // eslint-disable-next-line complexity -- X(
21
- (0, astro_1.walkElements)(resultTemplate.ast, ctx.code, (node, parent) => {
22
+ (node, [parent]) => {
22
23
  if (node.type === "frontmatter") {
23
24
  const start = node.position.start.offset;
24
25
  script.appendOriginal(start);
@@ -32,7 +33,8 @@ function processTemplate(ctx, resultTemplate) {
32
33
  for (let index = 0; index < result.ast.body.length; index++) {
33
34
  const st = result.ast.body[index];
34
35
  if (st.type === types_1.AST_NODE_TYPES.EmptyStatement) {
35
- if (st.range[0] === end - 3 && st.range[1] === end) {
36
+ if (st.range[0] === end - 3 &&
37
+ st.range[1] === end) {
36
38
  result.ast.body.splice(index, 1);
37
39
  break;
38
40
  }
@@ -47,8 +49,42 @@ function processTemplate(ctx, resultTemplate) {
47
49
  script.addToken(types_1.AST_TOKEN_TYPES.Punctuator, [end - 3, end]);
48
50
  }
49
51
  else if ((0, astro_1.isTag)(node)) {
52
+ // Process for multiple tag
53
+ if (parent.type === "expression") {
54
+ const index = parent.children.indexOf(node);
55
+ const before = parent.children[index - 1];
56
+ if (!before || !(0, astro_1.isTag)(before)) {
57
+ const after = parent.children[index + 1];
58
+ if (after &&
59
+ ((0, astro_1.isTag)(after) || after.type === "comment")) {
60
+ const start = node.position.start.offset;
61
+ script.appendOriginal(start);
62
+ script.appendScript("<>");
63
+ script.addRestoreNodeProcess((scriptNode) => {
64
+ if (scriptNode.range[0] === start &&
65
+ scriptNode.type ===
66
+ types_1.AST_NODE_TYPES.JSXFragment) {
67
+ delete scriptNode.openingFragment;
68
+ delete scriptNode.closingFragment;
69
+ const fragmentNode = scriptNode;
70
+ fragmentNode.type = "AstroFragment";
71
+ const last = fragmentNode.children[fragmentNode.children.length - 1];
72
+ if (fragmentNode.range[1] < last.range[1]) {
73
+ fragmentNode.range[1] = last.range[1];
74
+ fragmentNode.loc.end =
75
+ ctx.getLocFromIndex(fragmentNode.range[1]);
76
+ }
77
+ return true;
78
+ }
79
+ return false;
80
+ });
81
+ }
82
+ }
83
+ }
84
+ // Process for attributes
50
85
  for (const attr of node.attributes) {
51
- if ((node.type === "component" || node.type === "fragment") &&
86
+ if ((node.type === "component" ||
87
+ node.type === "fragment") &&
52
88
  (attr.kind === "quoted" ||
53
89
  attr.kind === "empty" ||
54
90
  attr.kind === "expression" ||
@@ -76,7 +112,8 @@ function processTemplate(ctx, resultTemplate) {
76
112
  types_1.AST_NODE_TYPES.JSXAttribute &&
77
113
  scriptNode.range[0] === start) {
78
114
  const baseNameNode = scriptNode.name;
79
- 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] +
115
+ 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] +
116
+ colonIndex)), name: Object.assign({ type: types_1.AST_NODE_TYPES.JSXIdentifier, name: attr.name.slice(colonIndex + 1) }, ctx.getLocations(baseNameNode.range[0] +
80
117
  colonIndex +
81
118
  1, baseNameNode.range[1])) });
82
119
  scriptNode.name = nsn;
@@ -87,7 +124,8 @@ function processTemplate(ctx, resultTemplate) {
87
124
  const token = tokens[index];
88
125
  if (token.range[0] ===
89
126
  baseNameNode.range[0] &&
90
- token.range[1] === baseNameNode.range[1]) {
127
+ token.range[1] ===
128
+ baseNameNode.range[1]) {
91
129
  tokens.splice(index, 1);
92
130
  break;
93
131
  }
@@ -106,7 +144,8 @@ function processTemplate(ctx, resultTemplate) {
106
144
  : attr.name;
107
145
  script.appendScript(`${jsxName}=`);
108
146
  script.addRestoreNodeProcess((scriptNode) => {
109
- if (scriptNode.type === types_1.AST_NODE_TYPES.JSXAttribute &&
147
+ if (scriptNode.type ===
148
+ types_1.AST_NODE_TYPES.JSXAttribute &&
110
149
  scriptNode.range[0] === start) {
111
150
  const attrNode = scriptNode;
112
151
  attrNode.type = "AstroShorthandAttribute";
@@ -130,7 +169,8 @@ function processTemplate(ctx, resultTemplate) {
130
169
  script.appendOriginal(end);
131
170
  script.appendScript("}");
132
171
  script.addRestoreNodeProcess((scriptNode) => {
133
- if (scriptNode.type === types_1.AST_NODE_TYPES.JSXAttribute &&
172
+ if (scriptNode.type ===
173
+ types_1.AST_NODE_TYPES.JSXAttribute &&
134
174
  scriptNode.range[0] === attrStart) {
135
175
  const attrNode = scriptNode;
136
176
  attrNode.type = "AstroTemplateLiteralAttribute";
@@ -140,11 +180,13 @@ function processTemplate(ctx, resultTemplate) {
140
180
  });
141
181
  }
142
182
  }
143
- const end = getVoidSelfClosingTag(node, parent, ctx);
144
- if (end && end.end === ">") {
145
- script.appendOriginal(end.offset - 1);
183
+ // Process for start tag close
184
+ const closing = (0, astro_1.getSelfClosingTag)(node, parent, ctx);
185
+ if (closing && closing.end === ">") {
186
+ script.appendOriginal(closing.offset - 1);
146
187
  script.appendScript("/");
147
188
  }
189
+ // Process for raw text
148
190
  if (node.name === "script" || node.name === "style") {
149
191
  const text = node.children[0];
150
192
  if (text && text.type === "text") {
@@ -176,15 +218,17 @@ function processTemplate(ctx, resultTemplate) {
176
218
  script.appendOriginal(start);
177
219
  let targetType;
178
220
  if (fragmentOpened) {
179
- script.appendScript(`<></>`);
221
+ script.appendOriginal(start + 1);
222
+ script.appendScript(`></`);
223
+ script.skipOriginalOffset(length - 2);
180
224
  targetType = types_1.AST_NODE_TYPES.JSXFragment;
181
225
  }
182
226
  else {
183
227
  script.appendScript(`0;`);
184
228
  targetType = types_1.AST_NODE_TYPES.ExpressionStatement;
229
+ script.skipOriginalOffset(length);
185
230
  }
186
- script.skipOriginalOffset(length);
187
- script.addRestoreNodeProcess((scriptNode) => {
231
+ script.addRestoreNodeProcess((scriptNode, result) => {
188
232
  if (scriptNode.range[0] === start &&
189
233
  scriptNode.type === targetType) {
190
234
  delete scriptNode.children;
@@ -194,6 +238,27 @@ function processTemplate(ctx, resultTemplate) {
194
238
  const commentNode = scriptNode;
195
239
  commentNode.type = "AstroHTMLComment";
196
240
  commentNode.value = node.value;
241
+ if (fragmentOpened) {
242
+ const removeTokenSet = new Set([
243
+ (token) => token.value === "<" &&
244
+ token.range[0] === scriptNode.range[0],
245
+ (token) => token.value === ">" &&
246
+ token.range[1] === scriptNode.range[1],
247
+ ]);
248
+ const tokens = result.ast.tokens || [];
249
+ for (let index = tokens.length - 1; index >= 0; index--) {
250
+ const token = tokens[index];
251
+ for (const rt of removeTokenSet) {
252
+ if (rt(token)) {
253
+ tokens.splice(index, 1);
254
+ removeTokenSet.delete(rt);
255
+ if (!removeTokenSet.size) {
256
+ break;
257
+ }
258
+ }
259
+ }
260
+ }
261
+ }
197
262
  return true;
198
263
  }
199
264
  return false;
@@ -232,6 +297,45 @@ function processTemplate(ctx, resultTemplate) {
232
297
  });
233
298
  script.addToken("HTMLDocType", [start, end]);
234
299
  }
300
+ }, (node, [parent]) => {
301
+ if ((0, astro_1.isTag)(node)) {
302
+ const closing = (0, astro_1.getSelfClosingTag)(node, parent, ctx);
303
+ if (!closing) {
304
+ const end = (0, astro_1.getEndTag)(node, ctx);
305
+ if (!end) {
306
+ const offset = (0, astro_1.getContentEndOffset)(node, ctx);
307
+ script.appendOriginal(offset);
308
+ script.appendScript(`</${node.name}>`);
309
+ script.addRestoreNodeProcess((scriptNode, _result, parent) => {
310
+ if (scriptNode.range[0] === offset &&
311
+ scriptNode.type ===
312
+ types_1.AST_NODE_TYPES.JSXClosingElement &&
313
+ parent.type === types_1.AST_NODE_TYPES.JSXElement) {
314
+ parent.closingElement = null;
315
+ return true;
316
+ }
317
+ return false;
318
+ });
319
+ }
320
+ }
321
+ }
322
+ // Process for multiple tag
323
+ if (((0, astro_1.isTag)(node) || node.type === "comment") &&
324
+ parent.type === "expression") {
325
+ const index = parent.children.indexOf(node);
326
+ const after = parent.children[index + 1];
327
+ if (!after || (!(0, astro_1.isTag)(after) && after.type !== "comment")) {
328
+ const before = parent.children[index - 1];
329
+ if (before &&
330
+ ((0, astro_1.isTag)(before) || before.type === "comment")) {
331
+ const end = (0, astro_1.isTag)(node)
332
+ ? (0, astro_1.getTagEndOffset)(node, ctx)
333
+ : (0, astro_1.getCommentEndOffset)(node, ctx);
334
+ script.appendOriginal(end);
335
+ script.appendScript("</>");
336
+ }
337
+ }
338
+ }
235
339
  });
236
340
  script.appendOriginal(ctx.code.length);
237
341
  script.appendScript("</>");
@@ -249,36 +353,3 @@ function processTemplate(ctx, resultTemplate) {
249
353
  }
250
354
  }
251
355
  exports.processTemplate = processTemplate;
252
- /**
253
- * If the given tag is a void tag, get the self-closing tag.
254
- */
255
- function getVoidSelfClosingTag(node, parent, ctx) {
256
- var _a;
257
- const children = node.children.filter((c) => c.type !== "text" || c.value.trim());
258
- if (children.length > 0) {
259
- return false;
260
- }
261
- const code = ctx.code;
262
- let nextElementIndex = code.length;
263
- const childIndex = parent.children.indexOf(node);
264
- if (childIndex === parent.children.length - 1) {
265
- // last
266
- if ((_a = parent.position) === null || _a === void 0 ? void 0 : _a.end) {
267
- nextElementIndex = parent.position.end.offset;
268
- nextElementIndex = code.lastIndexOf("</", nextElementIndex);
269
- }
270
- }
271
- else {
272
- const next = parent.children[childIndex + 1];
273
- nextElementIndex = next.position.start.offset;
274
- }
275
- const endOffset = (0, astro_1.getStartTagEndOffset)(node, ctx);
276
- if (code.slice(endOffset, nextElementIndex).trim()) {
277
- // has end tag
278
- return null;
279
- }
280
- return {
281
- offset: endOffset,
282
- end: code.slice(endOffset - 2, endOffset) === "/>" ? "/>" : ">",
283
- };
284
- }
@@ -30,7 +30,9 @@ function parseScript(code, ctx) {
30
30
  return { ast: result };
31
31
  }
32
32
  catch (e) {
33
- (0, debug_1.debug)("[script] parsing error:", e.message, `@ ${JSON.stringify(code)}`);
33
+ (0, debug_1.debug)("[script] parsing error:", e.message, `@ ${JSON.stringify(code)}
34
+
35
+ ${code}`);
34
36
  throw e;
35
37
  }
36
38
  finally {
@@ -10,5 +10,6 @@ const astroKeys = {
10
10
  AstroShorthandAttribute: ["name", "value"],
11
11
  AstroTemplateLiteralAttribute: ["name", "value"],
12
12
  AstroRawText: [],
13
+ AstroFragment: ["children"],
13
14
  };
14
15
  exports.KEYS = (0, eslint_visitor_keys_1.unionWith)(astroKeys);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-eslint-parser",
3
- "version": "0.0.12",
3
+ "version": "0.0.15",
4
4
  "description": "Astro parser for ESLint",
5
5
  "main": "lib/index.js",
6
6
  "files": [
@@ -19,7 +19,7 @@
19
19
  "cover": "nyc --reporter=lcov npm run test",
20
20
  "debug": "mocha --require ts-node/register/transpile-only \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
21
21
  "preversion": "npm run lint && npm test",
22
- "update-fixtures": "ts-node --transpile-only ./tools/update-fixtures.ts",
22
+ "update-fixtures": "DEBUG='astro-eslint-parser' ts-node --transpile-only ./tools/update-fixtures.ts",
23
23
  "debug-parser": "ts-node --transpile-only ./tools/parser-test.ts",
24
24
  "eslint-playground": "eslint tests/fixtures --ext .astro --config .eslintrc-for-playground.js --format codeframe",
25
25
  "benchmark": "ts-node --transpile-only benchmark/index.ts"