ember-eslint-parser 0.7.0 → 0.8.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 +3 -1
- package/src/parser/hbs-parser.js +4 -1
- package/src/parser/transforms.js +126 -110
- package/src/parser/ts-patch.js +24 -4
- package/src/utils/document.js +26 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember-eslint-parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"repository": {
|
|
@@ -68,6 +68,8 @@
|
|
|
68
68
|
"lint:js": "eslint . --max-warnings=0",
|
|
69
69
|
"lint:js:fix": "eslint . --fix --max-warnings=0",
|
|
70
70
|
"lint:package": "pnpm publint",
|
|
71
|
+
"bench": "vitest bench",
|
|
72
|
+
"bench:compare": "node scripts/bench-compare.mjs",
|
|
71
73
|
"test": "vitest run"
|
|
72
74
|
}
|
|
73
75
|
}
|
package/src/parser/hbs-parser.js
CHANGED
|
@@ -2,6 +2,9 @@ const eslintScope = require('eslint-scope');
|
|
|
2
2
|
const DocumentLines = require('../utils/document');
|
|
3
3
|
const { processGlimmerTemplate, buildGlimmerVisitorKeys } = require('./transforms');
|
|
4
4
|
|
|
5
|
+
// Constant: Program + all Glimmer node types. Computed once at module load.
|
|
6
|
+
const hbsVisitorKeys = { Program: ['body'], ...buildGlimmerVisitorKeys() };
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* implements https://eslint.org/docs/latest/extend/custom-parsers
|
|
7
10
|
* for Handlebars (.hbs) template files.
|
|
@@ -64,7 +67,7 @@ module.exports = {
|
|
|
64
67
|
};
|
|
65
68
|
|
|
66
69
|
// Build visitor keys: Program + all Glimmer node types
|
|
67
|
-
const visitorKeys =
|
|
70
|
+
const visitorKeys = hbsVisitorKeys;
|
|
68
71
|
|
|
69
72
|
// Create an empty scope manager.
|
|
70
73
|
// For HBS, all locals are assumed to be defined at runtime,
|
package/src/parser/transforms.js
CHANGED
|
@@ -3,9 +3,9 @@ const glimmer = require('@glimmer/syntax');
|
|
|
3
3
|
const { visitorKeys: glimmerVisitorKeys } = glimmer;
|
|
4
4
|
const DocumentLines = require('../utils/document');
|
|
5
5
|
const { Reference, Scope, Variable, Definition } = require('eslint-scope');
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
6
|
+
const htmlTagsSet = new Set(require('html-tags').default);
|
|
7
|
+
const svgTagsSet = new Set(require('svg-tags'));
|
|
8
|
+
const mathMLTagsSet = new Set(require('mathml-tag-names').mathmlTagNames);
|
|
9
9
|
|
|
10
10
|
let TypescriptScope = null;
|
|
11
11
|
try {
|
|
@@ -19,31 +19,6 @@ try {
|
|
|
19
19
|
// not available
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const BufferMap = new Map();
|
|
23
|
-
|
|
24
|
-
function getBuffer(str) {
|
|
25
|
-
let buf = BufferMap.get(str);
|
|
26
|
-
if (!buf) {
|
|
27
|
-
buf = Buffer.from(str);
|
|
28
|
-
BufferMap.set(str, buf);
|
|
29
|
-
}
|
|
30
|
-
return buf;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function sliceByteRange(str, a, b) {
|
|
34
|
-
const buf = getBuffer(str);
|
|
35
|
-
return buf.slice(a, b).toString();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function byteToCharIndex(str, byteOffset) {
|
|
39
|
-
const buf = getBuffer(str);
|
|
40
|
-
return buf.slice(0, byteOffset).toString().length;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function byteLength(str) {
|
|
44
|
-
return getBuffer(str).length;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
22
|
/**
|
|
48
23
|
* finds the nearest node scope
|
|
49
24
|
* @param scopeManager
|
|
@@ -67,27 +42,33 @@ function findParentScope(scopeManager, nodePath) {
|
|
|
67
42
|
* tries to find the variable names {name} in any parent scope
|
|
68
43
|
* if the variable is not found it just returns the nearest scope,
|
|
69
44
|
* so that it's usage can be registered.
|
|
45
|
+
*
|
|
46
|
+
* Also returns the nearest scope (equivalent to findParentScope) in one pass,
|
|
47
|
+
* avoiding the redundant second traversal that findParentScope would perform.
|
|
70
48
|
* @param scopeManager
|
|
71
49
|
* @param nodePath
|
|
72
50
|
* @param name
|
|
73
51
|
* @return {{scope: null, variable: *}|{scope: (*|null)}}
|
|
74
52
|
*/
|
|
75
53
|
function findVarInParentScopes(scopeManager, nodePath, name) {
|
|
76
|
-
let
|
|
54
|
+
let defScope = null;
|
|
55
|
+
let currentScope = null;
|
|
77
56
|
let path = nodePath;
|
|
78
57
|
while (path) {
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
58
|
+
const s = scopeManager.acquire(path.node, true);
|
|
59
|
+
if (s) {
|
|
60
|
+
if (!currentScope) currentScope = s;
|
|
61
|
+
if (s.set.has(name)) {
|
|
62
|
+
defScope = s;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
82
65
|
}
|
|
83
66
|
path = path.parentPath;
|
|
84
67
|
}
|
|
85
|
-
|
|
86
|
-
if (!scope) {
|
|
68
|
+
if (!defScope) {
|
|
87
69
|
return { scope: currentScope };
|
|
88
70
|
}
|
|
89
|
-
|
|
90
|
-
return { scope: currentScope, variable: scope.set.get(name) };
|
|
71
|
+
return { scope: currentScope, variable: defScope.set.get(name) };
|
|
91
72
|
}
|
|
92
73
|
|
|
93
74
|
/**
|
|
@@ -115,9 +96,12 @@ function registerNodeInScope(node, scope, variable) {
|
|
|
115
96
|
/**
|
|
116
97
|
* Builds the complete Glimmer visitor keys map with "Glimmer" prefix and
|
|
117
98
|
* additional keys needed for traversal (blockParamNodes, parts, etc).
|
|
99
|
+
* Result is cached since glimmerVisitorKeys is a constant.
|
|
118
100
|
* @return {object}
|
|
119
101
|
*/
|
|
102
|
+
let _cachedGlimmerVisitorKeys = null;
|
|
120
103
|
function buildGlimmerVisitorKeys() {
|
|
104
|
+
if (_cachedGlimmerVisitorKeys) return _cachedGlimmerVisitorKeys;
|
|
121
105
|
const keys = {};
|
|
122
106
|
for (const [k, v] of Object.entries(glimmerVisitorKeys)) {
|
|
123
107
|
keys[`Glimmer${k}`] = [...v];
|
|
@@ -127,6 +111,7 @@ function buildGlimmerVisitorKeys() {
|
|
|
127
111
|
}
|
|
128
112
|
keys.GlimmerProgram = ['body', 'blockParamNodes'];
|
|
129
113
|
keys.GlimmerTemplate = ['body'];
|
|
114
|
+
_cachedGlimmerVisitorKeys = keys;
|
|
130
115
|
return keys;
|
|
131
116
|
}
|
|
132
117
|
|
|
@@ -170,7 +155,7 @@ function traverse(visitorKeys, node, visitor) {
|
|
|
170
155
|
queue.push({
|
|
171
156
|
node: item,
|
|
172
157
|
parent: currentPath.node,
|
|
173
|
-
context:
|
|
158
|
+
context: currentPath.context,
|
|
174
159
|
parentKey: visitorKey,
|
|
175
160
|
parentPath: currentPath,
|
|
176
161
|
});
|
|
@@ -179,7 +164,7 @@ function traverse(visitorKeys, node, visitor) {
|
|
|
179
164
|
queue.push({
|
|
180
165
|
node: child,
|
|
181
166
|
parent: currentPath.node,
|
|
182
|
-
context:
|
|
167
|
+
context: currentPath.context,
|
|
183
168
|
parentKey: visitorKey,
|
|
184
169
|
parentPath: currentPath,
|
|
185
170
|
});
|
|
@@ -200,8 +185,14 @@ function isAlphaNumeric(code) {
|
|
|
200
185
|
);
|
|
201
186
|
}
|
|
202
187
|
|
|
203
|
-
function
|
|
204
|
-
return
|
|
188
|
+
function isWhiteSpaceCode(code) {
|
|
189
|
+
return (
|
|
190
|
+
code === 32 /* space */ ||
|
|
191
|
+
code === 9 /* tab */ ||
|
|
192
|
+
code === 13 /* carriageReturn */ ||
|
|
193
|
+
code === 10 /* lineFeed */ ||
|
|
194
|
+
code === 11 /* verticalTab */
|
|
195
|
+
);
|
|
205
196
|
}
|
|
206
197
|
|
|
207
198
|
/**
|
|
@@ -213,8 +204,7 @@ function isWhiteSpace(code) {
|
|
|
213
204
|
*/
|
|
214
205
|
function tokenize(template, doc, startOffset) {
|
|
215
206
|
const tokens = [];
|
|
216
|
-
let
|
|
217
|
-
let start = 0;
|
|
207
|
+
let wordStart = -1;
|
|
218
208
|
function pushToken(value, type, range) {
|
|
219
209
|
const t = {
|
|
220
210
|
type,
|
|
@@ -229,24 +219,28 @@ function tokenize(template, doc, startOffset) {
|
|
|
229
219
|
};
|
|
230
220
|
tokens.push(t);
|
|
231
221
|
}
|
|
232
|
-
for (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
222
|
+
for (let i = 0; i < template.length; i++) {
|
|
223
|
+
const code = template.charCodeAt(i);
|
|
224
|
+
if (isAlphaNumeric(code)) {
|
|
225
|
+
if (wordStart < 0) {
|
|
226
|
+
wordStart = i;
|
|
236
227
|
}
|
|
237
|
-
current += c;
|
|
238
228
|
} else {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
current = '';
|
|
229
|
+
if (wordStart >= 0) {
|
|
230
|
+
pushToken(template.slice(wordStart, i), 'word', [startOffset + wordStart, startOffset + i]);
|
|
231
|
+
wordStart = -1;
|
|
243
232
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
pushToken(c, 'Punctuator', range);
|
|
233
|
+
if (!isWhiteSpaceCode(code)) {
|
|
234
|
+
pushToken(template[i], 'Punctuator', [startOffset + i, startOffset + i + 1]);
|
|
247
235
|
}
|
|
248
236
|
}
|
|
249
237
|
}
|
|
238
|
+
if (wordStart >= 0) {
|
|
239
|
+
pushToken(template.slice(wordStart), 'word', [
|
|
240
|
+
startOffset + wordStart,
|
|
241
|
+
startOffset + template.length,
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
250
244
|
return tokens;
|
|
251
245
|
}
|
|
252
246
|
|
|
@@ -306,27 +300,55 @@ function removeFromParent(nodes) {
|
|
|
306
300
|
* @return {object[]}
|
|
307
301
|
*/
|
|
308
302
|
function buildTokenStream(rawTokens, comments, textNodes) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
|
|
303
|
+
// Build sorted interval arrays for O(log n) exclusion checks
|
|
304
|
+
const commentIntervals = comments.map((c) => c.range).sort((a, b) => a[0] - b[0]);
|
|
305
|
+
const textNodeIntervals = textNodes.map((t) => t.range).sort((a, b) => a[0] - b[0]);
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Binary-search: is the token's range fully covered by any interval in `intervals`?
|
|
309
|
+
* Intervals must be sorted by start offset.
|
|
310
|
+
* @param {number[]} tokenRange
|
|
311
|
+
* @param {number[][]} intervals
|
|
312
|
+
*/
|
|
313
|
+
function isCovered(tokenRange, intervals) {
|
|
314
|
+
let lo = 0;
|
|
315
|
+
let hi = intervals.length - 1;
|
|
316
|
+
while (lo <= hi) {
|
|
317
|
+
const mid = (lo + hi) >> 1;
|
|
318
|
+
const iv = intervals[mid];
|
|
319
|
+
if (iv[0] <= tokenRange[0] && iv[1] >= tokenRange[1]) {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
if (iv[0] > tokenRange[0]) {
|
|
323
|
+
hi = mid - 1;
|
|
324
|
+
} else {
|
|
325
|
+
lo = mid + 1;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Single-pass filter: drop tokens covered by a comment or text node
|
|
332
|
+
const filteredTokens = rawTokens.filter(
|
|
333
|
+
(t) => !isCovered(t.range, commentIntervals) && !isCovered(t.range, textNodeIntervals)
|
|
314
334
|
);
|
|
315
335
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
336
|
+
// Merge text nodes (already sorted by position from the AST) into filteredTokens
|
|
337
|
+
// using a single linear merge pass instead of repeated splice calls.
|
|
338
|
+
const sortedTextNodes = [...textNodes].sort((a, b) => a.range[0] - b.range[0]);
|
|
339
|
+
const result = [];
|
|
340
|
+
let ti = 0;
|
|
341
|
+
for (const token of filteredTokens) {
|
|
342
|
+
while (ti < sortedTextNodes.length && sortedTextNodes[ti].range[0] < token.range[0]) {
|
|
343
|
+
result.push(sortedTextNodes[ti++]);
|
|
323
344
|
}
|
|
345
|
+
result.push(token);
|
|
324
346
|
}
|
|
325
|
-
|
|
326
|
-
|
|
347
|
+
while (ti < sortedTextNodes.length) {
|
|
348
|
+
result.push(sortedTextNodes[ti++]);
|
|
327
349
|
}
|
|
328
350
|
|
|
329
|
-
return
|
|
351
|
+
return result;
|
|
330
352
|
}
|
|
331
353
|
|
|
332
354
|
/**
|
|
@@ -399,6 +421,18 @@ function processGlimmerTemplate({ templateContent, codeLines, templateRange, tok
|
|
|
399
421
|
});
|
|
400
422
|
}
|
|
401
423
|
|
|
424
|
+
// Nullify empty hashes before the type is renamed
|
|
425
|
+
if (
|
|
426
|
+
(n.type === 'MustacheStatement' ||
|
|
427
|
+
n.type === 'BlockStatement' ||
|
|
428
|
+
n.type === 'SubExpression') &&
|
|
429
|
+
n.hash &&
|
|
430
|
+
n.hash.pairs &&
|
|
431
|
+
n.hash.pairs.length === 0
|
|
432
|
+
) {
|
|
433
|
+
n.hash = null;
|
|
434
|
+
}
|
|
435
|
+
|
|
402
436
|
n.type = `Glimmer${n.type}`;
|
|
403
437
|
}
|
|
404
438
|
|
|
@@ -409,17 +443,6 @@ function processGlimmerTemplate({ templateContent, codeLines, templateRange, tok
|
|
|
409
443
|
comment.type = 'Block';
|
|
410
444
|
}
|
|
411
445
|
|
|
412
|
-
// Nullify empty hashes
|
|
413
|
-
for (const n of allNodes) {
|
|
414
|
-
if (
|
|
415
|
-
['GlimmerMustacheStatement', 'GlimmerBlockStatement', 'GlimmerSubExpression'].includes(n.type)
|
|
416
|
-
) {
|
|
417
|
-
if (n.hash && n.hash.pairs && n.hash.pairs.length === 0) {
|
|
418
|
-
n.hash = null;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
446
|
// Build final token stream
|
|
424
447
|
ast.tokens = buildTokenStream(
|
|
425
448
|
tokenize(tokenSource || templateContent, codeLines, offset),
|
|
@@ -442,23 +465,18 @@ function processGlimmerTemplate({ templateContent, codeLines, templateRange, tok
|
|
|
442
465
|
*/
|
|
443
466
|
module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(info, code) {
|
|
444
467
|
const templateInfos = info.templateInfos.map((r) => ({
|
|
445
|
-
|
|
446
|
-
templateRange: [r.range.startByte, r.range.endByte],
|
|
447
|
-
utf16Range: [byteToCharIndex(code, r.range.startByte), byteToCharIndex(code, r.range.endByte)],
|
|
468
|
+
utf16Range: [r.range.startUtf16Codepoint, r.range.endUtf16Codepoint],
|
|
448
469
|
}));
|
|
449
470
|
const codeLines = new DocumentLines(code);
|
|
450
471
|
const allComments = [];
|
|
451
|
-
const templateVisitorKeys = {};
|
|
452
472
|
|
|
453
473
|
for (const tpl of templateInfos) {
|
|
454
|
-
const
|
|
455
|
-
const template = code.slice(...range);
|
|
474
|
+
const template = code.slice(...tpl.utf16Range);
|
|
456
475
|
|
|
457
476
|
const { ast, comments } = processGlimmerTemplate({
|
|
458
477
|
templateContent: template,
|
|
459
478
|
codeLines,
|
|
460
479
|
templateRange: [...tpl.utf16Range],
|
|
461
|
-
tokenSource: sliceByteRange(code, ...tpl.templateRange),
|
|
462
480
|
});
|
|
463
481
|
|
|
464
482
|
ast.content = template;
|
|
@@ -466,12 +484,8 @@ module.exports.preprocessGlimmerTemplates = function preprocessGlimmerTemplates(
|
|
|
466
484
|
tpl.ast = ast;
|
|
467
485
|
}
|
|
468
486
|
|
|
469
|
-
for (const [k, v] of Object.entries(glimmerVisitorKeys)) {
|
|
470
|
-
templateVisitorKeys[`Glimmer${k}`] = [...v];
|
|
471
|
-
}
|
|
472
|
-
|
|
473
487
|
return {
|
|
474
|
-
templateVisitorKeys,
|
|
488
|
+
templateVisitorKeys: buildGlimmerVisitorKeys(),
|
|
475
489
|
templateInfos,
|
|
476
490
|
comments: allComments,
|
|
477
491
|
};
|
|
@@ -499,6 +513,9 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
|
|
|
499
513
|
result.ast.tokens.splice(firstIdx, lastIdx - firstIdx + 1, ...ti.ast.tokens);
|
|
500
514
|
}
|
|
501
515
|
|
|
516
|
+
// Build a Map keyed by range start for O(1) lookup during traversal
|
|
517
|
+
const templateInfoByStart = new Map(templateInfos.map((t) => [t.utf16Range[0], t]));
|
|
518
|
+
|
|
502
519
|
// eslint-disable-next-line complexity
|
|
503
520
|
traverse(visitorKeys, result.ast, (path) => {
|
|
504
521
|
const node = path.node;
|
|
@@ -515,12 +532,11 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
|
|
|
515
532
|
range = [node.declaration.range[0], node.declaration.range[1]];
|
|
516
533
|
}
|
|
517
534
|
|
|
518
|
-
const template =
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
)
|
|
523
|
-
if (!template) {
|
|
535
|
+
const template = templateInfoByStart.get(range[0]);
|
|
536
|
+
if (
|
|
537
|
+
!template ||
|
|
538
|
+
(template.utf16Range[1] !== range[1] && template.utf16Range[1] !== range[1] + 1)
|
|
539
|
+
) {
|
|
524
540
|
return null;
|
|
525
541
|
}
|
|
526
542
|
counter++;
|
|
@@ -570,9 +586,9 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
|
|
|
570
586
|
const registerUndef =
|
|
571
587
|
isUpperCase(n.name[0]) ||
|
|
572
588
|
node.name.includes('.') ||
|
|
573
|
-
(!
|
|
574
|
-
!
|
|
575
|
-
!
|
|
589
|
+
(!htmlTagsSet.has(node.name) &&
|
|
590
|
+
!svgTagsSet.has(node.name) &&
|
|
591
|
+
!mathMLTagsSet.has(node.name));
|
|
576
592
|
|
|
577
593
|
if (!ignore && (variable || registerUndef)) {
|
|
578
594
|
registerNodeInScope(n, scope, variable);
|
|
@@ -622,7 +638,7 @@ module.exports.convertAst = function convertAst(result, preprocessedResult, visi
|
|
|
622
638
|
};
|
|
623
639
|
|
|
624
640
|
const replaceRange = function replaceRange(s, start, end, substitute) {
|
|
625
|
-
return
|
|
641
|
+
return s.slice(0, start) + substitute + s.slice(end);
|
|
626
642
|
};
|
|
627
643
|
module.exports.replaceRange = replaceRange;
|
|
628
644
|
|
|
@@ -725,25 +741,25 @@ module.exports.transformForLint = function transformForLint(code, fileName) {
|
|
|
725
741
|
for (const tplInfo of result.reverse()) {
|
|
726
742
|
const content = tplInfo.contents.replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
727
743
|
if (tplInfo.type === 'class-member') {
|
|
728
|
-
const tplLength = tplInfo.range.
|
|
729
|
-
const spaces = tplLength -
|
|
744
|
+
const tplLength = tplInfo.range.endUtf16Codepoint - tplInfo.range.startUtf16Codepoint;
|
|
745
|
+
const spaces = tplLength - content.length - 'static{`'.length - '`}'.length;
|
|
730
746
|
const total = content + ' '.repeat(spaces);
|
|
731
747
|
const replacementCode = `static{\`${total}\`}`;
|
|
732
748
|
jsCode = replaceRange(
|
|
733
749
|
jsCode,
|
|
734
|
-
tplInfo.range.
|
|
735
|
-
tplInfo.range.
|
|
750
|
+
tplInfo.range.startUtf16Codepoint,
|
|
751
|
+
tplInfo.range.endUtf16Codepoint,
|
|
736
752
|
replacementCode
|
|
737
753
|
);
|
|
738
754
|
} else {
|
|
739
|
-
const tplLength = tplInfo.range.
|
|
740
|
-
const spaces = tplLength -
|
|
755
|
+
const tplLength = tplInfo.range.endUtf16Codepoint - tplInfo.range.startUtf16Codepoint;
|
|
756
|
+
const spaces = tplLength - content.length - '`'.length - '`'.length;
|
|
741
757
|
const total = content + ' '.repeat(spaces);
|
|
742
758
|
const replacementCode = `\`${total}\``;
|
|
743
759
|
jsCode = replaceRange(
|
|
744
760
|
jsCode,
|
|
745
|
-
tplInfo.range.
|
|
746
|
-
tplInfo.range.
|
|
761
|
+
tplInfo.range.startUtf16Codepoint,
|
|
762
|
+
tplInfo.range.endUtf16Codepoint,
|
|
747
763
|
replacementCode
|
|
748
764
|
);
|
|
749
765
|
}
|
package/src/parser/ts-patch.js
CHANGED
|
@@ -80,12 +80,32 @@ try {
|
|
|
80
80
|
let jsCode = code;
|
|
81
81
|
const sourceFile = ts.createSourceFile('__x__.ts', code, ts.ScriptTarget.Latest);
|
|
82
82
|
const length = jsCode.length;
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
function visit(node) {
|
|
84
|
+
if (
|
|
85
|
+
node.kind === ts.SyntaxKind.ImportDeclaration &&
|
|
86
|
+
node.moduleSpecifier.text.endsWith('.gts')
|
|
87
|
+
) {
|
|
88
|
+
const value = node.moduleSpecifier.text.replace(/\.gts$/, '.mts');
|
|
89
|
+
jsCode = replaceRange(
|
|
90
|
+
jsCode,
|
|
91
|
+
node.moduleSpecifier.pos + 2,
|
|
92
|
+
node.moduleSpecifier.end - 1,
|
|
93
|
+
value
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (
|
|
97
|
+
node.kind === ts.SyntaxKind.CallExpression &&
|
|
98
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
99
|
+
) {
|
|
100
|
+
const arg = node.arguments[0];
|
|
101
|
+
if (arg && arg.kind === ts.SyntaxKind.StringLiteral && arg.text.endsWith('.gts')) {
|
|
102
|
+
const value = arg.text.replace(/\.gts$/, '.mts');
|
|
103
|
+
jsCode = replaceRange(jsCode, arg.getStart(sourceFile) + 1, arg.end - 1, value); // +1/-1 to skip surrounding quotes
|
|
104
|
+
}
|
|
87
105
|
}
|
|
106
|
+
ts.forEachChild(node, visit);
|
|
88
107
|
}
|
|
108
|
+
visit(sourceFile);
|
|
89
109
|
if (length !== jsCode.length) {
|
|
90
110
|
throw new Error('bad replacement');
|
|
91
111
|
}
|
package/src/utils/document.js
CHANGED
|
@@ -28,12 +28,20 @@ class DocumentLines {
|
|
|
28
28
|
*/
|
|
29
29
|
offsetToPosition(position) {
|
|
30
30
|
const lineStarts = this.lineStarts;
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
let lo = 0;
|
|
32
|
+
let hi = lineStarts.length - 1;
|
|
33
|
+
// Upper-biased midpoint is required here: we want the *rightmost* line whose
|
|
34
|
+
// start is ≤ position. With a lower-biased mid, when lo + 1 === hi and the
|
|
35
|
+
// condition is true, mid === lo and lo never advances, causing an infinite loop.
|
|
36
|
+
while (lo < hi) {
|
|
37
|
+
const mid = (lo + hi + 1) >> 1;
|
|
38
|
+
if (lineStarts[mid] <= position) {
|
|
39
|
+
lo = mid;
|
|
40
|
+
} else {
|
|
41
|
+
hi = mid - 1;
|
|
42
|
+
}
|
|
34
43
|
}
|
|
35
|
-
|
|
36
|
-
return { line: line + 1, column };
|
|
44
|
+
return { line: lo + 1, column: position - lineStarts[lo] };
|
|
37
45
|
}
|
|
38
46
|
}
|
|
39
47
|
|
|
@@ -46,27 +54,20 @@ function computeLineStarts(text) {
|
|
|
46
54
|
let pos = 0;
|
|
47
55
|
let lineStart = 0;
|
|
48
56
|
while (pos < text.length) {
|
|
49
|
-
const ch = text.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (text.codePointAt(pos) === 10 /* lineFeed */) {
|
|
54
|
-
pos++;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// falls through
|
|
58
|
-
case 10 /* lineFeed */: {
|
|
59
|
-
result.push(lineStart);
|
|
60
|
-
lineStart = pos;
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
default: {
|
|
64
|
-
if (ch > 127 /* maxAsciiCharacter */ && isLineBreak(ch)) {
|
|
65
|
-
result.push(lineStart);
|
|
66
|
-
lineStart = pos;
|
|
67
|
-
}
|
|
68
|
-
break;
|
|
57
|
+
const ch = text.charCodeAt(pos++);
|
|
58
|
+
if (ch === 13 /* carriageReturn */) {
|
|
59
|
+
if (text.charCodeAt(pos) === 10 /* lineFeed */) {
|
|
60
|
+
pos++;
|
|
69
61
|
}
|
|
62
|
+
result.push(lineStart);
|
|
63
|
+
lineStart = pos;
|
|
64
|
+
} else if (
|
|
65
|
+
ch === 10 /* lineFeed */ ||
|
|
66
|
+
ch === 8232 /* lineSeparator */ ||
|
|
67
|
+
ch === 8233 /* paragraphSeparator */
|
|
68
|
+
) {
|
|
69
|
+
result.push(lineStart);
|
|
70
|
+
lineStart = pos;
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
result.push(lineStart);
|