ember-eslint-parser 0.2.5 → 0.3.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember-eslint-parser",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -30,7 +30,8 @@
30
30
  "eslint-plugin-promise": "^6.0.0",
31
31
  "execa": "^8.0.1",
32
32
  "fs-extra": "^11.2.0",
33
- "jest": "^29.7.0",
33
+ "vite": "^5.0.12",
34
+ "vitest": "^1.2.2",
34
35
  "prettier": "^3.1.1",
35
36
  "publint": "^0.2.6",
36
37
  "release-plan": "^0.6.0",
@@ -38,7 +39,7 @@
38
39
  },
39
40
  "dependencies": {
40
41
  "@babel/eslint-parser": "^7.23.3",
41
- "@glimmer/syntax": "^0.85.13",
42
+ "@glimmer/syntax": "^0.88.0",
42
43
  "@typescript-eslint/scope-manager": "^6.15.0",
43
44
  "content-tag": "^1.2.2",
44
45
  "eslint-scope": "^7.2.2",
@@ -53,6 +54,7 @@
53
54
  "typescript": "^5.3.3"
54
55
  },
55
56
  "scripts": {
57
+ "test": "vitest run",
56
58
  "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
57
59
  "lint:js": "eslint .",
58
60
  "lint:package": "pnpm publint",
@@ -40,20 +40,30 @@ module.exports = {
40
40
  throw new Error('Please install typescript to process gts');
41
41
  }
42
42
 
43
- result = isTypescript
44
- ? typescriptParser.parseForESLint(jsCode, { ...options, ranges: true, filePath })
45
- : babelParser.parseForESLint(jsCode, { ...options, ranges: true });
46
- if (!info.templateInfos?.length) {
47
- return result;
43
+ try {
44
+ result = isTypescript
45
+ ? typescriptParser.parseForESLint(jsCode, { ...options, ranges: true, filePath })
46
+ : babelParser.parseForESLint(jsCode, {
47
+ ...options,
48
+ requireConfigFile: false,
49
+ ranges: true,
50
+ });
51
+ if (!info.templateInfos?.length) {
52
+ return result;
53
+ }
54
+ const preprocessedResult = preprocessGlimmerTemplates(info, code);
55
+ preprocessedResult.code = code;
56
+ const { templateVisitorKeys } = preprocessedResult;
57
+ const visitorKeys = { ...result.visitorKeys, ...templateVisitorKeys };
58
+ result.isTypescript = isTypescript;
59
+ convertAst(result, preprocessedResult, visitorKeys);
60
+ if (result.services?.program) {
61
+ syncMtsGtsSourceFiles(result.services?.program);
62
+ }
63
+ return { ...result, visitorKeys };
64
+ } catch (e) {
65
+ console.error(e);
66
+ throw e;
48
67
  }
49
- const preprocessedResult = preprocessGlimmerTemplates(info, code);
50
- const { templateVisitorKeys } = preprocessedResult;
51
- const visitorKeys = { ...result.visitorKeys, ...templateVisitorKeys };
52
- result.isTypescript = isTypescript;
53
- convertAst(result, preprocessedResult, visitorKeys);
54
- if (result.services?.program) {
55
- syncMtsGtsSourceFiles(result.services?.program);
56
- }
57
- return { ...result, visitorKeys };
58
68
  },
59
69
  };
