depvitals 0.2.0 → 0.4.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/README.md +95 -30
- package/dist/cli/entry.js +8 -21
- package/dist/cli/entry.js.map +1 -1
- package/dist/cli/parseCliArgs.d.ts +5 -2
- package/dist/cli/parseCliArgs.d.ts.map +1 -1
- package/dist/cli/parseCliArgs.js +51 -36
- package/dist/cli/parseCliArgs.js.map +1 -1
- package/dist/cli/runCli.d.ts +4 -5
- package/dist/cli/runCli.d.ts.map +1 -1
- package/dist/cli/runCli.js +41 -46
- package/dist/cli/runCli.js.map +1 -1
- package/dist/config/ignoreDependencies.d.ts +9 -0
- package/dist/config/ignoreDependencies.d.ts.map +1 -0
- package/dist/config/ignoreDependencies.js +22 -0
- package/dist/config/ignoreDependencies.js.map +1 -0
- package/dist/config/ignorePaths.d.ts +13 -0
- package/dist/config/ignorePaths.d.ts.map +1 -0
- package/dist/config/ignorePaths.js +47 -0
- package/dist/config/ignorePaths.js.map +1 -0
- package/dist/config/projectConfigError.d.ts +7 -0
- package/dist/config/projectConfigError.d.ts.map +1 -0
- package/dist/config/projectConfigError.js +10 -0
- package/dist/config/projectConfigError.js.map +1 -0
- package/dist/config/readProjectConfig.d.ts +28 -0
- package/dist/config/readProjectConfig.d.ts.map +1 -0
- package/dist/config/readProjectConfig.js +120 -0
- package/dist/config/readProjectConfig.js.map +1 -0
- package/dist/core/analyzeProject.d.ts +40 -1
- package/dist/core/analyzeProject.d.ts.map +1 -1
- package/dist/core/analyzeProject.js +189 -45
- package/dist/core/analyzeProject.js.map +1 -1
- package/dist/core/evidence.d.ts +67 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +115 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/scanContext.d.ts +20 -0
- package/dist/core/scanContext.d.ts.map +1 -0
- package/dist/core/scanContext.js +43 -0
- package/dist/core/scanContext.js.map +1 -0
- package/dist/detectors/eslintManifestConfig.d.ts +18 -0
- package/dist/detectors/eslintManifestConfig.d.ts.map +1 -1
- package/dist/detectors/eslintManifestConfig.js +62 -80
- package/dist/detectors/eslintManifestConfig.js.map +1 -1
- package/dist/detectors/eslintRcJsonConfig.d.ts +8 -2
- package/dist/detectors/eslintRcJsonConfig.d.ts.map +1 -1
- package/dist/detectors/eslintRcJsonConfig.js +27 -12
- package/dist/detectors/eslintRcJsonConfig.js.map +1 -1
- package/dist/detectors/jestManifestConfig.d.ts +15 -3
- package/dist/detectors/jestManifestConfig.d.ts.map +1 -1
- package/dist/detectors/jestManifestConfig.js +63 -83
- package/dist/detectors/jestManifestConfig.js.map +1 -1
- package/dist/reporters/jsonReporter.d.ts +39 -0
- package/dist/reporters/jsonReporter.d.ts.map +1 -0
- package/dist/reporters/jsonReporter.js +91 -0
- package/dist/reporters/jsonReporter.js.map +1 -0
- package/dist/reporters/terminalReporter.d.ts +15 -4
- package/dist/reporters/terminalReporter.d.ts.map +1 -1
- package/dist/reporters/terminalReporter.js +64 -4
- package/dist/reporters/terminalReporter.js.map +1 -1
- package/dist/scanner/moduleSpecifiers.d.ts +12 -2
- package/dist/scanner/moduleSpecifiers.d.ts.map +1 -1
- package/dist/scanner/moduleSpecifiers.js +1154 -183
- package/dist/scanner/moduleSpecifiers.js.map +1 -1
- package/dist/scanner/packageSpecifier.d.ts +26 -2
- package/dist/scanner/packageSpecifier.d.ts.map +1 -1
- package/dist/scanner/packageSpecifier.js +70 -2
- package/dist/scanner/packageSpecifier.js.map +1 -1
- package/dist/scanner/projectManifest.d.ts +28 -3
- package/dist/scanner/projectManifest.d.ts.map +1 -1
- package/dist/scanner/projectManifest.js +58 -13
- package/dist/scanner/projectManifest.js.map +1 -1
- package/dist/scanner/scriptUsage.d.ts +15 -1
- package/dist/scanner/scriptUsage.d.ts.map +1 -1
- package/dist/scanner/scriptUsage.js +284 -69
- package/dist/scanner/scriptUsage.js.map +1 -1
- package/dist/scanner/sourceFiles.d.ts +21 -2
- package/dist/scanner/sourceFiles.d.ts.map +1 -1
- package/dist/scanner/sourceFiles.js +19 -6
- package/dist/scanner/sourceFiles.js.map +1 -1
- package/package.json +4 -2
- package/schemas/README.md +47 -0
- package/schemas/examples/README.md +27 -0
- package/schemas/examples/explain-excluded.json +53 -0
- package/schemas/examples/explain-missing.json +65 -0
- package/schemas/examples/explain-node-preload.json +70 -0
- package/schemas/examples/explain-unknown.json +53 -0
- package/schemas/examples/scan-clean.json +40 -0
- package/schemas/examples/scan-findings.json +80 -0
- package/schemas/report-v1.schema.json +376 -0
|
@@ -43,14 +43,55 @@ export class ModuleSpecifierError extends Error {
|
|
|
43
43
|
* from which the scanner cannot safely continue.
|
|
44
44
|
*/
|
|
45
45
|
export function extractModuleSpecifiers(source, options = {}) {
|
|
46
|
-
return
|
|
46
|
+
return collectModuleSpecifiers(source, options).map(({ specifier, kind }) => ({
|
|
47
|
+
specifier,
|
|
48
|
+
kind,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Authoritative occurrence collector. The lexer must finish successfully before locations escape.
|
|
53
|
+
* Recognition is shared with the legacy projection above; no additional extraction is performed.
|
|
54
|
+
*/
|
|
55
|
+
export function collectModuleSpecifiers(source, options = {}) {
|
|
56
|
+
const jsx = options.jsx ?? false;
|
|
57
|
+
// Ambiguous input gets one non-interpreting lexical probe and one definitive extraction. No
|
|
58
|
+
// source reread, parser retry after an exception, or repeated per-statement suffix scans.
|
|
59
|
+
const module = options.module === 'detect'
|
|
60
|
+
? tokenize(source, jsx, false, true).some((token) => token.moduleSignal === true)
|
|
61
|
+
: (options.module ?? false);
|
|
62
|
+
const occurrences = interpret(tokenize(source, jsx, module));
|
|
63
|
+
let cursor = 0;
|
|
64
|
+
let line = 1;
|
|
65
|
+
let column = 1;
|
|
66
|
+
// Interpreter output is in literal encounter order. Each original UTF-16 unit is visited at
|
|
67
|
+
// most once, except a bounded CRLF lookahead; no per-occurrence prefix/suffix scan is needed.
|
|
68
|
+
return occurrences.map(({ specifier, kind, offset }) => {
|
|
69
|
+
while (cursor < offset) {
|
|
70
|
+
const character = source[cursor++];
|
|
71
|
+
if (character === '\r') {
|
|
72
|
+
if (source[cursor] === '\n')
|
|
73
|
+
cursor++;
|
|
74
|
+
line++;
|
|
75
|
+
column = 1;
|
|
76
|
+
}
|
|
77
|
+
else if (character === '\n' || character === '\u2028' || character === '\u2029') {
|
|
78
|
+
line++;
|
|
79
|
+
column = 1;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
column++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { specifier, kind, position: { offset, line, column } };
|
|
86
|
+
});
|
|
47
87
|
}
|
|
48
88
|
/**
|
|
49
89
|
* Turn source into a stream of significant tokens, skipping whitespace, line/block comments, regex
|
|
50
90
|
* literals and template literals.
|
|
51
91
|
*/
|
|
52
|
-
function tokenize(source, jsx = false, module = false) {
|
|
92
|
+
function tokenize(source, jsx = false, module = false, detectModule = false) {
|
|
53
93
|
const tokens = [];
|
|
94
|
+
const typeArgumentBounds = new Map();
|
|
54
95
|
const n = source.length;
|
|
55
96
|
let i = 0;
|
|
56
97
|
// Whether the previous identifier token is an expression-starting keyword (return, typeof, ...).
|
|
@@ -82,7 +123,14 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
82
123
|
if (memberAtToken !== undefined) {
|
|
83
124
|
advanceMember(memberAtToken, significant, tokens[tokens.length - 1]);
|
|
84
125
|
}
|
|
85
|
-
|
|
126
|
+
const scope = requireScopes[requireScopes.length - 1];
|
|
127
|
+
tokens.push(scope?.inParameters === true
|
|
128
|
+
? {
|
|
129
|
+
...significant,
|
|
130
|
+
parameterFrame: frames.length,
|
|
131
|
+
...(typeAnnotation === undefined ? {} : { parameterType: true }),
|
|
132
|
+
}
|
|
133
|
+
: significant);
|
|
86
134
|
if (token.kind !== 'punct' || token.syntheticBoundary !== true) {
|
|
87
135
|
htmlCloseCommentAllowed = false;
|
|
88
136
|
}
|
|
@@ -108,6 +156,19 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
108
156
|
// Function context of every open function body, tracked independently of expression frame resets
|
|
109
157
|
// so `${}` boundaries cannot change the nearest await/yield grammar.
|
|
110
158
|
const functionContextStack = [];
|
|
159
|
+
const requireScopes = [];
|
|
160
|
+
const closeRequireScope = (scope) => {
|
|
161
|
+
if (scope !== undefined && requireScopes[requireScopes.length - 1] === scope) {
|
|
162
|
+
requireScopes.pop();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const recordRequireBinding = (parameters, end) => {
|
|
166
|
+
const scope = parameters.signature?.context.requireScope;
|
|
167
|
+
const bindingEnd = parameters.bindingEnd ?? end;
|
|
168
|
+
if (scope !== undefined && parameters.bindingStart < bindingEnd) {
|
|
169
|
+
scope.bindings.push({ start: parameters.bindingStart, end: bindingEnd });
|
|
170
|
+
}
|
|
171
|
+
};
|
|
111
172
|
// The nearest function body owns the await/yield grammar. An ordinary nested function therefore
|
|
112
173
|
// suspends an async/generator context from an outer function instead of inheriting it.
|
|
113
174
|
// A default callback may open and finish while an outer signature is still reading parameters.
|
|
@@ -139,6 +200,12 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
139
200
|
frames.length === context.frameDepth;
|
|
140
201
|
const insideObjectLiteral = () => braceContexts[braceContexts.length - 1]?.role === 'object';
|
|
141
202
|
const insideClassBody = () => braceContexts[braceContexts.length - 1]?.role === 'class';
|
|
203
|
+
const atMethodContainer = () => {
|
|
204
|
+
const brace = braceContexts[braceContexts.length - 1];
|
|
205
|
+
return brace?.role === 'object'
|
|
206
|
+
? atLexicalDepth(brace.depth)
|
|
207
|
+
: brace?.role === 'class' && atLexicalDepth(brace.state);
|
|
208
|
+
};
|
|
142
209
|
const currentClass = () => {
|
|
143
210
|
const brace = braceContexts[braceContexts.length - 1];
|
|
144
211
|
return brace?.role === 'class' ? brace.state : undefined;
|
|
@@ -166,8 +233,8 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
166
233
|
pendingControlKeyword = undefined;
|
|
167
234
|
closedObjectBrace = false;
|
|
168
235
|
};
|
|
169
|
-
// JSX markup and sibling
|
|
170
|
-
// boundary keeps the token stream from gluing
|
|
236
|
+
// Template interpolations, JSX markup and sibling containers are separate expressions. An
|
|
237
|
+
// explicit boundary keeps the token stream from gluing them into an import/require sequence that
|
|
171
238
|
// does not exist in source (for example `<p>{import}{('pkg')}</p>`).
|
|
172
239
|
const pushBoundary = (offset) => {
|
|
173
240
|
emit({
|
|
@@ -228,6 +295,8 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
228
295
|
? { typeOwner: { annotation: typeAnnotation, depth: { ...typeAnnotation } } }
|
|
229
296
|
: {}),
|
|
230
297
|
});
|
|
298
|
+
// The preceding tag is not the callee of tokens inside this interpolation.
|
|
299
|
+
pushBoundary(i);
|
|
231
300
|
inText = false;
|
|
232
301
|
resetExpressionState();
|
|
233
302
|
i += 2;
|
|
@@ -361,6 +430,35 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
361
430
|
typeAnnotation = undefined;
|
|
362
431
|
inType = false;
|
|
363
432
|
}
|
|
433
|
+
const expressionOwner = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
434
|
+
if (typeAnnotation === undefined &&
|
|
435
|
+
ch === '<' &&
|
|
436
|
+
prevKind === 'identifier' &&
|
|
437
|
+
expressionOwner !== undefined &&
|
|
438
|
+
atLexicalDepth(expressionOwner)) {
|
|
439
|
+
const end = expressionTypeArgumentEnd(source, i, n, typeArgumentBounds);
|
|
440
|
+
if (end !== undefined) {
|
|
441
|
+
// Use the ordinary type overlay: it preserves template imports and lexical scopes.
|
|
442
|
+
typeAnnotation = {
|
|
443
|
+
role: 'assertion',
|
|
444
|
+
conditionalDepth: 0,
|
|
445
|
+
conditionalOperand: false,
|
|
446
|
+
importCallPending: false,
|
|
447
|
+
owner: {
|
|
448
|
+
parenDepth,
|
|
449
|
+
bracketDepth,
|
|
450
|
+
braceDepth: braceContexts.length,
|
|
451
|
+
frameDepth: frames.length,
|
|
452
|
+
},
|
|
453
|
+
parenDepth: 0,
|
|
454
|
+
bracketDepth: 0,
|
|
455
|
+
braceDepth: 0,
|
|
456
|
+
angleDepth: 0,
|
|
457
|
+
typeOperandExpected: true,
|
|
458
|
+
};
|
|
459
|
+
inType = true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
364
462
|
// An assertion must release its type context before the same runtime delimiter can retire
|
|
365
463
|
// an enclosing arrow. Consume the delimiter only after restoring the nearest function grammar.
|
|
366
464
|
let expressionFunctionContext = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
@@ -369,7 +467,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
369
467
|
frames[frames.length - 1]?.kind !== 'jsx-text' &&
|
|
370
468
|
frames[frames.length - 1]?.kind !== 'jsx-tag' &&
|
|
371
469
|
expressionFunctionBodyEnded(ch, parenDepth, bracketDepth, braceContexts.length, frames.length, functionContextStack.length, conditionalContexts.length, expressionFunctionContext)) {
|
|
372
|
-
expressionFunctionContexts.pop();
|
|
470
|
+
closeRequireScope(expressionFunctionContexts.pop()?.requireScope);
|
|
373
471
|
expressionFunctionContext = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
374
472
|
}
|
|
375
473
|
if (typeAnnotation?.role === 'return' &&
|
|
@@ -377,11 +475,15 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
377
475
|
atLexicalDepth(typeAnnotation.signature.owner) &&
|
|
378
476
|
atTypeRoot(typeAnnotation) &&
|
|
379
477
|
!typeAnnotation.typeOperandExpected &&
|
|
380
|
-
identifier !== undefined &&
|
|
381
|
-
|
|
478
|
+
((identifier !== undefined &&
|
|
479
|
+
(!TYPE_OPERAND_KEYWORDS.has(identifier.value) || identifier.value === 'const')) ||
|
|
480
|
+
(ch === '[' &&
|
|
481
|
+
typeAnnotation.signature.context.isClassMember === true &&
|
|
482
|
+
computedMethodFollows(source, i, n)))) {
|
|
382
483
|
const signature = typeAnnotation.signature;
|
|
383
|
-
if (pendingFunctionContexts[pendingFunctionContexts.length - 1] ===
|
|
384
|
-
|
|
484
|
+
if (pendingFunctionContexts[pendingFunctionContexts.length - 1]?.requireScope ===
|
|
485
|
+
signature.context.requireScope) {
|
|
486
|
+
closeRequireScope(pendingFunctionContexts.pop()?.requireScope);
|
|
385
487
|
}
|
|
386
488
|
if (classState !== undefined && signature.context.isClassMember === true) {
|
|
387
489
|
classState.member = undefined;
|
|
@@ -389,6 +491,18 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
389
491
|
typeAnnotation = undefined;
|
|
390
492
|
inType = false;
|
|
391
493
|
}
|
|
494
|
+
const pendingBodylessSignature = pendingFunctionContexts[pendingFunctionContexts.length - 1];
|
|
495
|
+
if (pendingBodylessSignature?.phase === 'awaiting-body' &&
|
|
496
|
+
atLexicalDepth(pendingBodylessSignature) &&
|
|
497
|
+
((typeAnnotation === undefined && tokenLineBreakBefore && ch !== '{' && ch !== ':') ||
|
|
498
|
+
(ch === '}' &&
|
|
499
|
+
(typeAnnotation === undefined ||
|
|
500
|
+
(typeAnnotation.role === 'return' && atTypeRoot(typeAnnotation)))))) {
|
|
501
|
+
closeRequireScope(pendingFunctionContexts.pop()?.requireScope);
|
|
502
|
+
typeAnnotation = undefined;
|
|
503
|
+
inType = false;
|
|
504
|
+
resetExpressionState();
|
|
505
|
+
}
|
|
392
506
|
if (classState !== undefined &&
|
|
393
507
|
atLexicalDepth(classState) &&
|
|
394
508
|
(typeAnnotation === undefined ||
|
|
@@ -426,7 +540,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
426
540
|
frames[frames.length - 1]?.kind !== 'jsx-text' &&
|
|
427
541
|
frames[frames.length - 1]?.kind !== 'jsx-tag' &&
|
|
428
542
|
lineTerminatorEndsExpressionFunctionBody(ch, prevKind, prevPunct, tokens[tokens.length - 1], identifier?.value, (ch === '+' || ch === '-') && source[i + 1] === ch, parenDepth, bracketDepth, braceContexts.length, frames.length, functionContextStack.length, context)) {
|
|
429
|
-
expressionFunctionContexts.pop();
|
|
543
|
+
closeRequireScope(expressionFunctionContexts.pop()?.requireScope);
|
|
430
544
|
context = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
431
545
|
}
|
|
432
546
|
lineTerminatorSinceLastToken = false;
|
|
@@ -556,7 +670,9 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
556
670
|
typeAnnotation = undefined;
|
|
557
671
|
inType = false;
|
|
558
672
|
lineTerminatorSinceLastToken = false;
|
|
559
|
-
if (!annotation.signature.isFunctionKeyword
|
|
673
|
+
if (!annotation.signature.isFunctionKeyword &&
|
|
674
|
+
pendingFunctionContexts[pendingFunctionContexts.length - 1]?.requireScope !==
|
|
675
|
+
annotation.signature.context.requireScope) {
|
|
560
676
|
pendingFunctionContexts.push({
|
|
561
677
|
...annotation.signature.context,
|
|
562
678
|
...annotation.signature.owner,
|
|
@@ -569,7 +685,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
569
685
|
atRoot &&
|
|
570
686
|
annotation.role === 'return' &&
|
|
571
687
|
annotation.signature.isArrowSignature &&
|
|
572
|
-
!functionTypeArrowContinues(source, i, n)) {
|
|
688
|
+
!functionTypeArrowContinues(source, i, n, conditional !== undefined && atLexicalDepth(conditional))) {
|
|
573
689
|
// The signature's own `=>`: everything after it is the body, not the return type.
|
|
574
690
|
typedArrowContext = annotation.signature.context;
|
|
575
691
|
typeAnnotation = undefined;
|
|
@@ -666,6 +782,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
666
782
|
closed?.role === 'function' ||
|
|
667
783
|
closed?.role === 'class';
|
|
668
784
|
if (closed?.role === 'function') {
|
|
785
|
+
closeRequireScope(closed.context.requireScope);
|
|
669
786
|
functionContextStack.pop();
|
|
670
787
|
}
|
|
671
788
|
if ((closed?.role === 'function' || closed?.role === 'static-block') &&
|
|
@@ -741,8 +858,17 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
741
858
|
!afterControlHead &&
|
|
742
859
|
!prevIsExpressionKeyword &&
|
|
743
860
|
canStartTypeAssertion(tokens[tokens.length - 1]);
|
|
861
|
+
// Capture the enclosing operand grammar before this identifier replaces it. Contextual
|
|
862
|
+
// await/yield, an arrow head and a fresh embedded expression all expect an expression.
|
|
863
|
+
const expressionPosition = functionIsExpressionPosition(prevKind, prevPunct, colonIsExpression, (prevKind === 'identifier' &&
|
|
864
|
+
prevIsExpressionKeyword &&
|
|
865
|
+
!BLOCK_AFTER_KEYWORD.has(previousIdentifier ?? '') &&
|
|
866
|
+
!((previousIdentifier === 'return' || previousIdentifier === 'yield') &&
|
|
867
|
+
tokenLineBreakBefore)) ||
|
|
868
|
+
(isPunct(tokens[tokens.length - 1], '>') && isArrowHead(tokens[tokens.length - 2])) ||
|
|
869
|
+
(prevKind === 'start' && (frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr')));
|
|
744
870
|
if (value === 'async') {
|
|
745
|
-
asyncFunctionIsExpression =
|
|
871
|
+
asyncFunctionIsExpression = expressionPosition;
|
|
746
872
|
if (isIdentifier(tokens[tokens.length - 1], 'default') &&
|
|
747
873
|
isIdentifier(tokens[tokens.length - 2], 'export')) {
|
|
748
874
|
asyncFunctionIsExpression = false;
|
|
@@ -750,8 +876,13 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
750
876
|
}
|
|
751
877
|
prevIdentifier = value;
|
|
752
878
|
const isMemberName = prevKind === 'punct' && prevPunct === '.';
|
|
879
|
+
const atRoot = braceContexts.length === 0 && parenDepth === 0 && bracketDepth === 0 && frames.length === 0;
|
|
880
|
+
let moduleSignal = detectModule &&
|
|
881
|
+
!isMemberName &&
|
|
882
|
+
((value === 'import' && importSignalsModule(source, i, atRoot)) ||
|
|
883
|
+
(value === 'export' && atRoot && exportSignalsModule(source, i)));
|
|
753
884
|
const classMemberName = value === 'class' &&
|
|
754
|
-
(
|
|
885
|
+
atMethodContainer() &&
|
|
755
886
|
memberDeclarationHead(tokens, tokens.length, insideClassBody(), currentClass()?.member?.start) !== undefined;
|
|
756
887
|
const nextSignificant = skipTrivia(source, i, n);
|
|
757
888
|
const classPropertyName = value === 'class' &&
|
|
@@ -769,10 +900,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
769
900
|
bracketDepth,
|
|
770
901
|
braceDepth: braceContexts.length,
|
|
771
902
|
frameDepth: frames.length,
|
|
772
|
-
isExpression: !isExportDefault &&
|
|
773
|
-
(previousIdentifier === 'extends' ||
|
|
774
|
-
functionIsExpressionPosition(prevKind, prevPunct, previousIdentifier, colonIsExpression) ||
|
|
775
|
-
(isPunct(tokens[tokens.length - 1], '>') && isArrowHead(tokens[tokens.length - 2]))),
|
|
903
|
+
isExpression: !isExportDefault && (previousIdentifier === 'extends' || expressionPosition),
|
|
776
904
|
});
|
|
777
905
|
}
|
|
778
906
|
// A keyword after `.` is a property name (e.g. `x.return`), not an expression start.
|
|
@@ -805,8 +933,19 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
805
933
|
const functionContext = parameterGrammarSuspended || fieldGrammarSuspended
|
|
806
934
|
? undefined
|
|
807
935
|
: (expressionContextIsCurrent ?? bodyContext);
|
|
936
|
+
const probe = detectModule &&
|
|
937
|
+
value === 'await' &&
|
|
938
|
+
!(prevKind === 'identifier' &&
|
|
939
|
+
['class', 'function', 'var', 'let', 'const', 'extends'].includes(previousIdentifier ?? '')) &&
|
|
940
|
+
functionContext === undefined &&
|
|
941
|
+
!parameterGrammarSuspended &&
|
|
942
|
+
!fieldGrammarSuspended
|
|
943
|
+
? probeAwaitOperand(source, i, parameterContexts[parameterContexts.length - 1]?.forPhase !== undefined)
|
|
944
|
+
: undefined;
|
|
945
|
+
moduleSignal ||= probe?.module === true;
|
|
808
946
|
if (value === 'await' &&
|
|
809
947
|
(functionContext?.async === true ||
|
|
948
|
+
probe?.keyword === true ||
|
|
810
949
|
(functionContext === undefined &&
|
|
811
950
|
module &&
|
|
812
951
|
!parameterGrammarSuspended &&
|
|
@@ -826,20 +965,24 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
826
965
|
// is async (`async function`, i.e. preceded by `async`) or a generator (`function*`, resolved
|
|
827
966
|
// when a following `*` is seen). The context is consumed at the body `{`.
|
|
828
967
|
const methodName = value === 'function' &&
|
|
829
|
-
(
|
|
968
|
+
atMethodContainer() &&
|
|
830
969
|
memberDeclarationHead(tokens, tokens.length, insideClassBody(), currentClass()?.member?.start) !== undefined;
|
|
831
970
|
if (value === 'function' && !isMemberName && !methodName && typeAnnotation === undefined) {
|
|
832
971
|
// `async function` is only async without a line terminator between the two words: a line
|
|
833
972
|
// break ends the `async` statement, so the declaration that follows is an ordinary one.
|
|
834
973
|
const isAsync = previousIdentifier === 'async' && prevKind === 'identifier' && !tokenLineBreakBefore;
|
|
835
974
|
pendingFunctionContexts.push({
|
|
975
|
+
requireScope: {
|
|
976
|
+
parent: requireScopes[requireScopes.length - 1],
|
|
977
|
+
bindings: [],
|
|
978
|
+
inParameters: true,
|
|
979
|
+
},
|
|
836
980
|
async: isAsync,
|
|
837
981
|
generator: false,
|
|
838
982
|
isExpression: isAsync
|
|
839
983
|
? asyncFunctionIsExpression
|
|
840
984
|
: !(isIdentifier(tokens[tokens.length - 1], 'default') &&
|
|
841
|
-
isIdentifier(tokens[tokens.length - 2], 'export')) &&
|
|
842
|
-
functionIsExpressionPosition(prevKind, prevPunct, previousIdentifier, colonIsExpression),
|
|
985
|
+
isIdentifier(tokens[tokens.length - 2], 'export')) && expressionPosition,
|
|
843
986
|
parenDepth,
|
|
844
987
|
bracketDepth,
|
|
845
988
|
braceDepth: braceContexts.length,
|
|
@@ -848,13 +991,42 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
848
991
|
phase: 'awaiting-parameters',
|
|
849
992
|
});
|
|
850
993
|
}
|
|
994
|
+
// A bare arrow parameter has no opening parenthesis to own its binding range.
|
|
995
|
+
const next = !inType && !isMemberName ? skipTrivia(source, i, n) : n;
|
|
996
|
+
const singleArrowContext = source[next] === '=' && source[next + 1] === '>'
|
|
997
|
+
? {
|
|
998
|
+
async: prevKind === 'identifier' &&
|
|
999
|
+
previousIdentifier === 'async' &&
|
|
1000
|
+
!tokenLineBreakBefore,
|
|
1001
|
+
generator: false,
|
|
1002
|
+
isExpression: false,
|
|
1003
|
+
requireScope: {
|
|
1004
|
+
parent: requireScopes[requireScopes.length - 1],
|
|
1005
|
+
bindings: [{ start: tokens.length, end: tokens.length + 1 }],
|
|
1006
|
+
inParameters: false,
|
|
1007
|
+
},
|
|
1008
|
+
}
|
|
1009
|
+
: undefined;
|
|
1010
|
+
if (singleArrowContext?.requireScope !== undefined) {
|
|
1011
|
+
requireScopes.push(singleArrowContext.requireScope);
|
|
1012
|
+
}
|
|
851
1013
|
emit({
|
|
852
1014
|
kind: 'identifier',
|
|
853
1015
|
value,
|
|
854
1016
|
offset: start,
|
|
1017
|
+
...(singleArrowContext === undefined ? {} : { signatureContext: singleArrowContext }),
|
|
855
1018
|
endOffset: i,
|
|
1019
|
+
...(value === 'require' && requireScopes.length > 0
|
|
1020
|
+
? { requireScope: requireScopes[requireScopes.length - 1] }
|
|
1021
|
+
: {}),
|
|
856
1022
|
...(isMemberName ? { propertyName: true } : {}),
|
|
1023
|
+
...(moduleSignal ? { moduleSignal: true } : {}),
|
|
857
1024
|
});
|
|
1025
|
+
// Once the mode is known, only the definitive module pass should validate the remainder.
|
|
1026
|
+
// Continuing this probe with script grammar could reject valid newline await operands.
|
|
1027
|
+
if (detectModule && moduleSignal) {
|
|
1028
|
+
return tokens;
|
|
1029
|
+
}
|
|
858
1030
|
if (startsAssertion) {
|
|
859
1031
|
typeAnnotation = {
|
|
860
1032
|
role: 'assertion',
|
|
@@ -940,6 +1112,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
940
1112
|
angleDepth: 0,
|
|
941
1113
|
typeOperandExpected: true,
|
|
942
1114
|
};
|
|
1115
|
+
parameter.bindingEnd ??= tokens.length;
|
|
943
1116
|
parameter.phase = 'type';
|
|
944
1117
|
inType = true;
|
|
945
1118
|
}
|
|
@@ -1042,7 +1215,17 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1042
1215
|
braceContexts.push({ role: 'static-block', owner });
|
|
1043
1216
|
}
|
|
1044
1217
|
else {
|
|
1045
|
-
braceContexts.push(
|
|
1218
|
+
braceContexts.push(isObject
|
|
1219
|
+
? {
|
|
1220
|
+
role: 'object',
|
|
1221
|
+
depth: {
|
|
1222
|
+
parenDepth,
|
|
1223
|
+
bracketDepth,
|
|
1224
|
+
braceDepth: braceContexts.length + 1,
|
|
1225
|
+
frameDepth: frames.length,
|
|
1226
|
+
},
|
|
1227
|
+
}
|
|
1228
|
+
: { role: 'block' });
|
|
1046
1229
|
}
|
|
1047
1230
|
// Count the brace inside the innermost open `${...}` or JSX `{...}` expression so its closing
|
|
1048
1231
|
// `}` is found.
|
|
@@ -1054,18 +1237,38 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1054
1237
|
// A `(` after a control keyword opens a control head; only its matching `)` may then be
|
|
1055
1238
|
// followed by a regex literal, so the nesting depth of every `(` has to be tracked.
|
|
1056
1239
|
const pending = pendingFunctionContexts[pendingFunctionContexts.length - 1];
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
const signature = signatureCandidate?.isArrowSignature === true && !arrowFollowsParametersSafely(source, i, n)
|
|
1240
|
+
const signatureCandidate = parameterSignatureContext(tokens, pending !== undefined && atLexicalDepth(pending) ? pending : undefined, atMethodContainer(), insideClassBody(), tokenLineBreakBefore, currentClass()?.member?.start);
|
|
1241
|
+
// A ternary's alternate may itself be an arrow. Its => is not proof that
|
|
1242
|
+
// the consequent's parentheses were parameters: a typed arrow must still
|
|
1243
|
+
// leave the enclosing conditional's colon after its own body.
|
|
1244
|
+
const needsConditionalEnd = conditional !== undefined && atLexicalDepth(conditional);
|
|
1245
|
+
const selectedSignature = signatureCandidate?.isArrowSignature === true &&
|
|
1246
|
+
!arrowFollowsParametersSafely(source, i, n, jsx, needsConditionalEnd)
|
|
1065
1247
|
? undefined
|
|
1066
1248
|
: signatureCandidate;
|
|
1249
|
+
const signature = selectedSignature === undefined
|
|
1250
|
+
? undefined
|
|
1251
|
+
: {
|
|
1252
|
+
...selectedSignature,
|
|
1253
|
+
context: selectedSignature.context.requireScope !== undefined
|
|
1254
|
+
? selectedSignature.context
|
|
1255
|
+
: {
|
|
1256
|
+
...selectedSignature.context,
|
|
1257
|
+
requireScope: {
|
|
1258
|
+
parent: requireScopes[requireScopes.length - 1],
|
|
1259
|
+
bindings: [],
|
|
1260
|
+
inParameters: true,
|
|
1261
|
+
},
|
|
1262
|
+
},
|
|
1263
|
+
};
|
|
1264
|
+
if (signature?.context.requireScope !== undefined) {
|
|
1265
|
+
requireScopes.push(signature.context.requireScope);
|
|
1266
|
+
}
|
|
1067
1267
|
parameterContexts.push({
|
|
1068
1268
|
openIndex: tokens.length,
|
|
1269
|
+
bindingStart: tokens.length + 1,
|
|
1270
|
+
bindingEnd: undefined,
|
|
1271
|
+
bindingCommas: [],
|
|
1069
1272
|
signature: signature === undefined
|
|
1070
1273
|
? undefined
|
|
1071
1274
|
: {
|
|
@@ -1111,8 +1314,25 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1111
1314
|
pending.phase = 'awaiting-body';
|
|
1112
1315
|
}
|
|
1113
1316
|
const parameters = parameterContexts.pop();
|
|
1317
|
+
if (parameters?.signature !== undefined) {
|
|
1318
|
+
recordRequireBinding(parameters, tokens.length);
|
|
1319
|
+
const scope = parameters.signature.context.requireScope;
|
|
1320
|
+
if (scope !== undefined) {
|
|
1321
|
+
scope.inParameters = false;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1114
1324
|
matchingOpen = parameters?.openIndex;
|
|
1115
1325
|
closedParameterSignature = parameters?.signature;
|
|
1326
|
+
if (closedParameterSignature !== undefined &&
|
|
1327
|
+
!closedParameterSignature.isFunctionKeyword &&
|
|
1328
|
+
!closedParameterSignature.isArrowSignature) {
|
|
1329
|
+
pendingFunctionContexts.push({
|
|
1330
|
+
...closedParameterSignature.context,
|
|
1331
|
+
...closedParameterSignature.owner,
|
|
1332
|
+
parameterDepth: undefined,
|
|
1333
|
+
phase: 'awaiting-body',
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1116
1336
|
afterControlHead = parameters?.control === true;
|
|
1117
1337
|
if (parenDepth > 0) {
|
|
1118
1338
|
parenDepth--;
|
|
@@ -1148,17 +1368,15 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1148
1368
|
const next = skipTrivia(source, i + 1, n);
|
|
1149
1369
|
if (context !== undefined) {
|
|
1150
1370
|
if (source[next] === '{') {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1371
|
+
pendingFunctionContexts.push({
|
|
1372
|
+
...context,
|
|
1373
|
+
parenDepth,
|
|
1374
|
+
bracketDepth,
|
|
1375
|
+
braceDepth: braceContexts.length,
|
|
1376
|
+
frameDepth: frames.length,
|
|
1377
|
+
parameterDepth: undefined,
|
|
1378
|
+
phase: 'awaiting-body',
|
|
1379
|
+
});
|
|
1162
1380
|
}
|
|
1163
1381
|
else {
|
|
1164
1382
|
const parameterOwner = nearestFunctionParameter(parameterContexts);
|
|
@@ -1187,9 +1405,23 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1187
1405
|
}
|
|
1188
1406
|
if (!inType && parameter !== undefined && atLexicalDepth(parameter.depth)) {
|
|
1189
1407
|
if (ch === ',') {
|
|
1408
|
+
const scope = parameter.signature?.context.requireScope;
|
|
1409
|
+
if (scope !== undefined) {
|
|
1410
|
+
parameter.bindingCommas.push({
|
|
1411
|
+
index: tokens.length,
|
|
1412
|
+
bindingStart: parameter.bindingStart,
|
|
1413
|
+
bindingEnd: parameter.bindingEnd,
|
|
1414
|
+
phase: parameter.phase,
|
|
1415
|
+
bindingCount: scope.bindings.length,
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
recordRequireBinding(parameter, tokens.length);
|
|
1419
|
+
parameter.bindingStart = tokens.length + 1;
|
|
1420
|
+
parameter.bindingEnd = undefined;
|
|
1190
1421
|
parameter.phase = 'binding';
|
|
1191
1422
|
}
|
|
1192
1423
|
else if (equalsOperator?.operator === 'assignment') {
|
|
1424
|
+
parameter.bindingEnd ??= tokens.length;
|
|
1193
1425
|
parameter.phase = 'initializer';
|
|
1194
1426
|
}
|
|
1195
1427
|
}
|
|
@@ -1205,9 +1437,10 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1205
1437
|
}
|
|
1206
1438
|
// A callback's statements cannot invalidate a signature outside its body or parameter list.
|
|
1207
1439
|
if (!inType && ch === ';' && pending !== undefined && atLexicalDepth(pending)) {
|
|
1208
|
-
pendingFunctionContexts.pop();
|
|
1440
|
+
closeRequireScope(pendingFunctionContexts.pop()?.requireScope);
|
|
1209
1441
|
}
|
|
1210
1442
|
const previousToken = tokens[tokens.length - 1];
|
|
1443
|
+
const openingSignature = ch === '(' ? parameterContexts[parameterContexts.length - 1]?.signature?.context : undefined;
|
|
1211
1444
|
emit({
|
|
1212
1445
|
kind: 'punct',
|
|
1213
1446
|
value: ch,
|
|
@@ -1221,12 +1454,37 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1221
1454
|
? { expressionEnd: true }
|
|
1222
1455
|
: {}),
|
|
1223
1456
|
...equalsOperator,
|
|
1457
|
+
...(openingSignature === undefined ? {} : { signatureContext: openingSignature }),
|
|
1224
1458
|
...(matchingOpen !== undefined ? { matchingOpen } : {}),
|
|
1225
1459
|
...(closedClassMemberBoundary || opensClassMemberBoundary || (ch === ';' && insideClassBody())
|
|
1226
1460
|
? { memberSeparator: true }
|
|
1227
1461
|
: {}),
|
|
1228
1462
|
...(closedExpressionBrace ? { expressionEnd: true } : {}),
|
|
1229
1463
|
});
|
|
1464
|
+
if (!inType &&
|
|
1465
|
+
ch === '>' &&
|
|
1466
|
+
requireScopes[requireScopes.length - 1]?.inParameters === true &&
|
|
1467
|
+
!isArrowHead(tokens[tokens.length - 2], i)) {
|
|
1468
|
+
const start = markParameterTypeArguments(tokens, tokens.length);
|
|
1469
|
+
const owner = parameterContexts[parameterContexts.length - 1];
|
|
1470
|
+
const scope = owner?.signature?.context.requireScope;
|
|
1471
|
+
if (start !== undefined && owner !== undefined && scope !== undefined) {
|
|
1472
|
+
// Commas were provisional until the closing > established a type-argument list.
|
|
1473
|
+
// Restore the initializer state as well as its binding ranges; do not re-lex source.
|
|
1474
|
+
let first = owner.bindingCommas.length;
|
|
1475
|
+
while (first > 0 && (owner.bindingCommas[first - 1]?.index ?? -1) >= start) {
|
|
1476
|
+
first--;
|
|
1477
|
+
}
|
|
1478
|
+
const checkpoint = owner.bindingCommas[first];
|
|
1479
|
+
if (checkpoint !== undefined) {
|
|
1480
|
+
owner.bindingStart = checkpoint.bindingStart;
|
|
1481
|
+
owner.bindingEnd = checkpoint.bindingEnd;
|
|
1482
|
+
owner.phase = checkpoint.phase;
|
|
1483
|
+
scope.bindings.length = checkpoint.bindingCount;
|
|
1484
|
+
owner.bindingCommas.length = first;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1230
1488
|
prevKind = 'punct';
|
|
1231
1489
|
prevPunct = ch;
|
|
1232
1490
|
i++;
|
|
@@ -1310,17 +1568,166 @@ function jsxTagIsGeneric(source, i, n) {
|
|
|
1310
1568
|
// because a template literal is a single argument value, so its internal parentheses never shape
|
|
1311
1569
|
// the parameter list. Regex-vs-division tracking mirrors the main tokenizer so a regex default
|
|
1312
1570
|
// (for example `= /a/)`) is skipped instead of read as division.
|
|
1313
|
-
function arrowFollowsParameters(source, open, n) {
|
|
1571
|
+
function arrowFollowsParameters(source, open, n, jsx = false, needsConditionalEnd = false, mode = 'parameters') {
|
|
1314
1572
|
let depth = 0;
|
|
1573
|
+
let bracketDepth = 0;
|
|
1574
|
+
let braceDepth = 0;
|
|
1575
|
+
let conditionalDepth = 0;
|
|
1576
|
+
let assertionOwner;
|
|
1315
1577
|
let prevKind = 'start';
|
|
1316
1578
|
let prevPunct;
|
|
1317
1579
|
let k = open;
|
|
1580
|
+
const jsxFrames = [];
|
|
1581
|
+
const typeArgumentBounds = new Map();
|
|
1318
1582
|
while (k < n) {
|
|
1319
|
-
|
|
1320
|
-
const
|
|
1583
|
+
let ch = source[k];
|
|
1584
|
+
const frame = jsxFrames[jsxFrames.length - 1];
|
|
1585
|
+
if (frame?.kind === 'jsx-text') {
|
|
1586
|
+
if (ch === '{') {
|
|
1587
|
+
jsxFrames.push({ kind: 'jsx-expr', depth: 0, offset: k });
|
|
1588
|
+
prevKind = 'start';
|
|
1589
|
+
prevPunct = undefined;
|
|
1590
|
+
k++;
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
if (ch === '<' && source[k + 1] === '/') {
|
|
1594
|
+
const end = source.indexOf('>', k + 2);
|
|
1595
|
+
if (end < 0) {
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1598
|
+
jsxFrames.pop();
|
|
1599
|
+
prevKind = 'template';
|
|
1600
|
+
prevPunct = undefined;
|
|
1601
|
+
k = end + 1;
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
// Quotes, parentheses, backticks and comment markers in children are inert.
|
|
1605
|
+
if (ch !== '<') {
|
|
1606
|
+
k++;
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
else if (frame?.kind === 'jsx-tag') {
|
|
1611
|
+
if (ch === '<' && skipTrivia(source, frame.nameEnd, n) === k) {
|
|
1612
|
+
k = readJsxTypeArguments(source, k, n, []);
|
|
1613
|
+
}
|
|
1614
|
+
else if (ch === '{') {
|
|
1615
|
+
jsxFrames.push({ kind: 'jsx-expr', depth: 0, offset: k });
|
|
1616
|
+
prevKind = 'start';
|
|
1617
|
+
prevPunct = undefined;
|
|
1618
|
+
k++;
|
|
1619
|
+
}
|
|
1620
|
+
else if (ch === '"' || ch === "'") {
|
|
1621
|
+
k = skipJsxAttributeValue(source, k, n);
|
|
1622
|
+
}
|
|
1623
|
+
else if (ch === '/' && source[k + 1] === '>') {
|
|
1624
|
+
jsxFrames.pop();
|
|
1625
|
+
prevKind = 'template';
|
|
1626
|
+
prevPunct = undefined;
|
|
1627
|
+
k += 2;
|
|
1628
|
+
}
|
|
1629
|
+
else if (ch === '>') {
|
|
1630
|
+
jsxFrames.pop();
|
|
1631
|
+
jsxFrames.push({ kind: 'jsx-text', name: frame.name, offset: frame.offset });
|
|
1632
|
+
k++;
|
|
1633
|
+
}
|
|
1634
|
+
else {
|
|
1635
|
+
k++;
|
|
1636
|
+
}
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
else {
|
|
1640
|
+
k = skipTrivia(source, k, n);
|
|
1641
|
+
ch = source[k];
|
|
1642
|
+
}
|
|
1321
1643
|
if (ch === undefined) {
|
|
1322
1644
|
break;
|
|
1323
1645
|
}
|
|
1646
|
+
if (jsx &&
|
|
1647
|
+
ch === '<' &&
|
|
1648
|
+
(frame?.kind === 'jsx-text' || isRegexStart(prevKind, prevPunct, false, false, false))) {
|
|
1649
|
+
if (source[k + 1] === '>') {
|
|
1650
|
+
jsxFrames.push({ kind: 'jsx-text', name: null, offset: k });
|
|
1651
|
+
k += 2;
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
if (isIdentifierStartAt(source, k + 1) &&
|
|
1655
|
+
(frame?.kind === 'jsx-text' || !jsxTagIsGeneric(source, k + 1, n))) {
|
|
1656
|
+
const tag = readJsxTagName(source, k + 1, n);
|
|
1657
|
+
jsxFrames.push({ kind: 'jsx-tag', name: tag.name, offset: k, nameEnd: tag.end });
|
|
1658
|
+
k = tag.end;
|
|
1659
|
+
continue;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
if (frame?.kind === 'jsx-text') {
|
|
1663
|
+
k++;
|
|
1664
|
+
continue;
|
|
1665
|
+
}
|
|
1666
|
+
if (frame?.kind === 'jsx-expr') {
|
|
1667
|
+
if (ch === '}') {
|
|
1668
|
+
if (frame.depth === 0) {
|
|
1669
|
+
jsxFrames.pop();
|
|
1670
|
+
prevKind = 'template';
|
|
1671
|
+
prevPunct = undefined;
|
|
1672
|
+
k++;
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
frame.depth--;
|
|
1676
|
+
}
|
|
1677
|
+
else if (ch === '{') {
|
|
1678
|
+
frame.depth++;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
if (mode === 'conditional-body' && ch === '<' && prevKind === 'identifier') {
|
|
1682
|
+
const inAssertion = assertionOwner?.paren === depth &&
|
|
1683
|
+
assertionOwner.bracket === bracketDepth &&
|
|
1684
|
+
assertionOwner.brace === braceDepth &&
|
|
1685
|
+
assertionOwner.frame === jsxFrames.length;
|
|
1686
|
+
const end = expressionTypeArgumentEnd(source, k, n, typeArgumentBounds, inAssertion);
|
|
1687
|
+
if (end !== undefined) {
|
|
1688
|
+
k = end;
|
|
1689
|
+
prevKind = 'identifier';
|
|
1690
|
+
prevPunct = undefined;
|
|
1691
|
+
continue;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
if (assertionOwner?.paren === depth &&
|
|
1695
|
+
assertionOwner.bracket === bracketDepth &&
|
|
1696
|
+
assertionOwner.brace === braceDepth &&
|
|
1697
|
+
assertionOwner.frame === jsxFrames.length &&
|
|
1698
|
+
['+', '-', '*', '/', '%', '=', '?', '!'].includes(ch)) {
|
|
1699
|
+
assertionOwner = undefined;
|
|
1700
|
+
}
|
|
1701
|
+
// Reuse the same opaque regions to confirm a typed nested arrow in a ternary.
|
|
1702
|
+
// Colons in JSX, object literals, nested delimiters or inner conditionals do not
|
|
1703
|
+
// close the enclosing conditional. Stop at this expression's own boundary.
|
|
1704
|
+
if (mode === 'conditional-body') {
|
|
1705
|
+
if (jsxFrames.length === 0 && depth === 0 && bracketDepth === 0 && braceDepth === 0) {
|
|
1706
|
+
if (ch === ':') {
|
|
1707
|
+
if (conditionalDepth === 0) {
|
|
1708
|
+
return true;
|
|
1709
|
+
}
|
|
1710
|
+
conditionalDepth--;
|
|
1711
|
+
}
|
|
1712
|
+
else if (ch === '?' &&
|
|
1713
|
+
source[k + 1] !== '?' &&
|
|
1714
|
+
source[k - 1] !== '?' &&
|
|
1715
|
+
source[k + 1] !== '.') {
|
|
1716
|
+
conditionalDepth++;
|
|
1717
|
+
}
|
|
1718
|
+
else if ([';', ',', ')', ']', '}'].includes(ch)) {
|
|
1719
|
+
return false;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
if (ch === '[')
|
|
1723
|
+
bracketDepth++;
|
|
1724
|
+
else if (ch === ']')
|
|
1725
|
+
bracketDepth--;
|
|
1726
|
+
else if (ch === '{')
|
|
1727
|
+
braceDepth++;
|
|
1728
|
+
else if (ch === '}')
|
|
1729
|
+
braceDepth--;
|
|
1730
|
+
}
|
|
1324
1731
|
// String literals: their brackets and slashes are inert text, so skip the whole value.
|
|
1325
1732
|
if (ch === "'" || ch === '"') {
|
|
1326
1733
|
k = readString(source, k).next;
|
|
@@ -1350,14 +1757,30 @@ function arrowFollowsParameters(source, open, n) {
|
|
|
1350
1757
|
depth--;
|
|
1351
1758
|
prevKind = 'punct';
|
|
1352
1759
|
prevPunct = ')';
|
|
1353
|
-
if (depth === 0) {
|
|
1760
|
+
if (depth === 0 && mode === 'parameters') {
|
|
1354
1761
|
const j = skipTrivia(source, k + 1, n);
|
|
1355
|
-
|
|
1356
|
-
|
|
1762
|
+
if (source[j] === '=' && source[j + 1] === '>') {
|
|
1763
|
+
return true;
|
|
1764
|
+
}
|
|
1765
|
+
const arrow = source[j] === ':' ? arrowAfterReturnType(source, j + 1, n) : undefined;
|
|
1766
|
+
return (arrow !== undefined &&
|
|
1767
|
+
(!needsConditionalEnd ||
|
|
1768
|
+
arrowFollowsParameters(source, arrow + 2, n, jsx, false, 'conditional-body')));
|
|
1357
1769
|
}
|
|
1358
1770
|
}
|
|
1359
1771
|
else if (isIdentifierStartAt(source, k)) {
|
|
1360
1772
|
const identifier = readIdentifier(source, k);
|
|
1773
|
+
if (mode === 'conditional-body' &&
|
|
1774
|
+
['as', 'satisfies'].includes(identifier.value) &&
|
|
1775
|
+
prevKind !== 'start' &&
|
|
1776
|
+
!(prevKind === 'punct' && prevPunct === '.')) {
|
|
1777
|
+
assertionOwner = {
|
|
1778
|
+
paren: depth,
|
|
1779
|
+
bracket: bracketDepth,
|
|
1780
|
+
brace: braceDepth,
|
|
1781
|
+
frame: jsxFrames.length,
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1361
1784
|
k = identifier.end - 1;
|
|
1362
1785
|
prevKind = 'identifier';
|
|
1363
1786
|
prevPunct = undefined;
|
|
@@ -1370,7 +1793,83 @@ function arrowFollowsParameters(source, open, n) {
|
|
|
1370
1793
|
}
|
|
1371
1794
|
return false;
|
|
1372
1795
|
}
|
|
1373
|
-
|
|
1796
|
+
/** Skip a balanced type-argument list only when its suffix can continue an expression. */
|
|
1797
|
+
function expressionTypeArgumentEnd(source, open, n, bounds, typeAssertion = false) {
|
|
1798
|
+
let end = bounds.get(open);
|
|
1799
|
+
if (end === undefined) {
|
|
1800
|
+
try {
|
|
1801
|
+
end = readJsxTypeArguments(source, open, n, [], true, bounds);
|
|
1802
|
+
}
|
|
1803
|
+
catch (error) {
|
|
1804
|
+
if (error instanceof ModuleSpecifierError) {
|
|
1805
|
+
bounds.set(open, null);
|
|
1806
|
+
return undefined;
|
|
1807
|
+
}
|
|
1808
|
+
throw error;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
if (end === null)
|
|
1812
|
+
return undefined;
|
|
1813
|
+
const next = skipTrivia(source, end, n);
|
|
1814
|
+
const ch = source[next];
|
|
1815
|
+
if (!typeAssertion)
|
|
1816
|
+
return ch === '(' ? end : undefined;
|
|
1817
|
+
if (isIdentifierStartAt(source, next) || isUnicodeEscapeStart(source, next)) {
|
|
1818
|
+
return ['as', 'satisfies', 'in', 'instanceof'].includes(readIdentifier(source, next).value)
|
|
1819
|
+
? end
|
|
1820
|
+
: undefined;
|
|
1821
|
+
}
|
|
1822
|
+
return ch !== undefined && (isNumberStart(ch) || ch === '"' || ch === "'") ? undefined : end;
|
|
1823
|
+
}
|
|
1824
|
+
/** A newline computed method head ends a complete bodyless member return annotation. */
|
|
1825
|
+
function computedMethodFollows(source, start, n) {
|
|
1826
|
+
let depth = 0;
|
|
1827
|
+
let index = start;
|
|
1828
|
+
let previous = 'start';
|
|
1829
|
+
let punct;
|
|
1830
|
+
while (index < n) {
|
|
1831
|
+
index = skipTrivia(source, index, n);
|
|
1832
|
+
const ch = source[index];
|
|
1833
|
+
if (ch === undefined) {
|
|
1834
|
+
break;
|
|
1835
|
+
}
|
|
1836
|
+
if (ch === "'" || ch === '"') {
|
|
1837
|
+
index = readString(source, index).next;
|
|
1838
|
+
previous = 'string';
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
if (ch === '`') {
|
|
1842
|
+
index = skipTemplateLiteral(source, index);
|
|
1843
|
+
previous = 'template';
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
if (ch === '/' && isRegexStart(previous, punct, false, false, false)) {
|
|
1847
|
+
index = skipRegex(source, index);
|
|
1848
|
+
previous = 'regex';
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
if (ch === '[') {
|
|
1852
|
+
depth++;
|
|
1853
|
+
}
|
|
1854
|
+
else if (ch === ']') {
|
|
1855
|
+
depth--;
|
|
1856
|
+
if (depth === 0) {
|
|
1857
|
+
return source[skipTrivia(source, index + 1, n)] === '(';
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
if (isIdentifierStartAt(source, index) || isUnicodeEscapeStart(source, index)) {
|
|
1861
|
+
index = readIdentifier(source, index).end;
|
|
1862
|
+
previous = 'identifier';
|
|
1863
|
+
}
|
|
1864
|
+
else {
|
|
1865
|
+
previous = isNumberStart(ch) ? 'number' : 'punct';
|
|
1866
|
+
punct = ch;
|
|
1867
|
+
index++;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
return false;
|
|
1871
|
+
}
|
|
1872
|
+
function arrowAfterReturnType(source, start, n) {
|
|
1374
1873
|
let parenDepth = 0;
|
|
1375
1874
|
let bracketDepth = 0;
|
|
1376
1875
|
let braceDepth = 0;
|
|
@@ -1380,7 +1879,7 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1380
1879
|
index = skipTrivia(source, index, n);
|
|
1381
1880
|
const ch = source[index];
|
|
1382
1881
|
if (ch === undefined) {
|
|
1383
|
-
return
|
|
1882
|
+
return undefined;
|
|
1384
1883
|
}
|
|
1385
1884
|
if (ch === "'" || ch === '"') {
|
|
1386
1885
|
index = readString(source, index).next;
|
|
@@ -1392,17 +1891,34 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1392
1891
|
}
|
|
1393
1892
|
if (ch === '=' && source[index + 1] === '>') {
|
|
1394
1893
|
if (parenDepth === 0 && bracketDepth === 0 && braceDepth === 0 && angleDepth === 0) {
|
|
1395
|
-
return
|
|
1894
|
+
return index;
|
|
1396
1895
|
}
|
|
1397
1896
|
index += 2;
|
|
1398
1897
|
continue;
|
|
1399
1898
|
}
|
|
1899
|
+
if (parenDepth === 0 &&
|
|
1900
|
+
bracketDepth === 0 &&
|
|
1901
|
+
braceDepth === 0 &&
|
|
1902
|
+
angleDepth === 0 &&
|
|
1903
|
+
(isIdentifierStartAt(source, index) || isUnicodeEscapeStart(source, index))) {
|
|
1904
|
+
const identifier = readIdentifier(source, index);
|
|
1905
|
+
const next = skipTrivia(source, identifier.end, n);
|
|
1906
|
+
// An operand followed by a call is runtime code (not a return type). In particular,
|
|
1907
|
+
// a conditional's `: async () =>` must not claim the preceding sequence expression.
|
|
1908
|
+
if (source[next] === '(' &&
|
|
1909
|
+
identifier.value !== 'import' &&
|
|
1910
|
+
!TYPE_OPERAND_KEYWORDS.has(identifier.value)) {
|
|
1911
|
+
return undefined;
|
|
1912
|
+
}
|
|
1913
|
+
index = identifier.end;
|
|
1914
|
+
continue;
|
|
1915
|
+
}
|
|
1400
1916
|
if (ch === '(') {
|
|
1401
1917
|
parenDepth++;
|
|
1402
1918
|
}
|
|
1403
1919
|
else if (ch === ')') {
|
|
1404
1920
|
if (parenDepth === 0) {
|
|
1405
|
-
return
|
|
1921
|
+
return undefined;
|
|
1406
1922
|
}
|
|
1407
1923
|
parenDepth--;
|
|
1408
1924
|
}
|
|
@@ -1411,7 +1927,7 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1411
1927
|
}
|
|
1412
1928
|
else if (ch === ']') {
|
|
1413
1929
|
if (bracketDepth === 0) {
|
|
1414
|
-
return
|
|
1930
|
+
return undefined;
|
|
1415
1931
|
}
|
|
1416
1932
|
bracketDepth--;
|
|
1417
1933
|
}
|
|
@@ -1420,7 +1936,7 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1420
1936
|
}
|
|
1421
1937
|
else if (ch === '}') {
|
|
1422
1938
|
if (braceDepth === 0) {
|
|
1423
|
-
return
|
|
1939
|
+
return undefined;
|
|
1424
1940
|
}
|
|
1425
1941
|
braceDepth--;
|
|
1426
1942
|
}
|
|
@@ -1429,7 +1945,7 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1429
1945
|
}
|
|
1430
1946
|
else if (ch === '>') {
|
|
1431
1947
|
if (angleDepth === 0) {
|
|
1432
|
-
return
|
|
1948
|
+
return undefined;
|
|
1433
1949
|
}
|
|
1434
1950
|
angleDepth--;
|
|
1435
1951
|
}
|
|
@@ -1438,15 +1954,15 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1438
1954
|
braceDepth === 0 &&
|
|
1439
1955
|
angleDepth === 0 &&
|
|
1440
1956
|
(ch === ';' || ch === ',')) {
|
|
1441
|
-
return
|
|
1957
|
+
return undefined;
|
|
1442
1958
|
}
|
|
1443
1959
|
index++;
|
|
1444
1960
|
}
|
|
1445
|
-
return
|
|
1961
|
+
return undefined;
|
|
1446
1962
|
}
|
|
1447
|
-
function arrowFollowsParametersSafely(source, open, n) {
|
|
1963
|
+
function arrowFollowsParametersSafely(source, open, n, jsx = false, needsConditionalEnd = false) {
|
|
1448
1964
|
try {
|
|
1449
|
-
return arrowFollowsParameters(source, open, n);
|
|
1965
|
+
return arrowFollowsParameters(source, open, n, jsx, needsConditionalEnd);
|
|
1450
1966
|
}
|
|
1451
1967
|
catch (error) {
|
|
1452
1968
|
if (error instanceof ModuleSpecifierError) {
|
|
@@ -1504,68 +2020,184 @@ function skipTemplateLiteral(source, start) {
|
|
|
1504
2020
|
}
|
|
1505
2021
|
throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', start);
|
|
1506
2022
|
}
|
|
2023
|
+
/** Probe template types with the same type boundaries as their interpolated operands. */
|
|
2024
|
+
function skipTemplateType(source, start, n, bounds) {
|
|
2025
|
+
let i = start + 1;
|
|
2026
|
+
while (i < n) {
|
|
2027
|
+
if (source[i] === '\\')
|
|
2028
|
+
i += 2;
|
|
2029
|
+
else if (source[i] === '`')
|
|
2030
|
+
return i + 1;
|
|
2031
|
+
else if (source[i] === '$' && source[i + 1] === '{') {
|
|
2032
|
+
i = readJsxTypeArguments(source, i + 1, n, [], true, bounds, '}');
|
|
2033
|
+
}
|
|
2034
|
+
else
|
|
2035
|
+
i++;
|
|
2036
|
+
}
|
|
2037
|
+
throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', start);
|
|
2038
|
+
}
|
|
2039
|
+
/** Only a complete type followed by a line break can begin another bodyless type member. */
|
|
2040
|
+
function typeMemberFollowsLineBreak(tokens, lineBreak) {
|
|
2041
|
+
const complete = (token) => {
|
|
2042
|
+
if (token === undefined)
|
|
2043
|
+
return false;
|
|
2044
|
+
if (token.kind === 'identifier')
|
|
2045
|
+
return !TYPE_OPERAND_KEYWORDS.has(token.value);
|
|
2046
|
+
return (token.kind === 'string' ||
|
|
2047
|
+
token.kind === 'number' ||
|
|
2048
|
+
token.kind === 'template' ||
|
|
2049
|
+
(token.kind === 'punct' &&
|
|
2050
|
+
([')', ']', '}', '>'].includes(token.value) || /^[0-9]$/.test(token.value))));
|
|
2051
|
+
};
|
|
2052
|
+
let index = tokens.length - 1;
|
|
2053
|
+
if (lineBreak && complete(tokens[index]))
|
|
2054
|
+
return true;
|
|
2055
|
+
// The line break may precede a modifier rather than the method name (e.g. get b()).
|
|
2056
|
+
while (index >= 0) {
|
|
2057
|
+
const token = tokens[index];
|
|
2058
|
+
if (token?.kind !== 'identifier' || !MEMBER_MODIFIERS.has(token.value))
|
|
2059
|
+
break;
|
|
2060
|
+
if (token.lineBreakBefore === true && complete(tokens[index - 1]))
|
|
2061
|
+
return true;
|
|
2062
|
+
index--;
|
|
2063
|
+
}
|
|
2064
|
+
return false;
|
|
2065
|
+
}
|
|
1507
2066
|
/**
|
|
1508
2067
|
* Lex only the boundaries of a component's type arguments. These tokens preserve literal imports
|
|
1509
2068
|
* without changing the surrounding JavaScript function/brace state or treating type braces as JSX.
|
|
1510
2069
|
*/
|
|
1511
|
-
function readJsxTypeArguments(source, open, n, tokens) {
|
|
1512
|
-
const closers = [
|
|
2070
|
+
function readJsxTypeArguments(source, open, n, tokens, expressionProbe = false, bounds, rootClose = '>') {
|
|
2071
|
+
const closers = [rootClose];
|
|
2072
|
+
const opens = [open];
|
|
2073
|
+
const conditions = [0];
|
|
1513
2074
|
let i = open + 1;
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
2075
|
+
try {
|
|
2076
|
+
while (i < n) {
|
|
2077
|
+
const trivia = readTrivia(source, i, n);
|
|
2078
|
+
i = trivia.end;
|
|
2079
|
+
const ch = source[i];
|
|
2080
|
+
if (ch === undefined) {
|
|
2081
|
+
break;
|
|
2082
|
+
}
|
|
2083
|
+
// A speculative expression probe must not borrow a > from a later statement
|
|
2084
|
+
// or conditional branch. Colons in conditional types still belong to the list.
|
|
2085
|
+
if (expressionProbe &&
|
|
2086
|
+
(closers[closers.length - 1] === '>' || (rootClose === '}' && closers.length === 1))) {
|
|
2087
|
+
const level = conditions.length - 1;
|
|
2088
|
+
if (ch === '?')
|
|
2089
|
+
conditions[level] = (conditions[level] ?? 0) + 1;
|
|
2090
|
+
else if (ch === ':' && (conditions[level] ?? 0) > 0)
|
|
2091
|
+
conditions[level] = (conditions[level] ?? 0) - 1;
|
|
2092
|
+
else if (ch === ';' ||
|
|
2093
|
+
ch === ':' ||
|
|
2094
|
+
(ch === '=' && source[i + 1] !== '>') ||
|
|
2095
|
+
([')', ']', '}'].includes(ch) && ch !== closers[closers.length - 1])) {
|
|
2096
|
+
throw new ModuleSpecifierError('Invalid type argument boundary', 'invalid-source', i);
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
if (expressionProbe && ch === '/') {
|
|
2100
|
+
throw new ModuleSpecifierError('Runtime slash in type argument probe', 'invalid-source', i);
|
|
2101
|
+
}
|
|
2102
|
+
if (ch === "'" || ch === '"') {
|
|
2103
|
+
const callee = tokens[tokens.length - 2];
|
|
2104
|
+
if (expressionProbe &&
|
|
2105
|
+
isPunct(tokens[tokens.length - 1], '(') &&
|
|
2106
|
+
callee?.kind === 'identifier' &&
|
|
2107
|
+
callee.value !== 'import' &&
|
|
2108
|
+
!TYPE_OPERAND_KEYWORDS.has(callee.value)) {
|
|
2109
|
+
throw new ModuleSpecifierError('Literal method parameter in type argument probe', 'invalid-source', i);
|
|
2110
|
+
}
|
|
2111
|
+
const { value, next } = readString(source, i);
|
|
2112
|
+
tokens.push({
|
|
2113
|
+
kind: 'string',
|
|
2114
|
+
value,
|
|
2115
|
+
offset: i,
|
|
2116
|
+
endOffset: next,
|
|
2117
|
+
lineBreakBefore: trivia.lineBreak,
|
|
2118
|
+
});
|
|
2119
|
+
i = next;
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
if (ch === '`') {
|
|
2123
|
+
if (expressionProbe) {
|
|
2124
|
+
const end = skipTemplateType(source, i, n, bounds);
|
|
2125
|
+
tokens.push({
|
|
2126
|
+
kind: 'template',
|
|
2127
|
+
offset: i,
|
|
2128
|
+
endOffset: end,
|
|
2129
|
+
lineBreakBefore: trivia.lineBreak,
|
|
2130
|
+
});
|
|
2131
|
+
i = end;
|
|
2132
|
+
continue;
|
|
2133
|
+
}
|
|
2134
|
+
throw new ModuleSpecifierError('Template literal types in JSX type arguments are not supported', 'invalid-source', i);
|
|
2135
|
+
}
|
|
2136
|
+
if (isIdentifierStartAt(source, i)) {
|
|
2137
|
+
const start = i;
|
|
2138
|
+
const identifier = readIdentifier(source, i);
|
|
2139
|
+
const next = skipTrivia(source, identifier.end, n);
|
|
2140
|
+
if (expressionProbe &&
|
|
2141
|
+
source[next] === '(' &&
|
|
2142
|
+
identifier.value !== 'import' &&
|
|
2143
|
+
!TYPE_OPERAND_KEYWORDS.has(identifier.value) &&
|
|
2144
|
+
!(closers[closers.length - 1] === '}' &&
|
|
2145
|
+
closers.length > 1 &&
|
|
2146
|
+
(memberDeclarationHead(tokens, tokens.length, false) !== undefined ||
|
|
2147
|
+
typeMemberFollowsLineBreak(tokens, trivia.lineBreak)))) {
|
|
2148
|
+
throw new ModuleSpecifierError('Runtime call in type argument probe', 'invalid-source', i);
|
|
2149
|
+
}
|
|
2150
|
+
i = identifier.end;
|
|
2151
|
+
tokens.push({
|
|
2152
|
+
kind: 'identifier',
|
|
2153
|
+
value: identifier.value,
|
|
2154
|
+
offset: start,
|
|
2155
|
+
endOffset: i,
|
|
2156
|
+
lineBreakBefore: trivia.lineBreak,
|
|
2157
|
+
});
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
if (expressionProbe && ch === '{' && isPunct(tokens[tokens.length - 1], ')')) {
|
|
2161
|
+
throw new ModuleSpecifierError('Runtime body in type argument probe', 'invalid-source', i);
|
|
2162
|
+
}
|
|
2163
|
+
if (ch === '<' || ch === '(' || ch === '{' || ch === '[') {
|
|
2164
|
+
closers.push(ch === '<' ? '>' : ch === '(' ? ')' : ch === '{' ? '}' : ']');
|
|
2165
|
+
opens.push(i);
|
|
2166
|
+
conditions.push(0);
|
|
2167
|
+
}
|
|
2168
|
+
else if (ch === closers[closers.length - 1] && !(ch === '>' && source[i - 1] === '=')) {
|
|
2169
|
+
closers.pop();
|
|
2170
|
+
const opened = opens.pop();
|
|
2171
|
+
conditions.pop();
|
|
2172
|
+
if (expressionProbe && ch === '>' && opened !== undefined)
|
|
2173
|
+
bounds?.set(opened, i + 1);
|
|
2174
|
+
if (closers.length === 0) {
|
|
2175
|
+
return i + 1;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
1523
2178
|
tokens.push({
|
|
1524
|
-
kind: '
|
|
1525
|
-
value,
|
|
2179
|
+
kind: 'punct',
|
|
2180
|
+
value: ch,
|
|
1526
2181
|
offset: i,
|
|
1527
|
-
endOffset:
|
|
1528
|
-
lineBreakBefore: trivia.lineBreak,
|
|
1529
|
-
});
|
|
1530
|
-
i = next;
|
|
1531
|
-
continue;
|
|
1532
|
-
}
|
|
1533
|
-
if (ch === '`') {
|
|
1534
|
-
throw new ModuleSpecifierError('Template literal types in JSX type arguments are not supported', 'invalid-source', i);
|
|
1535
|
-
}
|
|
1536
|
-
if (isIdentifierStartAt(source, i)) {
|
|
1537
|
-
const start = i;
|
|
1538
|
-
const identifier = readIdentifier(source, i);
|
|
1539
|
-
i = identifier.end;
|
|
1540
|
-
tokens.push({
|
|
1541
|
-
kind: 'identifier',
|
|
1542
|
-
value: identifier.value,
|
|
1543
|
-
offset: start,
|
|
1544
|
-
endOffset: i,
|
|
2182
|
+
endOffset: i + 1,
|
|
1545
2183
|
lineBreakBefore: trivia.lineBreak,
|
|
2184
|
+
...(ch === '=' ? classifyEquals(source, i, true) : {}),
|
|
1546
2185
|
});
|
|
1547
|
-
|
|
1548
|
-
}
|
|
1549
|
-
if (ch === '<' || ch === '(' || ch === '{' || ch === '[') {
|
|
1550
|
-
closers.push(ch === '<' ? '>' : ch === '(' ? ')' : ch === '{' ? '}' : ']');
|
|
2186
|
+
i++;
|
|
1551
2187
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
2188
|
+
throw new ModuleSpecifierError('Unterminated JSX type arguments', 'invalid-source', open);
|
|
2189
|
+
}
|
|
2190
|
+
catch (error) {
|
|
2191
|
+
if (expressionProbe && bounds !== undefined) {
|
|
2192
|
+
// One failed walk classifies every still-open angle encountered along the way.
|
|
2193
|
+
for (let level = 0; level < closers.length; level++) {
|
|
2194
|
+
const opened = opens[level];
|
|
2195
|
+
if (closers[level] === '>' && opened !== undefined)
|
|
2196
|
+
bounds.set(opened, null);
|
|
1556
2197
|
}
|
|
1557
2198
|
}
|
|
1558
|
-
|
|
1559
|
-
kind: 'punct',
|
|
1560
|
-
value: ch,
|
|
1561
|
-
offset: i,
|
|
1562
|
-
endOffset: i + 1,
|
|
1563
|
-
lineBreakBefore: trivia.lineBreak,
|
|
1564
|
-
...(ch === '=' ? classifyEquals(source, i, true) : {}),
|
|
1565
|
-
});
|
|
1566
|
-
i++;
|
|
2199
|
+
throw error;
|
|
1567
2200
|
}
|
|
1568
|
-
throw new ModuleSpecifierError('Unterminated JSX type arguments', 'invalid-source', open);
|
|
1569
2201
|
}
|
|
1570
2202
|
/**
|
|
1571
2203
|
* Whether the source between two offsets contains a line terminator.
|
|
@@ -1634,7 +2266,7 @@ const TYPE_OPERAND_KEYWORDS = new Set([
|
|
|
1634
2266
|
* (`(): (x: number) => void => {}`); once the return type is over, the next significant token is the
|
|
1635
2267
|
* body (`{`), a statement terminator or a delimiter that closes the surrounding code.
|
|
1636
2268
|
*/
|
|
1637
|
-
function functionTypeArrowContinues(source, arrowEnd, n) {
|
|
2269
|
+
function functionTypeArrowContinues(source, arrowEnd, n, conditionalEnd = false) {
|
|
1638
2270
|
let depth = 0;
|
|
1639
2271
|
for (let i = arrowEnd + 1; i < n; i++) {
|
|
1640
2272
|
const ch = source[i];
|
|
@@ -1651,7 +2283,7 @@ function functionTypeArrowContinues(source, arrowEnd, n) {
|
|
|
1651
2283
|
depth--;
|
|
1652
2284
|
}
|
|
1653
2285
|
else if (depth === 0) {
|
|
1654
|
-
if (ch === '{' || ch === ';' || ch === ',') {
|
|
2286
|
+
if (ch === '{' || ch === ';' || ch === ',' || (conditionalEnd && ch === ':')) {
|
|
1655
2287
|
return false;
|
|
1656
2288
|
}
|
|
1657
2289
|
if (ch === '=' && source[i + 1] === '>') {
|
|
@@ -1661,6 +2293,43 @@ function functionTypeArrowContinues(source, arrowEnd, n) {
|
|
|
1661
2293
|
}
|
|
1662
2294
|
return false;
|
|
1663
2295
|
}
|
|
2296
|
+
/** Preserve type-argument identity while inspecting a parameter binding pattern. */
|
|
2297
|
+
function markParameterTypeArguments(tokens, openIndex) {
|
|
2298
|
+
if (!isPunct(tokens[openIndex - 1], '>')) {
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
const start = signatureNameIndex(tokens, openIndex) + 1;
|
|
2302
|
+
if (!isPunct(tokens[start], '<')) {
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
// A real JS destructuring entry between comparison operators needs a default or property
|
|
2306
|
+
// separator. Do not mistake that binding for a generic argument. Nested type members may
|
|
2307
|
+
// contain their own separators inside balanced (), [] and {}.
|
|
2308
|
+
let depth = 0;
|
|
2309
|
+
for (let index = start + 1; index < openIndex - 1; index++) {
|
|
2310
|
+
const token = tokens[index];
|
|
2311
|
+
if (token?.kind !== 'punct') {
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
if (['(', '[', '{'].includes(token.value)) {
|
|
2315
|
+
depth++;
|
|
2316
|
+
}
|
|
2317
|
+
else if ([')', ']', '}'].includes(token.value)) {
|
|
2318
|
+
depth--;
|
|
2319
|
+
}
|
|
2320
|
+
else if (depth === 0 &&
|
|
2321
|
+
(token.operator === 'assignment' || token.value === ':' || token.value === ';')) {
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
for (let index = start; index < openIndex; index++) {
|
|
2326
|
+
const token = tokens[index];
|
|
2327
|
+
if (token !== undefined) {
|
|
2328
|
+
tokens[index] = { ...token, parameterType: true };
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
return start;
|
|
2332
|
+
}
|
|
1664
2333
|
/**
|
|
1665
2334
|
* Index of the token that names a `(` parameter list. A generic signature keeps its type-parameter
|
|
1666
2335
|
* list between the name and the `(` (`async function f<T>(): T {}`, `async <T,>(value: T) => {}`,
|
|
@@ -2013,11 +2682,22 @@ function inferArrowContext(tokens, arrowIndex) {
|
|
|
2013
2682
|
const async = markerToken?.kind === 'identifier' &&
|
|
2014
2683
|
markerToken.value === 'async' &&
|
|
2015
2684
|
asyncModifierIsContinuous(tokens, markerIndex, parametersIndex);
|
|
2016
|
-
return {
|
|
2685
|
+
return (parametersStart.signatureContext ?? {
|
|
2017
2686
|
async,
|
|
2018
2687
|
generator: false,
|
|
2019
2688
|
isExpression: false,
|
|
2020
|
-
};
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
/** Parameter lists can start at expression/statement boundaries, never after a call's name. */
|
|
2692
|
+
function arrowParameterPrefix(token) {
|
|
2693
|
+
if (token === undefined) {
|
|
2694
|
+
return true;
|
|
2695
|
+
}
|
|
2696
|
+
if (token.kind === 'punct') {
|
|
2697
|
+
return (EXPRESSION_BEFORE_BRACE.has(token.value) || [':', '>', ';', '{', '/'].includes(token.value));
|
|
2698
|
+
}
|
|
2699
|
+
return (token.kind === 'identifier' &&
|
|
2700
|
+
['return', 'throw', 'yield', 'await', 'void', 'typeof', 'delete', 'default'].includes(token.value));
|
|
2021
2701
|
}
|
|
2022
2702
|
function parameterSignatureContext(tokens, pending, insideMethodContainer, insideClassBody, lineBreakBefore, memberStart) {
|
|
2023
2703
|
const openIndex = tokens.length;
|
|
@@ -2043,12 +2723,7 @@ function parameterSignatureContext(tokens, pending, insideMethodContainer, insid
|
|
|
2043
2723
|
}
|
|
2044
2724
|
const marker = tokens[nameIndex - 1];
|
|
2045
2725
|
// A generic signature has no name of its own before `(` when it is anonymous (`async <T,>(v: T) => {}`).
|
|
2046
|
-
const startsArrowParameters =
|
|
2047
|
-
(isIdentifier(name, 'async') &&
|
|
2048
|
-
(openIndex === 1 ||
|
|
2049
|
-
(marker?.kind === 'punct' && ['=', '(', '[', ',', ':'].includes(marker.value)) ||
|
|
2050
|
-
isIdentifier(marker, 'return'))) ||
|
|
2051
|
-
(name?.kind === 'punct' && ['=', '(', '[', ',', ':'].includes(name.value));
|
|
2726
|
+
const startsArrowParameters = arrowParameterPrefix(name) || (isIdentifier(name, 'async') && arrowParameterPrefix(marker));
|
|
2052
2727
|
if (startsArrowParameters) {
|
|
2053
2728
|
const asyncToken = name?.kind === 'identifier' && name.value === 'async' ? name : undefined;
|
|
2054
2729
|
const async = asyncToken !== undefined &&
|
|
@@ -2421,13 +3096,13 @@ const BLOCK_AFTER_KEYWORD = new Set(['else', 'do']);
|
|
|
2421
3096
|
// so a slash after its body `{}` divides) rather than statement position (a declaration, so a slash
|
|
2422
3097
|
// after its body may start a regex). The previous significant token must expect an expression on
|
|
2423
3098
|
// its right: an expression-starting punctuation, an expression keyword, or a regex.
|
|
2424
|
-
function functionIsExpressionPosition(prevKind, prevPunct,
|
|
3099
|
+
function functionIsExpressionPosition(prevKind, prevPunct, colonIsExpression, expressionExpected) {
|
|
3100
|
+
if (expressionExpected) {
|
|
3101
|
+
return true;
|
|
3102
|
+
}
|
|
2425
3103
|
if (prevKind === 'punct') {
|
|
2426
3104
|
return prevPunct === ':' ? colonIsExpression : EXPRESSION_BEFORE_BRACE.has(prevPunct ?? '');
|
|
2427
3105
|
}
|
|
2428
|
-
if (prevKind === 'identifier') {
|
|
2429
|
-
return REGEX_PRECEDING_KEYWORDS.has(previousIdentifier ?? '');
|
|
2430
|
-
}
|
|
2431
3106
|
return prevKind === 'regex';
|
|
2432
3107
|
}
|
|
2433
3108
|
/**
|
|
@@ -2500,6 +3175,29 @@ function isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword, afterControl
|
|
|
2500
3175
|
function interpret(tokens) {
|
|
2501
3176
|
const occurrences = [];
|
|
2502
3177
|
const n = tokens.length;
|
|
3178
|
+
let closingDelimiters;
|
|
3179
|
+
const closingDelimiter = (openIndex) => {
|
|
3180
|
+
closingDelimiters ??= indexClosingDelimiters(tokens);
|
|
3181
|
+
return closingDelimiters.get(openIndex);
|
|
3182
|
+
};
|
|
3183
|
+
const resolvedRequireScopes = new Map();
|
|
3184
|
+
const requireIsShadowed = (scope) => {
|
|
3185
|
+
const path = [];
|
|
3186
|
+
let current = scope;
|
|
3187
|
+
while (current !== undefined && !resolvedRequireScopes.has(current)) {
|
|
3188
|
+
path.push(current);
|
|
3189
|
+
current = current.parent;
|
|
3190
|
+
}
|
|
3191
|
+
let shadowed = current === undefined ? false : (resolvedRequireScopes.get(current) ?? false);
|
|
3192
|
+
for (let index = path.length - 1; index >= 0; index--) {
|
|
3193
|
+
const owner = path[index];
|
|
3194
|
+
if (owner !== undefined) {
|
|
3195
|
+
shadowed ||= owner.bindings.some((range) => bindingHasRequire(tokens, range.start, range.end, closingDelimiter));
|
|
3196
|
+
resolvedRequireScopes.set(owner, shadowed);
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
return shadowed;
|
|
3200
|
+
};
|
|
2503
3201
|
let i = 0;
|
|
2504
3202
|
while (i < n) {
|
|
2505
3203
|
const token = tokens[i];
|
|
@@ -2508,7 +3206,7 @@ function interpret(tokens) {
|
|
|
2508
3206
|
continue;
|
|
2509
3207
|
}
|
|
2510
3208
|
if (token.value === 'import') {
|
|
2511
|
-
i = handleImport(tokens, i, occurrences);
|
|
3209
|
+
i = handleImport(tokens, i, occurrences, closingDelimiter);
|
|
2512
3210
|
continue;
|
|
2513
3211
|
}
|
|
2514
3212
|
if (token.value === 'export') {
|
|
@@ -2516,6 +3214,10 @@ function interpret(tokens) {
|
|
|
2516
3214
|
continue;
|
|
2517
3215
|
}
|
|
2518
3216
|
if (token.value === 'require') {
|
|
3217
|
+
if (token.requireScope !== undefined && requireIsShadowed(token.requireScope)) {
|
|
3218
|
+
i++;
|
|
3219
|
+
continue;
|
|
3220
|
+
}
|
|
2519
3221
|
i = handleRequire(tokens, i, occurrences);
|
|
2520
3222
|
continue;
|
|
2521
3223
|
}
|
|
@@ -2529,7 +3231,7 @@ function interpret(tokens) {
|
|
|
2529
3231
|
* - `obj.import(...)` is ignored (a member call, preceded by `.`).
|
|
2530
3232
|
* - `import.meta` is ignored (treated as `import` followed by `.`).
|
|
2531
3233
|
* - `import 'pkg'` and `import ... from 'pkg'` are `import`.
|
|
2532
|
-
* -
|
|
3234
|
+
* - Dynamic import accepts a literal source with optional bounded object-literal options.
|
|
2533
3235
|
*/
|
|
2534
3236
|
/**
|
|
2535
3237
|
* Whether the identifier at `i` is a property name (`obj.require`), which is never the global
|
|
@@ -2551,7 +3253,7 @@ function isMemberName(tokens, i) {
|
|
|
2551
3253
|
third.value === '.';
|
|
2552
3254
|
return !isSpread;
|
|
2553
3255
|
}
|
|
2554
|
-
function handleImport(tokens, i, occurrences) {
|
|
3256
|
+
function handleImport(tokens, i, occurrences, closingDelimiter) {
|
|
2555
3257
|
const next = tokens[i + 1];
|
|
2556
3258
|
// `obj.import(...)`: a member call, not the import keyword.
|
|
2557
3259
|
if (isMemberName(tokens, i)) {
|
|
@@ -2563,82 +3265,125 @@ function handleImport(tokens, i, occurrences) {
|
|
|
2563
3265
|
}
|
|
2564
3266
|
// `import 'pkg'`
|
|
2565
3267
|
if (next?.kind === 'string') {
|
|
2566
|
-
occurrences.push({ specifier: next.value, kind: 'import' });
|
|
3268
|
+
occurrences.push({ specifier: next.value, kind: 'import', offset: next.offset });
|
|
2567
3269
|
return i + 2;
|
|
2568
3270
|
}
|
|
2569
|
-
//
|
|
3271
|
+
// Keep literal specifiers with sole-argument or bounded object-literal options forms.
|
|
2570
3272
|
if (next?.kind === 'punct' && next.value === '(') {
|
|
2571
|
-
const specifier =
|
|
3273
|
+
const specifier = dynamicImportSpecifier(tokens, i + 2, closingDelimiter);
|
|
2572
3274
|
if (specifier !== undefined) {
|
|
2573
|
-
occurrences.push({
|
|
3275
|
+
occurrences.push({
|
|
3276
|
+
specifier: specifier.value,
|
|
3277
|
+
kind: 'dynamic-import',
|
|
3278
|
+
offset: specifier.offset,
|
|
3279
|
+
});
|
|
2574
3280
|
return i + 3;
|
|
2575
3281
|
}
|
|
2576
3282
|
return i + 1;
|
|
2577
3283
|
}
|
|
2578
|
-
// Static `import ... from 'pkg'`:
|
|
3284
|
+
// Static `import ... from 'pkg'`: consume only the supported clause.
|
|
2579
3285
|
return scanImportSource(tokens, i, occurrences, 'import');
|
|
2580
3286
|
}
|
|
2581
3287
|
/**
|
|
2582
3288
|
* Handle an `export` identifier. `export ... from 'pkg'` is `export-from`.
|
|
2583
3289
|
*/
|
|
2584
3290
|
function handleExport(tokens, i, occurrences) {
|
|
3291
|
+
// Member names share the same boundary rule as import and require.
|
|
3292
|
+
if (isMemberName(tokens, i)) {
|
|
3293
|
+
return i + 1;
|
|
3294
|
+
}
|
|
2585
3295
|
return scanImportSource(tokens, i, occurrences, 'export-from');
|
|
2586
3296
|
}
|
|
3297
|
+
/** Read only the supported clause; unrelated statements cannot supply its source. */
|
|
3298
|
+
function scanImportSource(tokens, i, occurrences, kind) {
|
|
3299
|
+
const sourceIndex = kind === 'export-from' ? exportSourceIndex(tokens, i) : importSourceIndex(tokens, i);
|
|
3300
|
+
const source = sourceIndex === undefined ? undefined : tokens[sourceIndex];
|
|
3301
|
+
if (source?.kind === 'string' && sourceIndex !== undefined) {
|
|
3302
|
+
occurrences.push({ specifier: source.value, kind, offset: source.offset });
|
|
3303
|
+
return sourceIndex + 1;
|
|
3304
|
+
}
|
|
3305
|
+
return i + 1;
|
|
3306
|
+
}
|
|
2587
3307
|
/**
|
|
2588
|
-
*
|
|
2589
|
-
*
|
|
2590
|
-
*
|
|
2591
|
-
* current statement (e.g. `import { export as v } from 'pkg'`).
|
|
3308
|
+
* Consume a default binding and/or a namespace/named clause, then require `from` + literal.
|
|
3309
|
+
* Each property lookalike fails locally. Named clauses accept only specifiers, never arbitrary
|
|
3310
|
+
* brace contents, so failed candidates cannot repeatedly search an unrelated suffix.
|
|
2592
3311
|
*/
|
|
2593
|
-
function
|
|
2594
|
-
|
|
2595
|
-
if (
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
3312
|
+
function importSourceIndex(tokens, importIndex) {
|
|
3313
|
+
let index = importIndex + 1;
|
|
3314
|
+
if (isIdentifier(tokens[index], 'type') &&
|
|
3315
|
+
(isPunct(tokens[index + 1], '{') ||
|
|
3316
|
+
isPunct(tokens[index + 1], '*') ||
|
|
3317
|
+
(tokens[index + 1]?.kind === 'identifier' &&
|
|
3318
|
+
(!isIdentifier(tokens[index + 1], 'from') || tokens[index + 2]?.kind !== 'string') &&
|
|
3319
|
+
(!isIdentifier(tokens[index + 1], 'as') || isIdentifier(tokens[index + 2], 'from'))))) {
|
|
3320
|
+
index++;
|
|
3321
|
+
}
|
|
3322
|
+
if (tokens[index]?.kind === 'identifier') {
|
|
3323
|
+
index++;
|
|
3324
|
+
// Preserve the historical synthetic default-alias form supported by this scanner.
|
|
3325
|
+
if (isIdentifier(tokens[index], 'as') && tokens[index + 1]?.kind === 'identifier') {
|
|
3326
|
+
index += 2;
|
|
2599
3327
|
}
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
3328
|
+
if (isPunct(tokens[index], ',')) {
|
|
3329
|
+
index++;
|
|
3330
|
+
}
|
|
3331
|
+
else {
|
|
3332
|
+
return importFromSourceIndex(tokens, index);
|
|
2604
3333
|
}
|
|
2605
|
-
return i + 1;
|
|
2606
3334
|
}
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
3335
|
+
if (isPunct(tokens[index], '*')) {
|
|
3336
|
+
if (!isIdentifier(tokens[index + 1], 'as') || tokens[index + 2]?.kind !== 'identifier') {
|
|
3337
|
+
return undefined;
|
|
3338
|
+
}
|
|
3339
|
+
index += 3;
|
|
3340
|
+
}
|
|
3341
|
+
else if (isPunct(tokens[index], '{')) {
|
|
3342
|
+
index++;
|
|
3343
|
+
while (!isPunct(tokens[index], '}')) {
|
|
3344
|
+
// `type as T` imports the value `type`, but `type as` imports the type `as`.
|
|
3345
|
+
// A second `as` plus an alias distinguishes `type as as Alias` from `type as as`.
|
|
3346
|
+
if (isIdentifier(tokens[index], 'type') &&
|
|
3347
|
+
tokens[index + 1]?.kind === 'identifier' &&
|
|
3348
|
+
(!isIdentifier(tokens[index + 1], 'as') ||
|
|
3349
|
+
isPunct(tokens[index + 2], ',') ||
|
|
3350
|
+
isPunct(tokens[index + 2], '}') ||
|
|
3351
|
+
(isIdentifier(tokens[index + 2], 'as') && tokens[index + 3]?.kind === 'identifier'))) {
|
|
3352
|
+
index++;
|
|
3353
|
+
}
|
|
3354
|
+
const imported = tokens[index];
|
|
3355
|
+
if (imported?.kind !== 'identifier' && imported?.kind !== 'string') {
|
|
3356
|
+
return undefined;
|
|
2617
3357
|
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
3358
|
+
index++;
|
|
3359
|
+
if (isIdentifier(tokens[index], 'as')) {
|
|
3360
|
+
if (tokens[index + 1]?.kind !== 'identifier') {
|
|
3361
|
+
return undefined;
|
|
3362
|
+
}
|
|
3363
|
+
index += 2;
|
|
2621
3364
|
}
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
const next = tokens[j + 1];
|
|
2625
|
-
// `import`/`export` only start a new construct outside the named clause. A `require`
|
|
2626
|
-
// identifier only starts one when it begins a direct call: a binding named `require` (e.g.
|
|
2627
|
-
// `import { require } from 'pkg'`) must not stop the search for the source that follows
|
|
2628
|
-
// `from`.
|
|
2629
|
-
if ((braceDepth === 0 && (token.value === 'import' || token.value === 'export')) ||
|
|
2630
|
-
(token.value === 'require' && next?.kind === 'punct' && next.value === '(')) {
|
|
2631
|
-
break;
|
|
3365
|
+
else if (imported.kind === 'string') {
|
|
3366
|
+
return undefined;
|
|
2632
3367
|
}
|
|
2633
|
-
if (
|
|
2634
|
-
|
|
2635
|
-
return j + 2;
|
|
3368
|
+
if (!isPunct(tokens[index], ',')) {
|
|
3369
|
+
break;
|
|
2636
3370
|
}
|
|
3371
|
+
index++;
|
|
2637
3372
|
}
|
|
2638
|
-
|
|
3373
|
+
if (!isPunct(tokens[index], '}')) {
|
|
3374
|
+
return undefined;
|
|
3375
|
+
}
|
|
3376
|
+
index++;
|
|
2639
3377
|
}
|
|
2640
|
-
|
|
2641
|
-
|
|
3378
|
+
else {
|
|
3379
|
+
return undefined;
|
|
3380
|
+
}
|
|
3381
|
+
return importFromSourceIndex(tokens, index);
|
|
3382
|
+
}
|
|
3383
|
+
function importFromSourceIndex(tokens, index) {
|
|
3384
|
+
return isIdentifier(tokens[index], 'from') && tokens[index + 1]?.kind === 'string'
|
|
3385
|
+
? index + 1
|
|
3386
|
+
: undefined;
|
|
2642
3387
|
}
|
|
2643
3388
|
function exportSourceIndex(tokens, exportIndex) {
|
|
2644
3389
|
let index = exportIndex + 1;
|
|
@@ -2684,32 +3429,177 @@ function handleRequire(tokens, i, occurrences) {
|
|
|
2684
3429
|
return i + 1;
|
|
2685
3430
|
}
|
|
2686
3431
|
const next = tokens[i + 1];
|
|
2687
|
-
//
|
|
3432
|
+
// A sole string argument, with an optional trailing comma, is static; options are not supported.
|
|
2688
3433
|
if (next?.kind === 'punct' && next.value === '(') {
|
|
2689
3434
|
const specifier = soleStringSpecifier(tokens, i + 2);
|
|
2690
3435
|
if (specifier !== undefined) {
|
|
2691
|
-
occurrences.push({ specifier, kind: 'require' });
|
|
3436
|
+
occurrences.push({ specifier: specifier.value, kind: 'require', offset: specifier.offset });
|
|
2692
3437
|
return i + 3;
|
|
2693
3438
|
}
|
|
2694
3439
|
}
|
|
2695
3440
|
return i + 1;
|
|
2696
3441
|
}
|
|
3442
|
+
/** Inspect binding targets only, skipping property keys, types and initializer expressions. */
|
|
3443
|
+
function bindingHasRequire(tokens, start, end, closingDelimiter) {
|
|
3444
|
+
const targets = [{ start, end }];
|
|
3445
|
+
while (targets.length > 0) {
|
|
3446
|
+
const target = targets.pop();
|
|
3447
|
+
if (target === undefined) {
|
|
3448
|
+
break;
|
|
3449
|
+
}
|
|
3450
|
+
let index = bindingAfterRest(tokens, target.start);
|
|
3451
|
+
// Constructor parameter properties prefix the binding, rather than naming it.
|
|
3452
|
+
let modifier = tokens[index];
|
|
3453
|
+
while (index + 1 < target.end &&
|
|
3454
|
+
modifier?.kind === 'identifier' &&
|
|
3455
|
+
['public', 'private', 'protected', 'readonly', 'override'].includes(modifier.value)) {
|
|
3456
|
+
index++;
|
|
3457
|
+
modifier = tokens[index];
|
|
3458
|
+
}
|
|
3459
|
+
const token = tokens[index];
|
|
3460
|
+
if (token?.kind === 'identifier') {
|
|
3461
|
+
const next = tokens[index + 1];
|
|
3462
|
+
if (token.value === 'require' &&
|
|
3463
|
+
(index + 1 >= target.end ||
|
|
3464
|
+
isPunct(next, ',') ||
|
|
3465
|
+
isPunct(next, '?') ||
|
|
3466
|
+
(next?.kind === 'punct' && next.operator === 'assignment'))) {
|
|
3467
|
+
return true;
|
|
3468
|
+
}
|
|
3469
|
+
continue;
|
|
3470
|
+
}
|
|
3471
|
+
const object = isPunct(token, '{');
|
|
3472
|
+
if (!object && !isPunct(token, '[')) {
|
|
3473
|
+
continue;
|
|
3474
|
+
}
|
|
3475
|
+
const close = closingDelimiter(index);
|
|
3476
|
+
if (close === undefined || close >= target.end) {
|
|
3477
|
+
continue;
|
|
3478
|
+
}
|
|
3479
|
+
const frame = token?.parameterFrame;
|
|
3480
|
+
index++;
|
|
3481
|
+
while (index < close) {
|
|
3482
|
+
if (isPunct(tokens[index], ',')) {
|
|
3483
|
+
index++;
|
|
3484
|
+
continue;
|
|
3485
|
+
}
|
|
3486
|
+
index = bindingAfterRest(tokens, index);
|
|
3487
|
+
let binding = index;
|
|
3488
|
+
if (object) {
|
|
3489
|
+
const keyClose = isPunct(tokens[index], '[') ? closingDelimiter(index) : index;
|
|
3490
|
+
if (keyClose === undefined) {
|
|
3491
|
+
break;
|
|
3492
|
+
}
|
|
3493
|
+
if (isPunct(tokens[keyClose + 1], ':')) {
|
|
3494
|
+
binding = keyClose + 2;
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
targets.push({ start: binding, end: close });
|
|
3498
|
+
index = bindingElementEnd(tokens, binding, close, frame, closingDelimiter);
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
return false;
|
|
3502
|
+
}
|
|
3503
|
+
function bindingAfterRest(tokens, index) {
|
|
3504
|
+
return isPunct(tokens[index], '.') &&
|
|
3505
|
+
isPunct(tokens[index + 1], '.') &&
|
|
3506
|
+
isPunct(tokens[index + 2], '.')
|
|
3507
|
+
? index + 3
|
|
3508
|
+
: index;
|
|
3509
|
+
}
|
|
3510
|
+
function bindingElementEnd(tokens, start, end, frame, closingDelimiter) {
|
|
3511
|
+
let index = start;
|
|
3512
|
+
while (index < end) {
|
|
3513
|
+
const token = tokens[index];
|
|
3514
|
+
if (isPunct(token, ',') && token?.parameterFrame === frame && token?.parameterType !== true) {
|
|
3515
|
+
return index + 1;
|
|
3516
|
+
}
|
|
3517
|
+
if (isPunct(token, '(') || isPunct(token, '[') || isPunct(token, '{')) {
|
|
3518
|
+
const close = closingDelimiter(index);
|
|
3519
|
+
if (close !== undefined && close < end) {
|
|
3520
|
+
index = close + 1;
|
|
3521
|
+
continue;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
index++;
|
|
3525
|
+
}
|
|
3526
|
+
return end;
|
|
3527
|
+
}
|
|
3528
|
+
/** Index balanced delimiters once, lazily, so nested option calls do not rescan suffixes. */
|
|
3529
|
+
function indexClosingDelimiters(tokens) {
|
|
3530
|
+
const closing = new Map();
|
|
3531
|
+
const stack = [];
|
|
3532
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
3533
|
+
const token = tokens[index];
|
|
3534
|
+
if (token?.kind !== 'punct') {
|
|
3535
|
+
continue;
|
|
3536
|
+
}
|
|
3537
|
+
if (token.value === '(' || token.value === '[' || token.value === '{') {
|
|
3538
|
+
stack.push(index);
|
|
3539
|
+
continue;
|
|
3540
|
+
}
|
|
3541
|
+
const opener = closerFor(token.value);
|
|
3542
|
+
if (opener === undefined) {
|
|
3543
|
+
continue;
|
|
3544
|
+
}
|
|
3545
|
+
const open = stack.pop();
|
|
3546
|
+
if (open === undefined || !isPunct(tokens[open], opener)) {
|
|
3547
|
+
// Do not pair an enclosing option object across a malformed nested delimiter.
|
|
3548
|
+
stack.length = 0;
|
|
3549
|
+
}
|
|
3550
|
+
else {
|
|
3551
|
+
closing.set(open, index);
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
return closing;
|
|
3555
|
+
}
|
|
3556
|
+
/** The opener a closer matches, or undefined when the token is not a closer. */
|
|
3557
|
+
function closerFor(value) {
|
|
3558
|
+
if (value === ')') {
|
|
3559
|
+
return '(';
|
|
3560
|
+
}
|
|
3561
|
+
return value === ']' ? '[' : value === '}' ? '{' : undefined;
|
|
3562
|
+
}
|
|
3563
|
+
/**
|
|
3564
|
+
* Recognize the supported dynamic import arguments: a literal source plus either no further
|
|
3565
|
+
* argument or one balanced object-literal options expression. Option contents are never evaluated
|
|
3566
|
+
* and never contribute evidence. `require` keeps the stricter sole-literal rule.
|
|
3567
|
+
*/
|
|
3568
|
+
function dynamicImportSpecifier(tokens, argIndex, closingDelimiter) {
|
|
3569
|
+
const sole = soleStringSpecifier(tokens, argIndex);
|
|
3570
|
+
if (sole !== undefined) {
|
|
3571
|
+
return sole;
|
|
3572
|
+
}
|
|
3573
|
+
const specifier = tokens[argIndex];
|
|
3574
|
+
if (specifier?.kind !== 'string' ||
|
|
3575
|
+
!isPunct(tokens[argIndex + 1], ',') ||
|
|
3576
|
+
!isPunct(tokens[argIndex + 2], '{')) {
|
|
3577
|
+
return undefined;
|
|
3578
|
+
}
|
|
3579
|
+
const optionsClose = closingDelimiter(argIndex + 2);
|
|
3580
|
+
if (optionsClose === undefined) {
|
|
3581
|
+
return undefined;
|
|
3582
|
+
}
|
|
3583
|
+
const next = isPunct(tokens[optionsClose + 1], ',') ? optionsClose + 2 : optionsClose + 1;
|
|
3584
|
+
return isPunct(tokens[next], ')') ? specifier : undefined;
|
|
3585
|
+
}
|
|
2697
3586
|
/**
|
|
2698
|
-
* The
|
|
2699
|
-
* when it is a string
|
|
3587
|
+
* The string token at `argIndex` when it is the only argument of the surrounding call, that is
|
|
3588
|
+
* when it is a string followed by an optional trailing comma and `)`. Returns undefined otherwise,
|
|
2700
3589
|
* so a dynamic argument (e.g. `'pkg-' + name`) or a call with extra arguments (e.g.
|
|
2701
3590
|
* `require('pkg', ctx)`) never yields a static specifier.
|
|
2702
3591
|
*/
|
|
2703
3592
|
function soleStringSpecifier(tokens, argIndex) {
|
|
2704
3593
|
const arg = tokens[argIndex];
|
|
2705
|
-
const
|
|
3594
|
+
const closingIndex = isPunct(tokens[argIndex + 1], ',') ? argIndex + 2 : argIndex + 1;
|
|
3595
|
+
const closing = tokens[closingIndex];
|
|
2706
3596
|
if (arg?.kind !== 'string') {
|
|
2707
3597
|
return undefined;
|
|
2708
3598
|
}
|
|
2709
3599
|
if (closing?.kind !== 'punct' || closing.value !== ')') {
|
|
2710
3600
|
return undefined;
|
|
2711
3601
|
}
|
|
2712
|
-
return arg
|
|
3602
|
+
return arg;
|
|
2713
3603
|
}
|
|
2714
3604
|
/* -------------------------------------------------------------------------- */
|
|
2715
3605
|
/* Lexical constructs */
|
|
@@ -2725,7 +3615,88 @@ function skipBlockComment(source, start) {
|
|
|
2725
3615
|
}
|
|
2726
3616
|
throw new ModuleSpecifierError('Unterminated block comment', 'invalid-source', start);
|
|
2727
3617
|
}
|
|
3618
|
+
/** A dynamic import remains valid script syntax; only static forms and meta signal a module. */
|
|
3619
|
+
function importSignalsModule(source, end, atRoot) {
|
|
3620
|
+
const next = skipTrivia(source, end, source.length);
|
|
3621
|
+
if (source[next] === '.') {
|
|
3622
|
+
const meta = skipTrivia(source, next + 1, source.length);
|
|
3623
|
+
return ((isIdentifierStartAt(source, meta) || isUnicodeEscapeStart(source, meta)) &&
|
|
3624
|
+
readIdentifier(source, meta).value === 'meta');
|
|
3625
|
+
}
|
|
3626
|
+
return (atRoot &&
|
|
3627
|
+
(source[next] === "'" ||
|
|
3628
|
+
source[next] === '"' ||
|
|
3629
|
+
source[next] === '{' ||
|
|
3630
|
+
source[next] === '*' ||
|
|
3631
|
+
isIdentifierStartAt(source, next) ||
|
|
3632
|
+
isUnicodeEscapeStart(source, next)));
|
|
3633
|
+
}
|
|
3634
|
+
function exportSignalsModule(source, end) {
|
|
3635
|
+
const next = skipTrivia(source, end, source.length);
|
|
3636
|
+
return (source[next] === '{' ||
|
|
3637
|
+
source[next] === '*' ||
|
|
3638
|
+
(isIdentifierStartAt(source, next) &&
|
|
3639
|
+
['default', 'const', 'let', 'var', 'function', 'async', 'class'].includes(readIdentifier(source, next).value)));
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Probe top-level await without treating every identifier named await as a keyword. A complete
|
|
3643
|
+
* regex may be read opaquely during this pass even when its following operand makes it script
|
|
3644
|
+
* division. That protects apostrophes until a later static declaration settles the whole file.
|
|
3645
|
+
* Calls, indexing, tags, unary +/- and flag-like identifier divisors remain ambiguous script
|
|
3646
|
+
* expressions absent another signal.
|
|
3647
|
+
*/
|
|
3648
|
+
function probeAwaitOperand(source, end, inForHead) {
|
|
3649
|
+
const trivia = readTrivia(source, end, source.length);
|
|
3650
|
+
const next = trivia.end;
|
|
3651
|
+
const ch = source[next];
|
|
3652
|
+
if (ch === '/') {
|
|
3653
|
+
const finish = regexEnd(source, next);
|
|
3654
|
+
if (finish === undefined) {
|
|
3655
|
+
return { keyword: false, module: false };
|
|
3656
|
+
}
|
|
3657
|
+
const following = skipTrivia(source, finish, source.length);
|
|
3658
|
+
const tail = source[following];
|
|
3659
|
+
// Characters consumed as flags can instead be a complete identifier divisor (including a
|
|
3660
|
+
// valid flag such as g). Its following operator/terminator cannot establish module grammar.
|
|
3661
|
+
const hasIdentifierDivisor = source[finish - 1] !== '/';
|
|
3662
|
+
const comparisonKeyword = (isIdentifierStartAt(source, following) || isUnicodeEscapeStart(source, following)) &&
|
|
3663
|
+
['in', 'instanceof'].includes(readIdentifier(source, following).value);
|
|
3664
|
+
const unambiguous = !hasIdentifierDivisor &&
|
|
3665
|
+
(tail === undefined ||
|
|
3666
|
+
[';', ',', ')', ']', '}', '?'].includes(tail) ||
|
|
3667
|
+
(tail === '.' && !isNumberStart(source[following + 1] ?? '')) ||
|
|
3668
|
+
// These binary operators cannot start the missing right-hand divisor. Unary +/-,
|
|
3669
|
+
// calls, indexing, templates and decimal starts still admit script expressions.
|
|
3670
|
+
['<', '>', '*', '%', '&', '|', '^'].includes(tail) ||
|
|
3671
|
+
source.startsWith('==', following) ||
|
|
3672
|
+
source.startsWith('!=', following) ||
|
|
3673
|
+
comparisonKeyword);
|
|
3674
|
+
return { keyword: true, module: unambiguous };
|
|
3675
|
+
}
|
|
3676
|
+
// A newline also permits a bare await identifier statement followed by another statement.
|
|
3677
|
+
if (trivia.lineBreak) {
|
|
3678
|
+
return { keyword: false, module: false };
|
|
3679
|
+
}
|
|
3680
|
+
if (isIdentifierStartAt(source, next) || isUnicodeEscapeStart(source, next)) {
|
|
3681
|
+
const name = readIdentifier(source, next).value;
|
|
3682
|
+
const operand = !['in', 'instanceof'].includes(name) && !(name === 'of' && inForHead);
|
|
3683
|
+
return { keyword: operand, module: operand };
|
|
3684
|
+
}
|
|
3685
|
+
const operand = ch !== undefined &&
|
|
3686
|
+
(isNumberStart(ch) ||
|
|
3687
|
+
(ch === '.' && isNumberStart(source[next + 1] ?? '')) ||
|
|
3688
|
+
["'", '"', '{', '~'].includes(ch) ||
|
|
3689
|
+
(ch === '!' && source[next + 1] !== '='));
|
|
3690
|
+
return { keyword: operand, module: operand };
|
|
3691
|
+
}
|
|
2728
3692
|
function skipRegex(source, start) {
|
|
3693
|
+
const end = regexEnd(source, start);
|
|
3694
|
+
if (end === undefined) {
|
|
3695
|
+
throw new ModuleSpecifierError('Unterminated regex literal', 'invalid-source', start);
|
|
3696
|
+
}
|
|
3697
|
+
return end;
|
|
3698
|
+
}
|
|
3699
|
+
function regexEnd(source, start) {
|
|
2729
3700
|
const n = source.length;
|
|
2730
3701
|
let i = start + 1;
|
|
2731
3702
|
let inClass = false;
|
|
@@ -2734,7 +3705,7 @@ function skipRegex(source, start) {
|
|
|
2734
3705
|
// A regex literal cannot contain a raw line terminator. Rejecting it here stops a wrong regex
|
|
2735
3706
|
// start from swallowing the following lines.
|
|
2736
3707
|
if (ch === '\n' || ch === '\r' || ch === '\u2028' || ch === '\u2029') {
|
|
2737
|
-
|
|
3708
|
+
return undefined;
|
|
2738
3709
|
}
|
|
2739
3710
|
if (ch === '\\') {
|
|
2740
3711
|
i += 2;
|
|
@@ -2757,7 +3728,7 @@ function skipRegex(source, start) {
|
|
|
2757
3728
|
}
|
|
2758
3729
|
i++;
|
|
2759
3730
|
}
|
|
2760
|
-
|
|
3731
|
+
return undefined;
|
|
2761
3732
|
}
|
|
2762
3733
|
function skipRegexFlags(source, i) {
|
|
2763
3734
|
const n = source.length;
|