ember-eslint-parser 0.2.6 → 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.6",
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",
@@ -52,6 +52,7 @@ module.exports = {
52
52
  return result;
53
53
  }
54
54
  const preprocessedResult = preprocessGlimmerTemplates(info, code);
55
+ preprocessedResult.code = code;
55
56
  const { templateVisitorKeys } = preprocessedResult;
56
57
  const visitorKeys = { ...result.visitorKeys, ...templateVisitorKeys };
57
58
  result.isTypescript = isTypescript;
@@ -240,15 +240,12 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
240
240
  for (const tpl of templateInfos) {
241
241
  const currentComments = [];
242
242
  const textNodes = [];
243
- const range = tpl.range;
244
- const template = sliceByteRange(code, ...range);
243
+ const emptyTextNodes = [];
244
+ const range = tpl.utf16Range;
245
+ const template = code.slice(...range);
245
246
  const docLines = new DocumentLines(template);
246
247
  const ast = glimmer.preprocess(template, { mode: 'codemod' });
247
- ast.tokens = tokenize(
248
- sliceByteRange(code, ...tpl.templateRange),
249
- codeLines,
250
- tpl.templateRange[0]
251
- );
248
+ ast.tokens = tokenize(sliceByteRange(code, ...tpl.templateRange), codeLines, tpl.utf16Range[0]);
252
249
  const allNodes = [];
253
250
  glimmer.traverse(ast, {
254
251
  All(node, path) {
@@ -261,7 +258,13 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
261
258
  }
262
259
  if (node.type === 'TextNode') {
263
260
  n.value = node.chars;
264
- 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
+ }
265
268
  }
266
269
  },
267
270
  });
@@ -280,12 +283,11 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
280
283
  }
281
284
  n.range =
282
285
  n.type === 'Template'
283
- ? [tpl.templateRange[0], tpl.templateRange[1]]
286
+ ? [tpl.utf16Range[0], tpl.utf16Range[1]]
284
287
  : [
285
288
  range[0] + docLines.positionToOffset(n.loc.start),
286
289
  range[0] + docLines.positionToOffset(n.loc.end),
287
290
  ];
288
-
289
291
  n.start = n.range[0];
290
292
  n.end = n.range[1];
291
293
  n.loc = {
@@ -293,70 +295,39 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
293
295
  end: codeLines.offsetToPosition(n.range[1]),
294
296
  };
295
297
  if (n.type === 'Template') {
296
- n.loc.start = codeLines.offsetToPosition(tpl.templateRange[0]);
297
- 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]);
298
300
  }
299
- // split up element node into sub nodes to be able to reference tag name
300
- // parts <Foo.Bar /> -> nodes for `Foo` and `Bar`
301
+
301
302
  if (n.type === 'ElementNode') {
302
303
  n.name = n.tag;
303
- n.parts = [];
304
- let start = n.range[0];
305
- let codeSlice = sliceByteRange(code, ...n.range);
306
- for (const part of n.tag.split('.')) {
307
- const idx = codeSlice.indexOf(part);
308
- const range = [start + idx, 0];
309
- range[1] = range[0] + part.length;
310
- codeSlice = sliceByteRange(code, range[1], n.range[1]);
311
- start = range[1];
312
- n.parts.push({
313
- type: 'GlimmerElementNodePart',
314
- name: part,
315
- range,
316
- parent: n,
317
- loc: {
318
- start: codeLines.offsetToPosition(range[0]),
319
- end: codeLines.offsetToPosition(range[1]),
320
- },
321
- });
322
- }
304
+ n.parts = n.parts.map((p) => ({
305
+ ...p,
306
+ parent: n,
307
+ type: 'GlimmerElementNodePart',
308
+ name: p.value,
309
+ }));
323
310
  }
324
- // block params do not have location information
325
- // add our own nodes so we can reference them
311
+
326
312
  if ('blockParams' in n) {
327
- n.params = [];
328
- }
329
- if ('blockParams' in n && n.parent) {
330
- // for blocks {{x as |b|}} the block range does not contain the block params...
331
- // for element tag it does <x as b />
332
- const blockRange = n.type === 'Block' ? n.parent.range : n.range;
333
- let part = sliceByteRange(code, ...blockRange);
334
- let start = blockRange[0];
335
- let idx = part.indexOf('|') + 1;
336
- start += idx;
337
- part = part.slice(idx, -1);
338
- idx = part.indexOf('|');
339
- part = part.slice(0, idx);
340
- for (const param of n.blockParams) {
341
- const regex = new RegExp(`\\b${param}\\b`);
342
- const match = part.match(regex);
343
- const range = [start + match.index, 0];
344
- range[1] = range[0] + param.length;
345
- n.params.push({
346
- type: 'BlockParam',
347
- name: param,
348
- range,
349
- parent: n,
350
- loc: {
351
- start: codeLines.offsetToPosition(range[0]),
352
- end: codeLines.offsetToPosition(range[1]),
353
- },
354
- });
355
- }
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
+ }));
356
319
  }
357
320
  n.type = `Glimmer${n.type}`;
358
321
  allNodeTypes.add(n.type);
359
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
+
360
331
  // ast should not contain comment nodes
361
332
  for (const comment of currentComments) {
362
333
  const parentBody = comment.parent.body || comment.parent.children;
@@ -416,8 +387,8 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
416
387
  result.ast.comments.push(...preprocessedResult.comments);
417
388
 
418
389
  for (const ti of templateInfos) {
419
- const firstIdx = result.ast.tokens.findIndex((t) => t.range[0] === ti.templateRange[0]);
420
- 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]);
421
392
  result.ast.tokens.splice(firstIdx, lastIdx - firstIdx + 1, ...ti.ast.tokens);
422
393
  }
423
394
 
@@ -425,6 +396,7 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
425
396
  traverse(visitorKeys, result.ast, (path) => {
426
397
  const node = path.node;
427
398
  if (!node) return null;
399
+
428
400
  if (
429
401
  node.type === 'ExpressionStatement' ||
430
402
  node.type === 'StaticBlock' ||
@@ -504,6 +476,21 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
504
476
  return null;
505
477
  });
506
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
+
507
494
  if (counter !== templateInfos.length) {
508
495
  throw new Error('failed to process all templates');
509
496
  }
@@ -572,3 +559,6 @@ module.exports.transformForLint = function transformForLint(code) {
572
559
  output: jsCode,
573
560
  };
574
561
  };
562
+
563
+ module.exports.traverse = traverse;
564
+ module.exports.tokenize = tokenize;