ember-eslint-parser 0.2.3 → 0.2.5

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.3",
3
+ "version": "0.2.5",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -6,6 +6,31 @@ const TypescriptScope = require('@typescript-eslint/scope-manager');
6
6
  const { Reference, Scope, Variable, Definition } = require('eslint-scope');
7
7
  const htmlTags = require('html-tags');
8
8
 
9
+ const BufferMap = new Map();
10
+
11
+ function getBuffer(str) {
12
+ let buf = BufferMap.get(str);
13
+ if (!buf) {
14
+ buf = Buffer.from(str);
15
+ BufferMap.set(str, buf);
16
+ }
17
+ return buf;
18
+ }
19
+
20
+ function sliceByteRange(str, a, b) {
21
+ const buf = getBuffer(str);
22
+ return buf.slice(a, b).toString();
23
+ }
24
+
25
+ function byteToCharIndex(str, byteOffset) {
26
+ const buf = getBuffer(str);
27
+ return buf.slice(0, byteOffset + 1).toString().length - 1;
28
+ }
29
+
30
+ function byteLength(str) {
31
+ return getBuffer(str).length;
32
+ }
33
+
9
34
  /**
10
35
  * finds the nearest node scope
11
36
  * @param scopeManager
@@ -89,6 +114,7 @@ function traverse(visitorKeys, node, visitor) {
89
114
  parent: null,
90
115
  parentKey: null,
91
116
  parentPath: null,
117
+ context: {},
92
118
  });
93
119
 
94
120
  while (queue.length > 0) {
@@ -111,6 +137,7 @@ function traverse(visitorKeys, node, visitor) {
111
137
  queue.push({
112
138
  node: item,
113
139
  parent: currentPath.node,
140
+ context: Object.assign({}, currentPath.context),
114
141
  parentKey: visitorKey,
115
142
  parentPath: currentPath,
116
143
  });
@@ -119,6 +146,7 @@ function traverse(visitorKeys, node, visitor) {
119
146
  queue.push({
120
147
  node: child,
121
148
  parent: currentPath.node,
149
+ context: Object.assign({}, currentPath.context),
122
150
  parentKey: visitorKey,
123
151
  parentPath: currentPath,
124
152
  });
@@ -202,17 +230,23 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
202
230
  const templateInfos = info.templateInfos.map((r) => ({
203
231
  range: [r.contentRange.start, r.contentRange.end],
204
232
  templateRange: [r.range.start, r.range.end],
233
+ utf16Range: [byteToCharIndex(code, r.range.start), byteToCharIndex(code, r.range.end)],
205
234
  }));
206
235
  const templateVisitorKeys = {};
207
236
  const codeLines = new DocumentLines(code);
208
237
  const comments = [];
209
- const textNodes = [];
210
238
  for (const tpl of templateInfos) {
239
+ const currentComments = [];
240
+ const textNodes = [];
211
241
  const range = tpl.range;
212
- const template = code.slice(...range);
242
+ const template = sliceByteRange(code, ...range);
213
243
  const docLines = new DocumentLines(template);
214
244
  const ast = glimmer.preprocess(template, { mode: 'codemod' });
215
- ast.tokens = tokenize(code.slice(...tpl.templateRange), codeLines, tpl.templateRange[0]);
245
+ ast.tokens = tokenize(
246
+ sliceByteRange(code, ...tpl.templateRange),
247
+ codeLines,
248
+ tpl.templateRange[0]
249
+ );
216
250
  const allNodes = [];
217
251
  glimmer.traverse(ast, {
218
252
  All(node, path) {
@@ -221,6 +255,7 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
221
255
  allNodes.push(node);
222
256
  if (node.type === 'CommentStatement' || node.type === 'MustacheCommentStatement') {
223
257
  comments.push(node);
258
+ currentComments.push(node);
224
259
  }
225
260
  if (node.type === 'TextNode') {
226
261
  n.value = node.chars;
@@ -265,17 +300,12 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
265
300
  n.name = n.tag;
266
301
  n.parts = [];
267
302
  let start = n.range[0];
268
- let codeSlice = code.slice(...n.range);
303
+ let codeSlice = sliceByteRange(code, ...n.range);
269
304
  for (const part of n.tag.split('.')) {
270
- let pattern = `\\b${part}\\b`;
271
- if (part.startsWith(':')) {
272
- pattern = `${part}\\b`;
273
- }
274
- const regex = new RegExp(pattern);
275
- const match = codeSlice.match(regex);
276
- const range = [start + match.index, 0];
305
+ const idx = codeSlice.indexOf(part);
306
+ const range = [start + idx, 0];
277
307
  range[1] = range[0] + part.length;
278
- codeSlice = code.slice(range[1], n.range[1]);
308
+ codeSlice = sliceByteRange(code, range[1], n.range[1]);
279
309
  start = range[1];
280
310
  n.parts.push({
281
311
  type: 'GlimmerElementNodePart',
@@ -295,8 +325,11 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
295
325
  n.params = [];
296
326
  }
297
327
  if ('blockParams' in n && n.parent) {
298
- let part = code.slice(...n.parent.range);
299
- let start = n.parent.range[0];
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];
300
333
  let idx = part.indexOf('|') + 1;
301
334
  start += idx;
302
335
  part = part.slice(idx, -1);
@@ -323,10 +356,12 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
323
356
  allNodeTypes.add(n.type);
324
357
  }
325
358
  // ast should not contain comment nodes
326
- for (const comment of comments) {
359
+ for (const comment of currentComments) {
327
360
  const parentBody = comment.parent.body || comment.parent.children;
328
361
  const idx = parentBody.indexOf(comment);
329
- parentBody.splice(idx, 1);
362
+ if (idx >= 0) {
363
+ parentBody.splice(idx, 1);
364
+ }
330
365
  // comment type can be a block comment or a line comment
331
366
  // mark comments as always block comment, this works for eslint in all cases
332
367
  comment.type = 'Block';
@@ -400,8 +435,8 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
400
435
 
401
436
  const template = templateInfos.find(
402
437
  (t) =>
403
- t.templateRange[0] === range[0] &&
404
- (t.templateRange[1] === range[1] || t.templateRange[1] === range[1] + 1)
438
+ t.utf16Range[0] === range[0] &&
439
+ (t.utf16Range[1] === range[1] || t.utf16Range[1] === range[1] + 1)
405
440
  );
406
441
  if (!template) {
407
442
  return null;
@@ -423,6 +458,9 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
423
458
  }
424
459
  }
425
460
  if (node.type === 'GlimmerElementNode') {
461
+ if (node.tag === 'svg') {
462
+ path.context.inSvg = true;
463
+ }
426
464
  // always reference first part of tag name, this also has the advantage
427
465
  // that errors regarding this tag will only mark the tag name instead of
428
466
  // the whole tag + children
@@ -430,21 +468,19 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
430
468
  const { scope, variable } = findVarInParentScopes(result.scopeManager, path, n.name) || {};
431
469
  /*
432
470
  register a node in scope if we found a variable
433
- we ignore named-blocks as we know that it doesn't reference anything in current scope
471
+ we ignore named-blocks and args as we know that it doesn't reference anything in current scope
472
+ we also ignore `this`
434
473
  if we do not find a variable we register it with a missing variable if
435
474
  * it starts with upper case, it should be a component with a reference
436
475
  * it includes a dot, it's a path which should have a reference
437
476
  * it's NOT a standard html tag, it should have a referenced variable
438
477
  */
