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 +1 -1
- package/src/parser/transforms.js +72 -35
package/package.json
CHANGED
package/src/parser/transforms.js
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
|
303
|
+
let codeSlice = sliceByteRange(code, ...n.range);
|
|
269
304
|
for (const part of n.tag.split('.')) {
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|
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
|
-
|
|
299
|
-
|
|
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
|
|
359
|
+
for (const comment of currentComments) {
|
|
327
360
|
const parentBody = comment.parent.body || comment.parent.children;
|
|
328
361
|
const idx = parentBody.indexOf(comment);
|
|
329
|
-
|
|
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.
|
|
404
|
-
(t.
|
|
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
|
-
|
|
440
|
-
n.name
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|
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
|
|
509
|
-
(prev, curr) => prev + (
|
|
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 -
|
|
515
|
-
const total =
|
|
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 -
|
|
521
|
-
const total =
|
|
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
|
}
|