astro-eslint-parser 0.0.12 → 0.0.13

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, cb: (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, parents?: ParentNode[]): 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, parents: ParentNode[], ctx: Context): number;
23
+ /**
24
+ * Get end offset of Expression
25
+ */
26
+ export declare function getExpressionEndOffset(node: ExpressionNode, parents: ParentNode[], ctx: Context): number;
19
27
  /**
20
28
  * Get end offset of attribute
21
29
  */
@@ -28,6 +36,13 @@ 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
+ * If the given tag is a void tag, get the self-closing tag.
41
+ */
42
+ export declare function getSelfClosingTag(node: TagLikeNode, parents: ParentNode[], ctx: Context): null | {
43
+ offset: number;
44
+ end: "/>" | ">";
45
+ };
31
46
  /**
32
47
  * Skip spaces
33
48
  */
@@ -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.getSelfClosingTag = 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,31 +20,34 @@ 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, cb, 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
+ cb(node, currParents);
27
28
  if (isParent(node)) {
28
- walkElements(node, code, cb);
29
+ walkElements(node, code, cb, currParents);
29
30
  }
30
31
  }
31
32
  }
32
33
  exports.walkElements = walkElements;
33
34
  /** walk nodes */
34
- function walk(parent, code, enter, leave) {
35
+ function walk(parent, code, enter, leave, parents = []) {
35
36
  const children = getSortedChildren(parent, code);
37
+ const currParents = [parent, ...parents];
36
38
  for (const node of children) {
37
- enter(node, parent);
39
+ enter(node, currParents);
38
40
  if (isTag(node)) {
41
+ const attrParents = [node, ...currParents];
39
42
  for (const attr of node.attributes) {
40
- enter(attr, node);
41
- leave === null || leave === void 0 ? void 0 : leave(attr, node);
43
+ enter(attr, attrParents);
44
+ leave === null || leave === void 0 ? void 0 : leave(attr, attrParents);
42
45
  }
43
46
  }
44
47
  if (isParent(node)) {
45
- walk(node, code, enter, leave);
48
+ walk(node, code, enter, leave, currParents);
46
49
  }
47
- leave === null || leave === void 0 ? void 0 : leave(node, parent);
50
+ leave === null || leave === void 0 ? void 0 : leave(node, currParents);
48
51
  }
49
52
  }
50
53
  exports.walk = walk;
@@ -65,6 +68,80 @@ function getStartTagEndOffset(node, ctx) {
65
68
  return info.index + info.match.length;
66
69
  }
67
70
  exports.getStartTagEndOffset = getStartTagEndOffset;
71
+ /**
72
+ * Get end offset of tag
73
+ */
74
+ function getTagEndOffset(node, parents, ctx) {
75
+ var _a;
76
+ if (((_a = node.position.end) === null || _a === void 0 ? void 0 : _a.offset) != null) {
77
+ return node.position.end.offset;
78
+ }
79
+ if (node.children.length) {
80
+ const code = ctx.code;
81
+ let nextElementIndex = code.length;
82
+ const parent = parents[0];
83
+ const childIndex = parent.children.indexOf(node);
84
+ if (childIndex === parent.children.length - 1) {
85
+ // last
86
+ if (isTag(parent)) {
87
+ nextElementIndex = getTagEndOffset(parent, parents.slice(1), ctx);
88
+ nextElementIndex = code.lastIndexOf("</", nextElementIndex);
89
+ }
90
+ else if (parent.type === "expression") {
91
+ nextElementIndex = getExpressionEndOffset(parent, parents.slice(1), ctx);
92
+ nextElementIndex = code.lastIndexOf("}", nextElementIndex);
93
+ }
94
+ }
95
+ else {
96
+ const next = parent.children[childIndex + 1];
97
+ nextElementIndex = next.position.start.offset;
98
+ }
99
+ return code.lastIndexOf(">", nextElementIndex);
100
+ }
101
+ let beforeIndex = getStartTagEndOffset(node, ctx);
102
+ beforeIndex = skipSpaces(ctx.code, beforeIndex);
103
+ if (ctx.code.startsWith(`</${node.name}`, beforeIndex)) {
104
+ beforeIndex = beforeIndex + 2 + node.name.length;
105
+ const info = getTokenInfo(ctx, [">"], beforeIndex);
106
+ return info.index + info.match.length;
107
+ }
108
+ return ctx.code.length;
109
+ }
110
+ exports.getTagEndOffset = getTagEndOffset;
111
+ /**
112
+ * Get end offset of Expression
113
+ */
114
+ function getExpressionEndOffset(node, parents, ctx) {
115
+ var _a;
116
+ if (((_a = node.position.end) === null || _a === void 0 ? void 0 : _a.offset) != null) {
117
+ return node.position.end.offset;
118
+ }
119
+ if (node.children.length) {
120
+ const code = ctx.code;
121
+ let nextElementIndex = code.length;
122
+ const parent = parents[0];
123
+ const childIndex = parent.children.indexOf(node);
124
+ if (childIndex === parent.children.length - 1) {
125
+ // last
126
+ if (isTag(parent)) {
127
+ nextElementIndex = getTagEndOffset(parent, parents.slice(1), ctx);
128
+ nextElementIndex = code.lastIndexOf("</", nextElementIndex);
129
+ }
130
+ else if (parent.type === "expression") {
131
+ nextElementIndex = getExpressionEndOffset(parent, parents.slice(1), ctx);
132
+ nextElementIndex = code.lastIndexOf("}", nextElementIndex);
133
+ }
134
+ }
135
+ else {
136
+ const next = parent.children[childIndex + 1];
137
+ nextElementIndex = next.position.start.offset;
138
+ }
139
+ return code.lastIndexOf("}", nextElementIndex);
140
+ }
141
+ const info = getTokenInfo(ctx, ["{", "}"], node.position.start.offset);
142
+ return info.index + info.match.length;
143
+ }
144
+ exports.getExpressionEndOffset = getExpressionEndOffset;
68
145
  /**
69
146
  * Get end offset of attribute
70
147
  */
@@ -122,6 +199,44 @@ function getCommentEndOffset(node, ctx) {
122
199
  return info.index + info.match.length;
123
200
  }
124
201
  exports.getCommentEndOffset = getCommentEndOffset;
202
+ /**
203
+ * If the given tag is a void tag, get the self-closing tag.
204
+ */
205
+ function getSelfClosingTag(node, parents, ctx) {
206
+ const children = node.children.filter((c) => c.type !== "text" || c.value.trim());
207
+ if (children.length > 0) {
208
+ return null;
209
+ }
210
+ const parent = parents[0];
211
+ const code = ctx.code;
212
+ let nextElementIndex = code.length;
213
+ const childIndex = parent.children.indexOf(node);
214
+ if (childIndex === parent.children.length - 1) {
215
+ // last
216
+ if (isTag(parent)) {
217
+ nextElementIndex = getTagEndOffset(parent, parents.slice(1), ctx);
218
+ nextElementIndex = code.lastIndexOf("</", nextElementIndex);
219
+ }
220
+ else if (parent.type === "expression") {
221
+ nextElementIndex = getExpressionEndOffset(parent, parents.slice(1), ctx);
222
+ nextElementIndex = code.lastIndexOf("}", nextElementIndex);
223
+ }
224
+ }
225
+ else {
226
+ const next = parent.children[childIndex + 1];
227
+ nextElementIndex = next.position.start.offset;
228
+ }
229
+ const endOffset = getStartTagEndOffset(node, ctx);
230
+ if (code.slice(endOffset, nextElementIndex).trim()) {
231
+ // has end tag
232
+ return null;
233
+ }
234
+ return {
235
+ offset: endOffset,
236
+ end: code.slice(endOffset - 2, endOffset) === "/>" ? "/>" : ">",
237
+ };
238
+ }
239
+ exports.getSelfClosingTag = getSelfClosingTag;
125
240
  /**
126
241
  * Get token info
127
242
  */
@@ -50,6 +50,7 @@ function parseByService(code) {
50
50
  return { ast };
51
51
  }
52
52
  catch (_a) {
53
+ // FIXME: Workaround for escape bugs
53
54
  // Adjust because may get the wrong escape as JSON.
54
55
  const ast = JSON.parse(jsonAst.replace(/\\./gu, (m) => {
55
56
  try {
@@ -119,7 +120,7 @@ function fixLocations(node, ctx) {
119
120
  let start = 0;
120
121
  (0, astro_1.walk)(node, ctx.code,
121
122
  // eslint-disable-next-line complexity -- X(
122
- (node, parent) => {
123
+ (node, [parent]) => {
123
124
  if (node.type === "frontmatter") {
124
125
  start = node.position.start.offset = tokenIndex(ctx, "---", start);
125
126
  start = node.position.end.offset =
@@ -155,7 +156,7 @@ function fixLocations(node, ctx) {
155
156
  if (start < 0) {
156
157
  start = ctx.code.length;
157
158
  }
158
- // Workaround for escape bugs
159
+ // FIXME: Workaround for escape bugs
159
160
  node.value = ctx.code.slice(node.position.start.offset, start);
160
161
  }
161
162
  else {
@@ -165,24 +166,25 @@ function fixLocations(node, ctx) {
165
166
  start += node.value.length;
166
167
  }
167
168
  else {
168
- // Workaround for escape bugs
169
+ // FIXME: Workaround for escape bugs
169
170
  node.position.start.offset = start;
170
- for (const token of node.value.split(/\s+/u)) {
171
- const index = tokenIndexSafe(ctx.code, token, start);
171
+ const value = node.value.replace(/\s+/gu, "");
172
+ for (let charIndex = 0; charIndex < value.length; charIndex++) {
173
+ const char = value[charIndex];
174
+ const index = tokenIndexSafe(ctx.code, char, start);
172
175
  if (index != null) {
173
- start = index + token.length;
176
+ start = index + 1;
174
177
  continue;
175
178
  }
176
179
  start = (0, astro_1.skipSpaces)(ctx.code, start);
177
- let t = token;
178
180
  if (ctx.code.startsWith("\\", start)) {
179
- const char = JSON.parse(`"\\${ctx.code[start + 1]}"`);
180
- if (char.trim()) {
181
- t = t.slice(1);
182
- }
181
+ const codeChar = JSON.parse(`"\\${ctx.code[start + 1]}"`);
183
182
  start += 2;
183
+ if (codeChar === char) {
184
+ continue;
185
+ }
184
186
  }
185
- start = tokenIndex(ctx, t, start) + t.length;
187
+ start = tokenIndex(ctx, char, start) + 1;
186
188
  }
187
189
  start = (0, astro_1.skipSpaces)(ctx.code, start);
188
190
  node.value = ctx.code.slice(node.position.start.offset, start);
@@ -208,7 +210,7 @@ function fixLocations(node, ctx) {
208
210
  else if (node.type === "root") {
209
211
  // noop
210
212
  }
211
- }, (node, parent) => {
213
+ }, (node, [parent]) => {
212
214
  if (node.type === "attribute") {
213
215
  const attributes = parent.attributes;
214
216
  if (attributes[attributes.length - 1] === node) {
@@ -18,7 +18,36 @@ function processTemplate(ctx, resultTemplate) {
18
18
  fragmentOpened = true;
19
19
  }
20
20
  // eslint-disable-next-line complexity -- X(
21
- (0, astro_1.walkElements)(resultTemplate.ast, ctx.code, (node, parent) => {
21
+ (0, astro_1.walkElements)(resultTemplate.ast, ctx.code, (node, parents) => {
22
+ const parent = parents[0];
23
+ if ((0, astro_1.isTag)(node) && parent.type === "expression") {
24
+ const index = parent.children.indexOf(node);
25
+ const before = parent.children[index - 1];
26
+ if (!before || !(0, astro_1.isTag)(before)) {
27
+ const after = parent.children[index + 1];
28
+ if (after && ((0, astro_1.isTag)(after) || after.type === "comment")) {
29
+ const start = node.position.start.offset;
30
+ script.appendOriginal(start);
31
+ script.appendScript("<>");
32
+ script.addRestoreNodeProcess((scriptNode) => {
33
+ if (scriptNode.range[0] === start &&
34
+ scriptNode.type === types_1.AST_NODE_TYPES.JSXFragment) {
35
+ delete scriptNode.openingFragment;
36
+ delete scriptNode.closingFragment;
37
+ const fragmentNode = scriptNode;
38
+ fragmentNode.type = "AstroFragment";
39
+ const last = fragmentNode.children[fragmentNode.children.length - 1];
40
+ if (fragmentNode.range[1] < last.range[1]) {
41
+ fragmentNode.range[1] = last.range[1];
42
+ fragmentNode.loc.end = ctx.getLocFromIndex(fragmentNode.range[1]);
43
+ }
44
+ return true;
45
+ }
46
+ return false;
47
+ });
48
+ }
49
+ }
50
+ }
22
51
  if (node.type === "frontmatter") {
23
52
  const start = node.position.start.offset;
24
53
  script.appendOriginal(start);
@@ -140,7 +169,7 @@ function processTemplate(ctx, resultTemplate) {
140
169
  });
141
170
  }
142
171
  }
143
- const end = getVoidSelfClosingTag(node, parent, ctx);
172
+ const end = (0, astro_1.getSelfClosingTag)(node, parents, ctx);
144
173
  if (end && end.end === ">") {
145
174
  script.appendOriginal(end.offset - 1);
146
175
  script.appendScript("/");
@@ -176,15 +205,17 @@ function processTemplate(ctx, resultTemplate) {
176
205
  script.appendOriginal(start);
177
206
  let targetType;
178
207
  if (fragmentOpened) {
179
- script.appendScript(`<></>`);
208
+ script.appendOriginal(start + 1);
209
+ script.appendScript(`></`);
210
+ script.skipOriginalOffset(length - 2);
180
211
  targetType = types_1.AST_NODE_TYPES.JSXFragment;
181
212
  }
182
213
  else {
183
214
  script.appendScript(`0;`);
184
215
  targetType = types_1.AST_NODE_TYPES.ExpressionStatement;
216
+ script.skipOriginalOffset(length);
185
217
  }
186
- script.skipOriginalOffset(length);
187
- script.addRestoreNodeProcess((scriptNode) => {
218
+ script.addRestoreNodeProcess((scriptNode, result) => {
188
219
  if (scriptNode.range[0] === start &&
189
220
  scriptNode.type === targetType) {
190
221
  delete scriptNode.children;
@@ -194,6 +225,27 @@ function processTemplate(ctx, resultTemplate) {
194
225
  const commentNode = scriptNode;
195
226
  commentNode.type = "AstroHTMLComment";
196
227
  commentNode.value = node.value;
228
+ if (fragmentOpened) {
229
+ const removeTokenSet = new Set([
230
+ (token) => token.value === "<" &&
231
+ token.range[0] === scriptNode.range[0],
232
+ (token) => token.value === ">" &&
233
+ token.range[1] === scriptNode.range[1],
234
+ ]);
235
+ const tokens = result.ast.tokens || [];
236
+ for (let index = tokens.length - 1; index >= 0; index--) {
237
+ const token = tokens[index];
238
+ for (const rt of removeTokenSet) {
239
+ if (rt(token)) {
240
+ tokens.splice(index, 1);
241
+ removeTokenSet.delete(rt);
242
+ if (!removeTokenSet.size) {
243
+ break;
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }
197
249
  return true;
198
250
  }
199
251
  return false;
@@ -232,6 +284,21 @@ function processTemplate(ctx, resultTemplate) {
232
284
  });
233
285
  script.addToken("HTMLDocType", [start, end]);
234
286
  }
287
+ if (((0, astro_1.isTag)(node) || node.type === "comment") &&
288
+ parent.type === "expression") {
289
+ const index = parent.children.indexOf(node);
290
+ const after = parent.children[index + 1];
291
+ if (!after || (!(0, astro_1.isTag)(after) && after.type !== "comment")) {
292
+ const before = parent.children[index - 1];
293
+ if (before && ((0, astro_1.isTag)(before) || before.type === "comment")) {
294
+ const end = (0, astro_1.isTag)(node)
295
+ ? (0, astro_1.getTagEndOffset)(node, parents, ctx)
296
+ : (0, astro_1.getCommentEndOffset)(node, ctx);
297
+ script.appendOriginal(end);
298
+ script.appendScript("</>");
299
+ }
300
+ }
301
+ }
235
302
  });
236
303
  script.appendOriginal(ctx.code.length);
237
304
  script.appendScript("</>");
@@ -249,36 +316,3 @@ function processTemplate(ctx, resultTemplate) {
249
316
  }
250
317
  }
251
318
  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
- }
@@ -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.13",
4
4
  "description": "Astro parser for ESLint",
5
5
  "main": "lib/index.js",
6
6
  "files": [