439
- if (
440
- n.name !== 'this' &&
441
- !n.name.startsWith(':') &&
442
- scope &&
443
- (variable ||
444
- isUpperCase(n.name[0]) ||
445
- node.name.includes('.') ||
446
- !htmlTags.includes(node.name))
447
- ) {
478
+ const ignore =
479
+ n.name === 'this' || n.name.startsWith(':') || n.name.startsWith('@') || !scope;
480
+ const registerUndef =
481
+ isUpperCase(n.name[0]) || node.name.includes('.') || !htmlTags.includes(node.name);
482
+ const allowUndef = path.parentPath?.context.inSvg;
483
+ if (!ignore && (variable || (registerUndef && !allowUndef))) {
448
484
  registerNodeInScope(n, scope, variable);
449
485
  }
450
486
  }
@@ -471,7 +507,7 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
471
507
  };
472
508
 
473
509
  const replaceRange = function replaceRange(s, start, end, substitute) {
474
- return s.slice(0, start) + substitute + s.slice(end);
510
+ return sliceByteRange(s, 0, start) + substitute + sliceByteRange(s, end);
475
511
  };
476
512
  module.exports.replaceRange = replaceRange;
477
513
 
@@ -505,20 +541,21 @@ module.exports.transformForLint = function transformForLint(code) {
505
541
  */
506
542
  const result = processor.parse(code);
507
543
  for (const tplInfo of result.reverse()) {
508
- const lineBreaks = [...tplInfo.contents].reduce(
509
- (prev, curr) => prev + (DocumentLines.isLineBreak(curr.codePointAt(0)) ? 1 : 0),
544
+ const backticks = [...tplInfo.contents].reduce(
545
+ (prev, curr) => (prev + (curr.codePointAt(0) === '`') ? 1 : 0),
510
546
  0
511
547
  );
548
+ const content = tplInfo.contents.replace(/`/g, '\\`');
512
549
  if (tplInfo.type === 'class-member') {
513
550
  const tplLength = tplInfo.range.end - tplInfo.range.start;
514
- const spaces = tplLength - 'static{`'.length - '`}'.length - lineBreaks;
515
- const total = ' '.repeat(spaces) + '\n'.repeat(lineBreaks);
551
+ const spaces = tplLength - byteLength(content) - 'static{`'.length - '`}'.length - backticks;
552
+ const total = content + ' '.repeat(spaces);
516
553
  const replacementCode = `static{\`${total}\`}`;
517
554
  jsCode = replaceRange(jsCode, tplInfo.range.start, tplInfo.range.end, replacementCode);
518
555
  } else {
519
556
  const tplLength = tplInfo.range.end - tplInfo.range.start;
520
- const spaces = tplLength - '""`'.length - '`'.length - lineBreaks;
521
- const total = ' '.repeat(spaces) + '\n'.repeat(lineBreaks);
557
+ const spaces = tplLength - byteLength(content) - '""`'.length - '`'.length - backticks;
558
+ const total = content + ' '.repeat(spaces);
522
559
  const replacementCode = `""\`${total}\``;
523
560
  jsCode = replaceRange(jsCode, tplInfo.range.start, tplInfo.range.end, replacementCode);
524
561
  }