@@ -122,6 +122,8 @@ function traverse(visitorKeys, node, visitor) {
122
122
 
123
123
  visitor(currentPath);
124
124
 
125
+ if (!currentPath.node) continue;
126
+
125
127
  const visitorKeys = allVisitorKeys[currentPath.node.type];
126
128
  if (!visitorKeys) {
127
129
  continue;
@@ -238,15 +240,12 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
238
240
  for (const tpl of templateInfos) {
239
241
  const currentComments = [];
240
242
  const textNodes = [];
241
- const range = tpl.range;
242
- const template = sliceByteRange(code, ...range);
243
+ const emptyTextNodes = [];
244
+ const range = tpl.utf16Range;
245
+ const template = code.slice(...range);
243
246
  const docLines = new DocumentLines(template);
244
247
  const ast = glimmer.preprocess(template, { mode: 'codemod' });
245
- ast.tokens = tokenize(
246
- sliceByteRange(code, ...tpl.templateRange),
247
- codeLines,
248
- tpl.templateRange[0]
249
- );
248
+ ast.tokens = tokenize(sliceByteRange(code, ...tpl.templateRange), codeLines, tpl.utf16Range[0]);
250
249
  const allNodes = [];
251
250
  glimmer.traverse(ast, {
252
251
  All(node, path) {
@@ -259,7 +258,13 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
259
258
  }
260
259
  if (node.type === 'TextNode') {
261
260
  n.value = node.chars;
262
- textNodes.push(node);
261
+ // empty text nodes are not allowed, it's empty if its only whitespace or line terminators
262
+ // it's okay for AttrNodes
263
+ if (n.value.trim().length !== 0 || n.parent.type === 'AttrNode') {
264
+ textNodes.push(node);
265
+ } else {
266
+ emptyTextNodes.push(node);
267
+ }
263
268
  }
264
269
  },
265
270
  });
@@ -278,12 +283,11 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
278
283
  }
279
284
  n.range =
280
285
  n.type === 'Template'
281
- ? [tpl.templateRange[0], tpl.templateRange[1]]
286
+ ? [tpl.utf16Range[0], tpl.utf16Range[1]]
282
287
  : [
283
288
  range[0] + docLines.positionToOffset(n.loc.start),
284
289
  range[0] + docLines.positionToOffset(n.loc.end),
285
290
  ];
286
-
287
291
  n.start = n.range[0];
288
292
  n.end = n.range[1];
289
293
  n.loc = {
@@ -291,70 +295,39 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
291
295
  end: codeLines.offsetToPosition(n.range[1]),
292
296
  };
293
297
  if (n.type === 'Template') {
294
- n.loc.start = codeLines.offsetToPosition(tpl.templateRange[0]);
295
- n.loc.end = codeLines.offsetToPosition(tpl.templateRange[1]);
298
+ n.loc.start = codeLines.offsetToPosition(tpl.utf16Range[0]);
299
+ n.loc.end = codeLines.offsetToPosition(tpl.utf16Range[1]);
296
300
  }
297
- // split up element node into sub nodes to be able to reference tag name
298
- // parts <Foo.Bar /> -> nodes for `Foo` and `Bar`
301
+
299
302
  if (n.type === 'ElementNode') {
300
303
  n.name = n.tag;
301
- n.parts = [];
302
- let start = n.range[0];
303
- let codeSlice = sliceByteRange(code, ...n.range);
304
- for (const part of n.tag.split('.')) {
305
- const idx = codeSlice.indexOf(part);
306
- const range = [start + idx, 0];
307
- range[1] = range[0] + part.length;
308
- codeSlice = sliceByteRange(code, range[1], n.range[1]);
309
- start = range[1];
310
- n.parts.push({
311
- type: 'GlimmerElementNodePart',
312
- name: part,
313
- range,
314
- parent: n,
315
- loc: {
316
- start: codeLines.offsetToPosition(range[0]),
317
- end: codeLines.offsetToPosition(range[1]),
318
- },
319
- });
320
- }
304
+ n.parts = n.parts.map((p) => ({
305
+ ...p,
306
+ parent: n,
307
+ type: 'GlimmerElementNodePart',
308
+ name: p.value,
309
+ }));
321
310
  }
322
- // block params do not have location information
323
- // add our own nodes so we can reference them
311
+
324
312
  if ('blockParams' in n) {
325
- n.params = [];
326
- }
327
- if ('blockParams' in n && n.parent) {
328
- // for blocks {{x as |b|}} the block range does not contain the block params...
329
- // for element tag it does <x as b />
330
- const blockRange = n.type === 'Block' ? n.parent.range : n.range;
331
- let part = sliceByteRange(code, ...blockRange);
332
- let start = blockRange[0];
333
- let idx = part.indexOf('|') + 1;
334
- start += idx;
335
- part = part.slice(idx, -1);
336
- idx = part.indexOf('|');
337
- part = part.slice(0, idx);
338
- for (const param of n.blockParams) {
339
- const regex = new RegExp(`\\b${param}\\b`);
340
- const match = part.match(regex);
341
- const range = [start + match.index, 0];
342
- range[1] = range[0] + param.length;
343
- n.params.push({
344
- type: 'BlockParam',
345
- name: param,
346
- range,
347
- parent: n,
348
- loc: {
349
- start: codeLines.offsetToPosition(range[0]),
350
- end: codeLines.offsetToPosition(range[1]),
351
- },
352
- });
353
- }
313
+ // program does have blockParams but not blockParamNodes
314
+ n.params = (n.blockParamNodes || []).map((p) => ({
315
+ ...p,
316
+ parent: n,
317
+ name: p.value,
318
+ }));
354
319
  }
355
320
  n.type = `Glimmer${n.type}`;
356
321
  allNodeTypes.add(n.type);
357
322
  }
323
+
324
+ // ast should not contains empty text nodes
325
+ for (const node of emptyTextNodes) {
326
+ const children = node.parent.children || node.parent.body;
327
+ const idx = children.indexOf(node);
328
+ children.splice(idx, 1);
329
+ }
330
+
358
331
  // ast should not contain comment nodes
359
332
  for (const comment of currentComments) {
360
333
  const parentBody = comment.parent.body || comment.parent.children;
@@ -414,14 +387,16 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
414
387
  result.ast.comments.push(...preprocessedResult.comments);
415
388
 
416
389
  for (const ti of templateInfos) {
417
- const firstIdx = result.ast.tokens.findIndex((t) => t.range[0] === ti.templateRange[0]);
418
- const lastIdx = result.ast.tokens.findIndex((t) => t.range[1] === ti.templateRange[1]);
390
+ const firstIdx = result.ast.tokens.findIndex((t) => t.range[0] === ti.utf16Range[0]);
391
+ const lastIdx = result.ast.tokens.findIndex((t) => t.range[1] === ti.utf16Range[1]);
419
392
  result.ast.tokens.splice(firstIdx, lastIdx - firstIdx + 1, ...ti.ast.tokens);
420
393
  }
421
394
 
422
395
  // eslint-disable-next-line complexity
423
396
  traverse(visitorKeys, result.ast, (path) => {
424
397
  const node = path.node;
398
+ if (!node) return null;
399
+
425
400
  if (
426
401
  node.type === 'ExpressionStatement' ||
427
402
  node.type === 'StaticBlock' ||
@@ -501,6 +476,21 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
501
476
  return null;
502
477
  });
503
478
 
479
+ traverse(visitorKeys, result.ast, (path) => {
480
+ const node = path.node;
481
+ if (!node) return null;
482
+
483
+ if (
484
+ ['GlimmerMustacheStatement', 'GlimmerBlockStatement', 'GlimmerSubExpression'].includes(
485
+ node.type
486
+ )
487
+ ) {
488
+ if (node.hash && node.hash.pairs.length === 0) {
489
+ node.hash = null;
490
+ }
491
+ }
492
+ });
493
+
504
494
  if (counter !== templateInfos.length) {
505
495
  throw new Error('failed to process all templates');
506
496
  }
@@ -569,3 +559,6 @@ module.exports.transformForLint = function transformForLint(code) {
569
559
  output: jsCode,
570
560
  };
571
561
  };
562
+
563
+ module.exports.traverse = traverse;
564
+ module.exports.tokenize = tokenize;