depvitals 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -40
- package/dist/cli/runCli.d.ts.map +1 -1
- package/dist/cli/runCli.js +9 -4
- package/dist/cli/runCli.js.map +1 -1
- package/dist/config/ignoreDependencies.d.ts +7 -0
- package/dist/config/ignoreDependencies.d.ts.map +1 -0
- package/dist/config/ignoreDependencies.js +18 -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 +23 -0
- package/dist/config/readProjectConfig.d.ts.map +1 -0
- package/dist/config/readProjectConfig.js +106 -0
- package/dist/config/readProjectConfig.js.map +1 -0
- package/dist/core/analyzeProject.d.ts +5 -1
- package/dist/core/analyzeProject.d.ts.map +1 -1
- package/dist/core/analyzeProject.js +37 -29
- package/dist/core/analyzeProject.js.map +1 -1
- package/dist/detectors/eslintManifestConfig.d.ts.map +1 -1
- package/dist/detectors/eslintManifestConfig.js +7 -4
- package/dist/detectors/eslintManifestConfig.js.map +1 -1
- package/dist/detectors/eslintRcJsonConfig.d.ts.map +1 -1
- package/dist/detectors/eslintRcJsonConfig.js +2 -1
- package/dist/detectors/eslintRcJsonConfig.js.map +1 -1
- package/dist/detectors/jestManifestConfig.d.ts.map +1 -1
- package/dist/detectors/jestManifestConfig.js +6 -4
- package/dist/detectors/jestManifestConfig.js.map +1 -1
- package/dist/scanner/moduleSpecifiers.d.ts +2 -2
- package/dist/scanner/moduleSpecifiers.d.ts.map +1 -1
- package/dist/scanner/moduleSpecifiers.js +1111 -178
- 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 +19 -3
- package/dist/scanner/projectManifest.d.ts.map +1 -1
- package/dist/scanner/projectManifest.js +51 -12
- package/dist/scanner/projectManifest.js.map +1 -1
- package/dist/scanner/scriptUsage.d.ts +2 -1
- package/dist/scanner/scriptUsage.d.ts.map +1 -1
- package/dist/scanner/scriptUsage.js +182 -37
- package/dist/scanner/scriptUsage.js.map +1 -1
- package/dist/scanner/sourceFiles.d.ts +16 -2
- package/dist/scanner/sourceFiles.d.ts.map +1 -1
- package/dist/scanner/sourceFiles.js +12 -6
- package/dist/scanner/sourceFiles.js.map +1 -1
- package/package.json +1 -1
|
@@ -43,14 +43,21 @@ export class ModuleSpecifierError extends Error {
|
|
|
43
43
|
* from which the scanner cannot safely continue.
|
|
44
44
|
*/
|
|
45
45
|
export function extractModuleSpecifiers(source, options = {}) {
|
|
46
|
-
|
|
46
|
+
const jsx = options.jsx ?? false;
|
|
47
|
+
// Ambiguous input gets one non-interpreting lexical probe and one definitive extraction. No
|
|
48
|
+
// source reread, parser retry after an exception, or repeated per-statement suffix scans.
|
|
49
|
+
const module = options.module === 'detect'
|
|
50
|
+
? tokenize(source, jsx, false, true).some((token) => token.moduleSignal === true)
|
|
51
|
+
: (options.module ?? false);
|
|
52
|
+
return interpret(tokenize(source, jsx, module));
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
55
|
* Turn source into a stream of significant tokens, skipping whitespace, line/block comments, regex
|
|
50
56
|
* literals and template literals.
|
|
51
57
|
*/
|
|
52
|
-
function tokenize(source, jsx = false, module = false) {
|
|
58
|
+
function tokenize(source, jsx = false, module = false, detectModule = false) {
|
|
53
59
|
const tokens = [];
|
|
60
|
+
const typeArgumentBounds = new Map();
|
|
54
61
|
const n = source.length;
|
|
55
62
|
let i = 0;
|
|
56
63
|
// Whether the previous identifier token is an expression-starting keyword (return, typeof, ...).
|
|
@@ -82,7 +89,14 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
82
89
|
if (memberAtToken !== undefined) {
|
|
83
90
|
advanceMember(memberAtToken, significant, tokens[tokens.length - 1]);
|
|
84
91
|
}
|
|
85
|
-
|
|
92
|
+
const scope = requireScopes[requireScopes.length - 1];
|
|
93
|
+
tokens.push(scope?.inParameters === true
|
|
94
|
+
? {
|
|
95
|
+
...significant,
|
|
96
|
+
parameterFrame: frames.length,
|
|
97
|
+
...(typeAnnotation === undefined ? {} : { parameterType: true }),
|
|
98
|
+
}
|
|
99
|
+
: significant);
|
|
86
100
|
if (token.kind !== 'punct' || token.syntheticBoundary !== true) {
|
|
87
101
|
htmlCloseCommentAllowed = false;
|
|
88
102
|
}
|
|
@@ -108,6 +122,19 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
108
122
|
// Function context of every open function body, tracked independently of expression frame resets
|
|
109
123
|
// so `${}` boundaries cannot change the nearest await/yield grammar.
|
|
110
124
|
const functionContextStack = [];
|
|
125
|
+
const requireScopes = [];
|
|
126
|
+
const closeRequireScope = (scope) => {
|
|
127
|
+
if (scope !== undefined && requireScopes[requireScopes.length - 1] === scope) {
|
|
128
|
+
requireScopes.pop();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const recordRequireBinding = (parameters, end) => {
|
|
132
|
+
const scope = parameters.signature?.context.requireScope;
|
|
133
|
+
const bindingEnd = parameters.bindingEnd ?? end;
|
|
134
|
+
if (scope !== undefined && parameters.bindingStart < bindingEnd) {
|
|
135
|
+
scope.bindings.push({ start: parameters.bindingStart, end: bindingEnd });
|
|
136
|
+
}
|
|
137
|
+
};
|
|
111
138
|
// The nearest function body owns the await/yield grammar. An ordinary nested function therefore
|
|
112
139
|
// suspends an async/generator context from an outer function instead of inheriting it.
|
|
113
140
|
// A default callback may open and finish while an outer signature is still reading parameters.
|
|
@@ -139,6 +166,12 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
139
166
|
frames.length === context.frameDepth;
|
|
140
167
|
const insideObjectLiteral = () => braceContexts[braceContexts.length - 1]?.role === 'object';
|
|
141
168
|
const insideClassBody = () => braceContexts[braceContexts.length - 1]?.role === 'class';
|
|
169
|
+
const atMethodContainer = () => {
|
|
170
|
+
const brace = braceContexts[braceContexts.length - 1];
|
|
171
|
+
return brace?.role === 'object'
|
|
172
|
+
? atLexicalDepth(brace.depth)
|
|
173
|
+
: brace?.role === 'class' && atLexicalDepth(brace.state);
|
|
174
|
+
};
|
|
142
175
|
const currentClass = () => {
|
|
143
176
|
const brace = braceContexts[braceContexts.length - 1];
|
|
144
177
|
return brace?.role === 'class' ? brace.state : undefined;
|
|
@@ -166,8 +199,8 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
166
199
|
pendingControlKeyword = undefined;
|
|
167
200
|
closedObjectBrace = false;
|
|
168
201
|
};
|
|
169
|
-
// JSX markup and sibling
|
|
170
|
-
// boundary keeps the token stream from gluing
|
|
202
|
+
// Template interpolations, JSX markup and sibling containers are separate expressions. An
|
|
203
|
+
// explicit boundary keeps the token stream from gluing them into an import/require sequence that
|
|
171
204
|
// does not exist in source (for example `<p>{import}{('pkg')}</p>`).
|
|
172
205
|
const pushBoundary = (offset) => {
|
|
173
206
|
emit({
|
|
@@ -228,6 +261,8 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
228
261
|
? { typeOwner: { annotation: typeAnnotation, depth: { ...typeAnnotation } } }
|
|
229
262
|
: {}),
|
|
230
263
|
});
|
|
264
|
+
// The preceding tag is not the callee of tokens inside this interpolation.
|
|
265
|
+
pushBoundary(i);
|
|
231
266
|
inText = false;
|
|
232
267
|
resetExpressionState();
|
|
233
268
|
i += 2;
|
|
@@ -361,6 +396,35 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
361
396
|
typeAnnotation = undefined;
|
|
362
397
|
inType = false;
|
|
363
398
|
}
|
|
399
|
+
const expressionOwner = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
400
|
+
if (typeAnnotation === undefined &&
|
|
401
|
+
ch === '<' &&
|
|
402
|
+
prevKind === 'identifier' &&
|
|
403
|
+
expressionOwner !== undefined &&
|
|
404
|
+
atLexicalDepth(expressionOwner)) {
|
|
405
|
+
const end = expressionTypeArgumentEnd(source, i, n, typeArgumentBounds);
|
|
406
|
+
if (end !== undefined) {
|
|
407
|
+
// Use the ordinary type overlay: it preserves template imports and lexical scopes.
|
|
408
|
+
typeAnnotation = {
|
|
409
|
+
role: 'assertion',
|
|
410
|
+
conditionalDepth: 0,
|
|
411
|
+
conditionalOperand: false,
|
|
412
|
+
importCallPending: false,
|
|
413
|
+
owner: {
|
|
414
|
+
parenDepth,
|
|
415
|
+
bracketDepth,
|
|
416
|
+
braceDepth: braceContexts.length,
|
|
417
|
+
frameDepth: frames.length,
|
|
418
|
+
},
|
|
419
|
+
parenDepth: 0,
|
|
420
|
+
bracketDepth: 0,
|
|
421
|
+
braceDepth: 0,
|
|
422
|
+
angleDepth: 0,
|
|
423
|
+
typeOperandExpected: true,
|
|
424
|
+
};
|
|
425
|
+
inType = true;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
364
428
|
// An assertion must release its type context before the same runtime delimiter can retire
|
|
365
429
|
// an enclosing arrow. Consume the delimiter only after restoring the nearest function grammar.
|
|
366
430
|
let expressionFunctionContext = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
@@ -369,7 +433,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
369
433
|
frames[frames.length - 1]?.kind !== 'jsx-text' &&
|
|
370
434
|
frames[frames.length - 1]?.kind !== 'jsx-tag' &&
|
|
371
435
|
expressionFunctionBodyEnded(ch, parenDepth, bracketDepth, braceContexts.length, frames.length, functionContextStack.length, conditionalContexts.length, expressionFunctionContext)) {
|
|
372
|
-
expressionFunctionContexts.pop();
|
|
436
|
+
closeRequireScope(expressionFunctionContexts.pop()?.requireScope);
|
|
373
437
|
expressionFunctionContext = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
374
438
|
}
|
|
375
439
|
if (typeAnnotation?.role === 'return' &&
|
|
@@ -377,11 +441,15 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
377
441
|
atLexicalDepth(typeAnnotation.signature.owner) &&
|
|
378
442
|
atTypeRoot(typeAnnotation) &&
|
|
379
443
|
!typeAnnotation.typeOperandExpected &&
|
|
380
|
-
identifier !== undefined &&
|
|
381
|
-
|
|
444
|
+
((identifier !== undefined &&
|
|
445
|
+
(!TYPE_OPERAND_KEYWORDS.has(identifier.value) || identifier.value === 'const')) ||
|
|
446
|
+
(ch === '[' &&
|
|
447
|
+
typeAnnotation.signature.context.isClassMember === true &&
|
|
448
|
+
computedMethodFollows(source, i, n)))) {
|
|
382
449
|
const signature = typeAnnotation.signature;
|
|
383
|
-
if (pendingFunctionContexts[pendingFunctionContexts.length - 1] ===
|
|
384
|
-
|
|
450
|
+
if (pendingFunctionContexts[pendingFunctionContexts.length - 1]?.requireScope ===
|
|
451
|
+
signature.context.requireScope) {
|
|
452
|
+
closeRequireScope(pendingFunctionContexts.pop()?.requireScope);
|
|
385
453
|
}
|
|
386
454
|
if (classState !== undefined && signature.context.isClassMember === true) {
|
|
387
455
|
classState.member = undefined;
|
|
@@ -389,6 +457,18 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
389
457
|
typeAnnotation = undefined;
|
|
390
458
|
inType = false;
|
|
391
459
|
}
|
|
460
|
+
const pendingBodylessSignature = pendingFunctionContexts[pendingFunctionContexts.length - 1];
|
|
461
|
+
if (pendingBodylessSignature?.phase === 'awaiting-body' &&
|
|
462
|
+
atLexicalDepth(pendingBodylessSignature) &&
|
|
463
|
+
((typeAnnotation === undefined && tokenLineBreakBefore && ch !== '{' && ch !== ':') ||
|
|
464
|
+
(ch === '}' &&
|
|
465
|
+
(typeAnnotation === undefined ||
|
|
466
|
+
(typeAnnotation.role === 'return' && atTypeRoot(typeAnnotation)))))) {
|
|
467
|
+
closeRequireScope(pendingFunctionContexts.pop()?.requireScope);
|
|
468
|
+
typeAnnotation = undefined;
|
|
469
|
+
inType = false;
|
|
470
|
+
resetExpressionState();
|
|
471
|
+
}
|
|
392
472
|
if (classState !== undefined &&
|
|
393
473
|
atLexicalDepth(classState) &&
|
|
394
474
|
(typeAnnotation === undefined ||
|
|
@@ -426,7 +506,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
426
506
|
frames[frames.length - 1]?.kind !== 'jsx-text' &&
|
|
427
507
|
frames[frames.length - 1]?.kind !== 'jsx-tag' &&
|
|
428
508
|
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();
|
|
509
|
+
closeRequireScope(expressionFunctionContexts.pop()?.requireScope);
|
|
430
510
|
context = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
431
511
|
}
|
|
432
512
|
lineTerminatorSinceLastToken = false;
|
|
@@ -556,7 +636,9 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
556
636
|
typeAnnotation = undefined;
|
|
557
637
|
inType = false;
|
|
558
638
|
lineTerminatorSinceLastToken = false;
|
|
559
|
-
if (!annotation.signature.isFunctionKeyword
|
|
639
|
+
if (!annotation.signature.isFunctionKeyword &&
|
|
640
|
+
pendingFunctionContexts[pendingFunctionContexts.length - 1]?.requireScope !==
|
|
641
|
+
annotation.signature.context.requireScope) {
|
|
560
642
|
pendingFunctionContexts.push({
|
|
561
643
|
...annotation.signature.context,
|
|
562
644
|
...annotation.signature.owner,
|
|
@@ -569,7 +651,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
569
651
|
atRoot &&
|
|
570
652
|
annotation.role === 'return' &&
|
|
571
653
|
annotation.signature.isArrowSignature &&
|
|
572
|
-
!functionTypeArrowContinues(source, i, n)) {
|
|
654
|
+
!functionTypeArrowContinues(source, i, n, conditional !== undefined && atLexicalDepth(conditional))) {
|
|
573
655
|
// The signature's own `=>`: everything after it is the body, not the return type.
|
|
574
656
|
typedArrowContext = annotation.signature.context;
|
|
575
657
|
typeAnnotation = undefined;
|
|
@@ -666,6 +748,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
666
748
|
closed?.role === 'function' ||
|
|
667
749
|
closed?.role === 'class';
|
|
668
750
|
if (closed?.role === 'function') {
|
|
751
|
+
closeRequireScope(closed.context.requireScope);
|
|
669
752
|
functionContextStack.pop();
|
|
670
753
|
}
|
|
671
754
|
if ((closed?.role === 'function' || closed?.role === 'static-block') &&
|
|
@@ -741,8 +824,17 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
741
824
|
!afterControlHead &&
|
|
742
825
|
!prevIsExpressionKeyword &&
|
|
743
826
|
canStartTypeAssertion(tokens[tokens.length - 1]);
|
|
827
|
+
// Capture the enclosing operand grammar before this identifier replaces it. Contextual
|
|
828
|
+
// await/yield, an arrow head and a fresh embedded expression all expect an expression.
|
|
829
|
+
const expressionPosition = functionIsExpressionPosition(prevKind, prevPunct, colonIsExpression, (prevKind === 'identifier' &&
|
|
830
|
+
prevIsExpressionKeyword &&
|
|
831
|
+
!BLOCK_AFTER_KEYWORD.has(previousIdentifier ?? '') &&
|
|
832
|
+
!((previousIdentifier === 'return' || previousIdentifier === 'yield') &&
|
|
833
|
+
tokenLineBreakBefore)) ||
|
|
834
|
+
(isPunct(tokens[tokens.length - 1], '>') && isArrowHead(tokens[tokens.length - 2])) ||
|
|
835
|
+
(prevKind === 'start' && (frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr')));
|
|
744
836
|
if (value === 'async') {
|
|
745
|
-
asyncFunctionIsExpression =
|
|
837
|
+
asyncFunctionIsExpression = expressionPosition;
|
|
746
838
|
if (isIdentifier(tokens[tokens.length - 1], 'default') &&
|
|
747
839
|
isIdentifier(tokens[tokens.length - 2], 'export')) {
|
|
748
840
|
asyncFunctionIsExpression = false;
|
|
@@ -750,8 +842,13 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
750
842
|
}
|
|
751
843
|
prevIdentifier = value;
|
|
752
844
|
const isMemberName = prevKind === 'punct' && prevPunct === '.';
|
|
845
|
+
const atRoot = braceContexts.length === 0 && parenDepth === 0 && bracketDepth === 0 && frames.length === 0;
|
|
846
|
+
let moduleSignal = detectModule &&
|
|
847
|
+
!isMemberName &&
|
|
848
|
+
((value === 'import' && importSignalsModule(source, i, atRoot)) ||
|
|
849
|
+
(value === 'export' && atRoot && exportSignalsModule(source, i)));
|
|
753
850
|
const classMemberName = value === 'class' &&
|
|
754
|
-
(
|
|
851
|
+
atMethodContainer() &&
|
|
755
852
|
memberDeclarationHead(tokens, tokens.length, insideClassBody(), currentClass()?.member?.start) !== undefined;
|
|
756
853
|
const nextSignificant = skipTrivia(source, i, n);
|
|
757
854
|
const classPropertyName = value === 'class' &&
|
|
@@ -769,10 +866,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
769
866
|
bracketDepth,
|
|
770
867
|
braceDepth: braceContexts.length,
|
|
771
868
|
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]))),
|
|
869
|
+
isExpression: !isExportDefault && (previousIdentifier === 'extends' || expressionPosition),
|
|
776
870
|
});
|
|
777
871
|
}
|
|
778
872
|
// A keyword after `.` is a property name (e.g. `x.return`), not an expression start.
|
|
@@ -805,8 +899,19 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
805
899
|
const functionContext = parameterGrammarSuspended || fieldGrammarSuspended
|
|
806
900
|
? undefined
|
|
807
901
|
: (expressionContextIsCurrent ?? bodyContext);
|
|
902
|
+
const probe = detectModule &&
|
|
903
|
+
value === 'await' &&
|
|
904
|
+
!(prevKind === 'identifier' &&
|
|
905
|
+
['class', 'function', 'var', 'let', 'const', 'extends'].includes(previousIdentifier ?? '')) &&
|
|
906
|
+
functionContext === undefined &&
|
|
907
|
+
!parameterGrammarSuspended &&
|
|
908
|
+
!fieldGrammarSuspended
|
|
909
|
+
? probeAwaitOperand(source, i, parameterContexts[parameterContexts.length - 1]?.forPhase !== undefined)
|
|
910
|
+
: undefined;
|
|
911
|
+
moduleSignal ||= probe?.module === true;
|
|
808
912
|
if (value === 'await' &&
|
|
809
913
|
(functionContext?.async === true ||
|
|
914
|
+
probe?.keyword === true ||
|
|
810
915
|
(functionContext === undefined &&
|
|
811
916
|
module &&
|
|
812
917
|
!parameterGrammarSuspended &&
|
|
@@ -826,20 +931,24 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
826
931
|
// is async (`async function`, i.e. preceded by `async`) or a generator (`function*`, resolved
|
|
827
932
|
// when a following `*` is seen). The context is consumed at the body `{`.
|
|
828
933
|
const methodName = value === 'function' &&
|
|
829
|
-
(
|
|
934
|
+
atMethodContainer() &&
|
|
830
935
|
memberDeclarationHead(tokens, tokens.length, insideClassBody(), currentClass()?.member?.start) !== undefined;
|
|
831
936
|
if (value === 'function' && !isMemberName && !methodName && typeAnnotation === undefined) {
|
|
832
937
|
// `async function` is only async without a line terminator between the two words: a line
|
|
833
938
|
// break ends the `async` statement, so the declaration that follows is an ordinary one.
|
|
834
939
|
const isAsync = previousIdentifier === 'async' && prevKind === 'identifier' && !tokenLineBreakBefore;
|
|
835
940
|
pendingFunctionContexts.push({
|
|
941
|
+
requireScope: {
|
|
942
|
+
parent: requireScopes[requireScopes.length - 1],
|
|
943
|
+
bindings: [],
|
|
944
|
+
inParameters: true,
|
|
945
|
+
},
|
|
836
946
|
async: isAsync,
|
|
837
947
|
generator: false,
|
|
838
948
|
isExpression: isAsync
|
|
839
949
|
? asyncFunctionIsExpression
|
|
840
950
|
: !(isIdentifier(tokens[tokens.length - 1], 'default') &&
|
|
841
|
-
isIdentifier(tokens[tokens.length - 2], 'export')) &&
|
|
842
|
-
functionIsExpressionPosition(prevKind, prevPunct, previousIdentifier, colonIsExpression),
|
|
951
|
+
isIdentifier(tokens[tokens.length - 2], 'export')) && expressionPosition,
|
|
843
952
|
parenDepth,
|
|
844
953
|
bracketDepth,
|
|
845
954
|
braceDepth: braceContexts.length,
|
|
@@ -848,13 +957,42 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
848
957
|
phase: 'awaiting-parameters',
|
|
849
958
|
});
|
|
850
959
|
}
|
|
960
|
+
// A bare arrow parameter has no opening parenthesis to own its binding range.
|
|
961
|
+
const next = !inType && !isMemberName ? skipTrivia(source, i, n) : n;
|
|
962
|
+
const singleArrowContext = source[next] === '=' && source[next + 1] === '>'
|
|
963
|
+
? {
|
|
964
|
+
async: prevKind === 'identifier' &&
|
|
965
|
+
previousIdentifier === 'async' &&
|
|
966
|
+
!tokenLineBreakBefore,
|
|
967
|
+
generator: false,
|
|
968
|
+
isExpression: false,
|
|
969
|
+
requireScope: {
|
|
970
|
+
parent: requireScopes[requireScopes.length - 1],
|
|
971
|
+
bindings: [{ start: tokens.length, end: tokens.length + 1 }],
|
|
972
|
+
inParameters: false,
|
|
973
|
+
},
|
|
974
|
+
}
|
|
975
|
+
: undefined;
|
|
976
|
+
if (singleArrowContext?.requireScope !== undefined) {
|
|
977
|
+
requireScopes.push(singleArrowContext.requireScope);
|
|
978
|
+
}
|
|
851
979
|
emit({
|
|
852
980
|
kind: 'identifier',
|
|
853
981
|
value,
|
|
854
982
|
offset: start,
|
|
983
|
+
...(singleArrowContext === undefined ? {} : { signatureContext: singleArrowContext }),
|
|
855
984
|
endOffset: i,
|
|
985
|
+
...(value === 'require' && requireScopes.length > 0
|
|
986
|
+
? { requireScope: requireScopes[requireScopes.length - 1] }
|
|
987
|
+
: {}),
|
|
856
988
|
...(isMemberName ? { propertyName: true } : {}),
|
|
989
|
+
...(moduleSignal ? { moduleSignal: true } : {}),
|
|
857
990
|
});
|
|
991
|
+
// Once the mode is known, only the definitive module pass should validate the remainder.
|
|
992
|
+
// Continuing this probe with script grammar could reject valid newline await operands.
|
|
993
|
+
if (detectModule && moduleSignal) {
|
|
994
|
+
return tokens;
|
|
995
|
+
}
|
|
858
996
|
if (startsAssertion) {
|
|
859
997
|
typeAnnotation = {
|
|
860
998
|
role: 'assertion',
|
|
@@ -940,6 +1078,7 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
940
1078
|
angleDepth: 0,
|
|
941
1079
|
typeOperandExpected: true,
|
|
942
1080
|
};
|
|
1081
|
+
parameter.bindingEnd ??= tokens.length;
|
|
943
1082
|
parameter.phase = 'type';
|
|
944
1083
|
inType = true;
|
|
945
1084
|
}
|
|
@@ -1042,7 +1181,17 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1042
1181
|
braceContexts.push({ role: 'static-block', owner });
|
|
1043
1182
|
}
|
|
1044
1183
|
else {
|
|
1045
|
-
braceContexts.push(
|
|
1184
|
+
braceContexts.push(isObject
|
|
1185
|
+
? {
|
|
1186
|
+
role: 'object',
|
|
1187
|
+
depth: {
|
|
1188
|
+
parenDepth,
|
|
1189
|
+
bracketDepth,
|
|
1190
|
+
braceDepth: braceContexts.length + 1,
|
|
1191
|
+
frameDepth: frames.length,
|
|
1192
|
+
},
|
|
1193
|
+
}
|
|
1194
|
+
: { role: 'block' });
|
|
1046
1195
|
}
|
|
1047
1196
|
// Count the brace inside the innermost open `${...}` or JSX `{...}` expression so its closing
|
|
1048
1197
|
// `}` is found.
|
|
@@ -1054,18 +1203,38 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1054
1203
|
// A `(` after a control keyword opens a control head; only its matching `)` may then be
|
|
1055
1204
|
// followed by a regex literal, so the nesting depth of every `(` has to be tracked.
|
|
1056
1205
|
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)
|
|
1206
|
+
const signatureCandidate = parameterSignatureContext(tokens, pending !== undefined && atLexicalDepth(pending) ? pending : undefined, atMethodContainer(), insideClassBody(), tokenLineBreakBefore, currentClass()?.member?.start);
|
|
1207
|
+
// A ternary's alternate may itself be an arrow. Its => is not proof that
|
|
1208
|
+
// the consequent's parentheses were parameters: a typed arrow must still
|
|
1209
|
+
// leave the enclosing conditional's colon after its own body.
|
|
1210
|
+
const needsConditionalEnd = conditional !== undefined && atLexicalDepth(conditional);
|
|
1211
|
+
const selectedSignature = signatureCandidate?.isArrowSignature === true &&
|
|
1212
|
+
!arrowFollowsParametersSafely(source, i, n, jsx, needsConditionalEnd)
|
|
1065
1213
|
? undefined
|
|
1066
1214
|
: signatureCandidate;
|
|
1215
|
+
const signature = selectedSignature === undefined
|
|
1216
|
+
? undefined
|
|
1217
|
+
: {
|
|
1218
|
+
...selectedSignature,
|
|
1219
|
+
context: selectedSignature.context.requireScope !== undefined
|
|
1220
|
+
? selectedSignature.context
|
|
1221
|
+
: {
|
|
1222
|
+
...selectedSignature.context,
|
|
1223
|
+
requireScope: {
|
|
1224
|
+
parent: requireScopes[requireScopes.length - 1],
|
|
1225
|
+
bindings: [],
|
|
1226
|
+
inParameters: true,
|
|
1227
|
+
},
|
|
1228
|
+
},
|
|
1229
|
+
};
|
|
1230
|
+
if (signature?.context.requireScope !== undefined) {
|
|
1231
|
+
requireScopes.push(signature.context.requireScope);
|
|
1232
|
+
}
|
|
1067
1233
|
parameterContexts.push({
|
|
1068
1234
|
openIndex: tokens.length,
|
|
1235
|
+
bindingStart: tokens.length + 1,
|
|
1236
|
+
bindingEnd: undefined,
|
|
1237
|
+
bindingCommas: [],
|
|
1069
1238
|
signature: signature === undefined
|
|
1070
1239
|
? undefined
|
|
1071
1240
|
: {
|
|
@@ -1111,8 +1280,25 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1111
1280
|
pending.phase = 'awaiting-body';
|
|
1112
1281
|
}
|
|
1113
1282
|
const parameters = parameterContexts.pop();
|
|
1283
|
+
if (parameters?.signature !== undefined) {
|
|
1284
|
+
recordRequireBinding(parameters, tokens.length);
|
|
1285
|
+
const scope = parameters.signature.context.requireScope;
|
|
1286
|
+
if (scope !== undefined) {
|
|
1287
|
+
scope.inParameters = false;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1114
1290
|
matchingOpen = parameters?.openIndex;
|
|
1115
1291
|
closedParameterSignature = parameters?.signature;
|
|
1292
|
+
if (closedParameterSignature !== undefined &&
|
|
1293
|
+
!closedParameterSignature.isFunctionKeyword &&
|
|
1294
|
+
!closedParameterSignature.isArrowSignature) {
|
|
1295
|
+
pendingFunctionContexts.push({
|
|
1296
|
+
...closedParameterSignature.context,
|
|
1297
|
+
...closedParameterSignature.owner,
|
|
1298
|
+
parameterDepth: undefined,
|
|
1299
|
+
phase: 'awaiting-body',
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1116
1302
|
afterControlHead = parameters?.control === true;
|
|
1117
1303
|
if (parenDepth > 0) {
|
|
1118
1304
|
parenDepth--;
|
|
@@ -1148,17 +1334,15 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1148
1334
|
const next = skipTrivia(source, i + 1, n);
|
|
1149
1335
|
if (context !== undefined) {
|
|
1150
1336
|
if (source[next] === '{') {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1337
|
+
pendingFunctionContexts.push({
|
|
1338
|
+
...context,
|
|
1339
|
+
parenDepth,
|
|
1340
|
+
bracketDepth,
|
|
1341
|
+
braceDepth: braceContexts.length,
|
|
1342
|
+
frameDepth: frames.length,
|
|
1343
|
+
parameterDepth: undefined,
|
|
1344
|
+
phase: 'awaiting-body',
|
|
1345
|
+
});
|
|
1162
1346
|
}
|
|
1163
1347
|
else {
|
|
1164
1348
|
const parameterOwner = nearestFunctionParameter(parameterContexts);
|
|
@@ -1187,9 +1371,23 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1187
1371
|
}
|
|
1188
1372
|
if (!inType && parameter !== undefined && atLexicalDepth(parameter.depth)) {
|
|
1189
1373
|
if (ch === ',') {
|
|
1374
|
+
const scope = parameter.signature?.context.requireScope;
|
|
1375
|
+
if (scope !== undefined) {
|
|
1376
|
+
parameter.bindingCommas.push({
|
|
1377
|
+
index: tokens.length,
|
|
1378
|
+
bindingStart: parameter.bindingStart,
|
|
1379
|
+
bindingEnd: parameter.bindingEnd,
|
|
1380
|
+
phase: parameter.phase,
|
|
1381
|
+
bindingCount: scope.bindings.length,
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
recordRequireBinding(parameter, tokens.length);
|
|
1385
|
+
parameter.bindingStart = tokens.length + 1;
|
|
1386
|
+
parameter.bindingEnd = undefined;
|
|
1190
1387
|
parameter.phase = 'binding';
|
|
1191
1388
|
}
|
|
1192
1389
|
else if (equalsOperator?.operator === 'assignment') {
|
|
1390
|
+
parameter.bindingEnd ??= tokens.length;
|
|
1193
1391
|
parameter.phase = 'initializer';
|
|
1194
1392
|
}
|
|
1195
1393
|
}
|
|
@@ -1205,9 +1403,10 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1205
1403
|
}
|
|
1206
1404
|
// A callback's statements cannot invalidate a signature outside its body or parameter list.
|
|
1207
1405
|
if (!inType && ch === ';' && pending !== undefined && atLexicalDepth(pending)) {
|
|
1208
|
-
pendingFunctionContexts.pop();
|
|
1406
|
+
closeRequireScope(pendingFunctionContexts.pop()?.requireScope);
|
|
1209
1407
|
}
|
|
1210
1408
|
const previousToken = tokens[tokens.length - 1];
|
|
1409
|
+
const openingSignature = ch === '(' ? parameterContexts[parameterContexts.length - 1]?.signature?.context : undefined;
|
|
1211
1410
|
emit({
|
|
1212
1411
|
kind: 'punct',
|
|
1213
1412
|
value: ch,
|
|
@@ -1221,12 +1420,37 @@ function tokenize(source, jsx = false, module = false) {
|
|
|
1221
1420
|
? { expressionEnd: true }
|
|
1222
1421
|
: {}),
|
|
1223
1422
|
...equalsOperator,
|
|
1423
|
+
...(openingSignature === undefined ? {} : { signatureContext: openingSignature }),
|
|
1224
1424
|
...(matchingOpen !== undefined ? { matchingOpen } : {}),
|
|
1225
1425
|
...(closedClassMemberBoundary || opensClassMemberBoundary || (ch === ';' && insideClassBody())
|
|
1226
1426
|
? { memberSeparator: true }
|
|
1227
1427
|
: {}),
|
|
1228
1428
|
...(closedExpressionBrace ? { expressionEnd: true } : {}),
|
|
1229
1429
|
});
|
|
1430
|
+
if (!inType &&
|
|
1431
|
+
ch === '>' &&
|
|
1432
|
+
requireScopes[requireScopes.length - 1]?.inParameters === true &&
|
|
1433
|
+
!isArrowHead(tokens[tokens.length - 2], i)) {
|
|
1434
|
+
const start = markParameterTypeArguments(tokens, tokens.length);
|
|
1435
|
+
const owner = parameterContexts[parameterContexts.length - 1];
|
|
1436
|
+
const scope = owner?.signature?.context.requireScope;
|
|
1437
|
+
if (start !== undefined && owner !== undefined && scope !== undefined) {
|
|
1438
|
+
// Commas were provisional until the closing > established a type-argument list.
|
|
1439
|
+
// Restore the initializer state as well as its binding ranges; do not re-lex source.
|
|
1440
|
+
let first = owner.bindingCommas.length;
|
|
1441
|
+
while (first > 0 && (owner.bindingCommas[first - 1]?.index ?? -1) >= start) {
|
|
1442
|
+
first--;
|
|
1443
|
+
}
|
|
1444
|
+
const checkpoint = owner.bindingCommas[first];
|
|
1445
|
+
if (checkpoint !== undefined) {
|
|
1446
|
+
owner.bindingStart = checkpoint.bindingStart;
|
|
1447
|
+
owner.bindingEnd = checkpoint.bindingEnd;
|
|
1448
|
+
owner.phase = checkpoint.phase;
|
|
1449
|
+
scope.bindings.length = checkpoint.bindingCount;
|
|
1450
|
+
owner.bindingCommas.length = first;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1230
1454
|
prevKind = 'punct';
|
|
1231
1455
|
prevPunct = ch;
|
|
1232
1456
|
i++;
|
|
@@ -1310,17 +1534,166 @@ function jsxTagIsGeneric(source, i, n) {
|
|
|
1310
1534
|
// because a template literal is a single argument value, so its internal parentheses never shape
|
|
1311
1535
|
// the parameter list. Regex-vs-division tracking mirrors the main tokenizer so a regex default
|
|
1312
1536
|
// (for example `= /a/)`) is skipped instead of read as division.
|
|
1313
|
-
function arrowFollowsParameters(source, open, n) {
|
|
1537
|
+
function arrowFollowsParameters(source, open, n, jsx = false, needsConditionalEnd = false, mode = 'parameters') {
|
|
1314
1538
|
let depth = 0;
|
|
1539
|
+
let bracketDepth = 0;
|
|
1540
|
+
let braceDepth = 0;
|
|
1541
|
+
let conditionalDepth = 0;
|
|
1542
|
+
let assertionOwner;
|
|
1315
1543
|
let prevKind = 'start';
|
|
1316
1544
|
let prevPunct;
|
|
1317
1545
|
let k = open;
|
|
1546
|
+
const jsxFrames = [];
|
|
1547
|
+
const typeArgumentBounds = new Map();
|
|
1318
1548
|
while (k < n) {
|
|
1319
|
-
|
|
1320
|
-
const
|
|
1549
|
+
let ch = source[k];
|
|
1550
|
+
const frame = jsxFrames[jsxFrames.length - 1];
|
|
1551
|
+
if (frame?.kind === 'jsx-text') {
|
|
1552
|
+
if (ch === '{') {
|
|
1553
|
+
jsxFrames.push({ kind: 'jsx-expr', depth: 0, offset: k });
|
|
1554
|
+
prevKind = 'start';
|
|
1555
|
+
prevPunct = undefined;
|
|
1556
|
+
k++;
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
if (ch === '<' && source[k + 1] === '/') {
|
|
1560
|
+
const end = source.indexOf('>', k + 2);
|
|
1561
|
+
if (end < 0) {
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
jsxFrames.pop();
|
|
1565
|
+
prevKind = 'template';
|
|
1566
|
+
prevPunct = undefined;
|
|
1567
|
+
k = end + 1;
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
// Quotes, parentheses, backticks and comment markers in children are inert.
|
|
1571
|
+
if (ch !== '<') {
|
|
1572
|
+
k++;
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
else if (frame?.kind === 'jsx-tag') {
|
|
1577
|
+
if (ch === '<' && skipTrivia(source, frame.nameEnd, n) === k) {
|
|
1578
|
+
k = readJsxTypeArguments(source, k, n, []);
|
|
1579
|
+
}
|
|
1580
|
+
else if (ch === '{') {
|
|
1581
|
+
jsxFrames.push({ kind: 'jsx-expr', depth: 0, offset: k });
|
|
1582
|
+
prevKind = 'start';
|
|
1583
|
+
prevPunct = undefined;
|
|
1584
|
+
k++;
|
|
1585
|
+
}
|
|
1586
|
+
else if (ch === '"' || ch === "'") {
|
|
1587
|
+
k = skipJsxAttributeValue(source, k, n);
|
|
1588
|
+
}
|
|
1589
|
+
else if (ch === '/' && source[k + 1] === '>') {
|
|
1590
|
+
jsxFrames.pop();
|
|
1591
|
+
prevKind = 'template';
|
|
1592
|
+
prevPunct = undefined;
|
|
1593
|
+
k += 2;
|
|
1594
|
+
}
|
|
1595
|
+
else if (ch === '>') {
|
|
1596
|
+
jsxFrames.pop();
|
|
1597
|
+
jsxFrames.push({ kind: 'jsx-text', name: frame.name, offset: frame.offset });
|
|
1598
|
+
k++;
|
|
1599
|
+
}
|
|
1600
|
+
else {
|
|
1601
|
+
k++;
|
|
1602
|
+
}
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
k = skipTrivia(source, k, n);
|
|
1607
|
+
ch = source[k];
|
|
1608
|
+
}
|
|
1321
1609
|
if (ch === undefined) {
|
|
1322
1610
|
break;
|
|
1323
1611
|
}
|
|
1612
|
+
if (jsx &&
|
|
1613
|
+
ch === '<' &&
|
|
1614
|
+
(frame?.kind === 'jsx-text' || isRegexStart(prevKind, prevPunct, false, false, false))) {
|
|
1615
|
+
if (source[k + 1] === '>') {
|
|
1616
|
+
jsxFrames.push({ kind: 'jsx-text', name: null, offset: k });
|
|
1617
|
+
k += 2;
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
if (isIdentifierStartAt(source, k + 1) &&
|
|
1621
|
+
(frame?.kind === 'jsx-text' || !jsxTagIsGeneric(source, k + 1, n))) {
|
|
1622
|
+
const tag = readJsxTagName(source, k + 1, n);
|
|
1623
|
+
jsxFrames.push({ kind: 'jsx-tag', name: tag.name, offset: k, nameEnd: tag.end });
|
|
1624
|
+
k = tag.end;
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
if (frame?.kind === 'jsx-text') {
|
|
1629
|
+
k++;
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
if (frame?.kind === 'jsx-expr') {
|
|
1633
|
+
if (ch === '}') {
|
|
1634
|
+
if (frame.depth === 0) {
|
|
1635
|
+
jsxFrames.pop();
|
|
1636
|
+
prevKind = 'template';
|
|
1637
|
+
prevPunct = undefined;
|
|
1638
|
+
k++;
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
frame.depth--;
|
|
1642
|
+
}
|
|
1643
|
+
else if (ch === '{') {
|
|
1644
|
+
frame.depth++;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
if (mode === 'conditional-body' && ch === '<' && prevKind === 'identifier') {
|
|
1648
|
+
const inAssertion = assertionOwner?.paren === depth &&
|
|
1649
|
+
assertionOwner.bracket === bracketDepth &&
|
|
1650
|
+
assertionOwner.brace === braceDepth &&
|
|
1651
|
+
assertionOwner.frame === jsxFrames.length;
|
|
1652
|
+
const end = expressionTypeArgumentEnd(source, k, n, typeArgumentBounds, inAssertion);
|
|
1653
|
+
if (end !== undefined) {
|
|
1654
|
+
k = end;
|
|
1655
|
+
prevKind = 'identifier';
|
|
1656
|
+
prevPunct = undefined;
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (assertionOwner?.paren === depth &&
|
|
1661
|
+
assertionOwner.bracket === bracketDepth &&
|
|
1662
|
+
assertionOwner.brace === braceDepth &&
|
|
1663
|
+
assertionOwner.frame === jsxFrames.length &&
|
|
1664
|
+
['+', '-', '*', '/', '%', '=', '?', '!'].includes(ch)) {
|
|
1665
|
+
assertionOwner = undefined;
|
|
1666
|
+
}
|
|
1667
|
+
// Reuse the same opaque regions to confirm a typed nested arrow in a ternary.
|
|
1668
|
+
// Colons in JSX, object literals, nested delimiters or inner conditionals do not
|
|
1669
|
+
// close the enclosing conditional. Stop at this expression's own boundary.
|
|
1670
|
+
if (mode === 'conditional-body') {
|
|
1671
|
+
if (jsxFrames.length === 0 && depth === 0 && bracketDepth === 0 && braceDepth === 0) {
|
|
1672
|
+
if (ch === ':') {
|
|
1673
|
+
if (conditionalDepth === 0) {
|
|
1674
|
+
return true;
|
|
1675
|
+
}
|
|
1676
|
+
conditionalDepth--;
|
|
1677
|
+
}
|
|
1678
|
+
else if (ch === '?' &&
|
|
1679
|
+
source[k + 1] !== '?' &&
|
|
1680
|
+
source[k - 1] !== '?' &&
|
|
1681
|
+
source[k + 1] !== '.') {
|
|
1682
|
+
conditionalDepth++;
|
|
1683
|
+
}
|
|
1684
|
+
else if ([';', ',', ')', ']', '}'].includes(ch)) {
|
|
1685
|
+
return false;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
if (ch === '[')
|
|
1689
|
+
bracketDepth++;
|
|
1690
|
+
else if (ch === ']')
|
|
1691
|
+
bracketDepth--;
|
|
1692
|
+
else if (ch === '{')
|
|
1693
|
+
braceDepth++;
|
|
1694
|
+
else if (ch === '}')
|
|
1695
|
+
braceDepth--;
|
|
1696
|
+
}
|
|
1324
1697
|
// String literals: their brackets and slashes are inert text, so skip the whole value.
|
|
1325
1698
|
if (ch === "'" || ch === '"') {
|
|
1326
1699
|
k = readString(source, k).next;
|
|
@@ -1350,14 +1723,30 @@ function arrowFollowsParameters(source, open, n) {
|
|
|
1350
1723
|
depth--;
|
|
1351
1724
|
prevKind = 'punct';
|
|
1352
1725
|
prevPunct = ')';
|
|
1353
|
-
if (depth === 0) {
|
|
1726
|
+
if (depth === 0 && mode === 'parameters') {
|
|
1354
1727
|
const j = skipTrivia(source, k + 1, n);
|
|
1355
|
-
|
|
1356
|
-
|
|
1728
|
+
if (source[j] === '=' && source[j + 1] === '>') {
|
|
1729
|
+
return true;
|
|
1730
|
+
}
|
|
1731
|
+
const arrow = source[j] === ':' ? arrowAfterReturnType(source, j + 1, n) : undefined;
|
|
1732
|
+
return (arrow !== undefined &&
|
|
1733
|
+
(!needsConditionalEnd ||
|
|
1734
|
+
arrowFollowsParameters(source, arrow + 2, n, jsx, false, 'conditional-body')));
|
|
1357
1735
|
}
|
|
1358
1736
|
}
|
|
1359
1737
|
else if (isIdentifierStartAt(source, k)) {
|
|
1360
1738
|
const identifier = readIdentifier(source, k);
|
|
1739
|
+
if (mode === 'conditional-body' &&
|
|
1740
|
+
['as', 'satisfies'].includes(identifier.value) &&
|
|
1741
|
+
prevKind !== 'start' &&
|
|
1742
|
+
!(prevKind === 'punct' && prevPunct === '.')) {
|
|
1743
|
+
assertionOwner = {
|
|
1744
|
+
paren: depth,
|
|
1745
|
+
bracket: bracketDepth,
|
|
1746
|
+
brace: braceDepth,
|
|
1747
|
+
frame: jsxFrames.length,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1361
1750
|
k = identifier.end - 1;
|
|
1362
1751
|
prevKind = 'identifier';
|
|
1363
1752
|
prevPunct = undefined;
|
|
@@ -1370,7 +1759,83 @@ function arrowFollowsParameters(source, open, n) {
|
|
|
1370
1759
|
}
|
|
1371
1760
|
return false;
|
|
1372
1761
|
}
|
|
1373
|
-
|
|
1762
|
+
/** Skip a balanced type-argument list only when its suffix can continue an expression. */
|
|
1763
|
+
function expressionTypeArgumentEnd(source, open, n, bounds, typeAssertion = false) {
|
|
1764
|
+
let end = bounds.get(open);
|
|
1765
|
+
if (end === undefined) {
|
|
1766
|
+
try {
|
|
1767
|
+
end = readJsxTypeArguments(source, open, n, [], true, bounds);
|
|
1768
|
+
}
|
|
1769
|
+
catch (error) {
|
|
1770
|
+
if (error instanceof ModuleSpecifierError) {
|
|
1771
|
+
bounds.set(open, null);
|
|
1772
|
+
return undefined;
|
|
1773
|
+
}
|
|
1774
|
+
throw error;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
if (end === null)
|
|
1778
|
+
return undefined;
|
|
1779
|
+
const next = skipTrivia(source, end, n);
|
|
1780
|
+
const ch = source[next];
|
|
1781
|
+
if (!typeAssertion)
|
|
1782
|
+
return ch === '(' ? end : undefined;
|
|
1783
|
+
if (isIdentifierStartAt(source, next) || isUnicodeEscapeStart(source, next)) {
|
|
1784
|
+
return ['as', 'satisfies', 'in', 'instanceof'].includes(readIdentifier(source, next).value)
|
|
1785
|
+
? end
|
|
1786
|
+
: undefined;
|
|
1787
|
+
}
|
|
1788
|
+
return ch !== undefined && (isNumberStart(ch) || ch === '"' || ch === "'") ? undefined : end;
|
|
1789
|
+
}
|
|
1790
|
+
/** A newline computed method head ends a complete bodyless member return annotation. */
|
|
1791
|
+
function computedMethodFollows(source, start, n) {
|
|
1792
|
+
let depth = 0;
|
|
1793
|
+
let index = start;
|
|
1794
|
+
let previous = 'start';
|
|
1795
|
+
let punct;
|
|
1796
|
+
while (index < n) {
|
|
1797
|
+
index = skipTrivia(source, index, n);
|
|
1798
|
+
const ch = source[index];
|
|
1799
|
+
if (ch === undefined) {
|
|
1800
|
+
break;
|
|
1801
|
+
}
|
|
1802
|
+
if (ch === "'" || ch === '"') {
|
|
1803
|
+
index = readString(source, index).next;
|
|
1804
|
+
previous = 'string';
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
if (ch === '`') {
|
|
1808
|
+
index = skipTemplateLiteral(source, index);
|
|
1809
|
+
previous = 'template';
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
if (ch === '/' && isRegexStart(previous, punct, false, false, false)) {
|
|
1813
|
+
index = skipRegex(source, index);
|
|
1814
|
+
previous = 'regex';
|
|
1815
|
+
continue;
|
|
1816
|
+
}
|
|
1817
|
+
if (ch === '[') {
|
|
1818
|
+
depth++;
|
|
1819
|
+
}
|
|
1820
|
+
else if (ch === ']') {
|
|
1821
|
+
depth--;
|
|
1822
|
+
if (depth === 0) {
|
|
1823
|
+
return source[skipTrivia(source, index + 1, n)] === '(';
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
if (isIdentifierStartAt(source, index) || isUnicodeEscapeStart(source, index)) {
|
|
1827
|
+
index = readIdentifier(source, index).end;
|
|
1828
|
+
previous = 'identifier';
|
|
1829
|
+
}
|
|
1830
|
+
else {
|
|
1831
|
+
previous = isNumberStart(ch) ? 'number' : 'punct';
|
|
1832
|
+
punct = ch;
|
|
1833
|
+
index++;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return false;
|
|
1837
|
+
}
|
|
1838
|
+
function arrowAfterReturnType(source, start, n) {
|
|
1374
1839
|
let parenDepth = 0;
|
|
1375
1840
|
let bracketDepth = 0;
|
|
1376
1841
|
let braceDepth = 0;
|
|
@@ -1380,7 +1845,7 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1380
1845
|
index = skipTrivia(source, index, n);
|
|
1381
1846
|
const ch = source[index];
|
|
1382
1847
|
if (ch === undefined) {
|
|
1383
|
-
return
|
|
1848
|
+
return undefined;
|
|
1384
1849
|
}
|
|
1385
1850
|
if (ch === "'" || ch === '"') {
|
|
1386
1851
|
index = readString(source, index).next;
|
|
@@ -1392,17 +1857,34 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1392
1857
|
}
|
|
1393
1858
|
if (ch === '=' && source[index + 1] === '>') {
|
|
1394
1859
|
if (parenDepth === 0 && bracketDepth === 0 && braceDepth === 0 && angleDepth === 0) {
|
|
1395
|
-
return
|
|
1860
|
+
return index;
|
|
1396
1861
|
}
|
|
1397
1862
|
index += 2;
|
|
1398
1863
|
continue;
|
|
1399
1864
|
}
|
|
1865
|
+
if (parenDepth === 0 &&
|
|
1866
|
+
bracketDepth === 0 &&
|
|
1867
|
+
braceDepth === 0 &&
|
|
1868
|
+
angleDepth === 0 &&
|
|
1869
|
+
(isIdentifierStartAt(source, index) || isUnicodeEscapeStart(source, index))) {
|
|
1870
|
+
const identifier = readIdentifier(source, index);
|
|
1871
|
+
const next = skipTrivia(source, identifier.end, n);
|
|
1872
|
+
// An operand followed by a call is runtime code (not a return type). In particular,
|
|
1873
|
+
// a conditional's `: async () =>` must not claim the preceding sequence expression.
|
|
1874
|
+
if (source[next] === '(' &&
|
|
1875
|
+
identifier.value !== 'import' &&
|
|
1876
|
+
!TYPE_OPERAND_KEYWORDS.has(identifier.value)) {
|
|
1877
|
+
return undefined;
|
|
1878
|
+
}
|
|
1879
|
+
index = identifier.end;
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1400
1882
|
if (ch === '(') {
|
|
1401
1883
|
parenDepth++;
|
|
1402
1884
|
}
|
|
1403
1885
|
else if (ch === ')') {
|
|
1404
1886
|
if (parenDepth === 0) {
|
|
1405
|
-
return
|
|
1887
|
+
return undefined;
|
|
1406
1888
|
}
|
|
1407
1889
|
parenDepth--;
|
|
1408
1890
|
}
|
|
@@ -1411,7 +1893,7 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1411
1893
|
}
|
|
1412
1894
|
else if (ch === ']') {
|
|
1413
1895
|
if (bracketDepth === 0) {
|
|
1414
|
-
return
|
|
1896
|
+
return undefined;
|
|
1415
1897
|
}
|
|
1416
1898
|
bracketDepth--;
|
|
1417
1899
|
}
|
|
@@ -1420,7 +1902,7 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1420
1902
|
}
|
|
1421
1903
|
else if (ch === '}') {
|
|
1422
1904
|
if (braceDepth === 0) {
|
|
1423
|
-
return
|
|
1905
|
+
return undefined;
|
|
1424
1906
|
}
|
|
1425
1907
|
braceDepth--;
|
|
1426
1908
|
}
|
|
@@ -1429,7 +1911,7 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1429
1911
|
}
|
|
1430
1912
|
else if (ch === '>') {
|
|
1431
1913
|
if (angleDepth === 0) {
|
|
1432
|
-
return
|
|
1914
|
+
return undefined;
|
|
1433
1915
|
}
|
|
1434
1916
|
angleDepth--;
|
|
1435
1917
|
}
|
|
@@ -1438,15 +1920,15 @@ function arrowFollowsReturnType(source, start, n) {
|
|
|
1438
1920
|
braceDepth === 0 &&
|
|
1439
1921
|
angleDepth === 0 &&
|
|
1440
1922
|
(ch === ';' || ch === ',')) {
|
|
1441
|
-
return
|
|
1923
|
+
return undefined;
|
|
1442
1924
|
}
|
|
1443
1925
|
index++;
|
|
1444
1926
|
}
|
|
1445
|
-
return
|
|
1927
|
+
return undefined;
|
|
1446
1928
|
}
|
|
1447
|
-
function arrowFollowsParametersSafely(source, open, n) {
|
|
1929
|
+
function arrowFollowsParametersSafely(source, open, n, jsx = false, needsConditionalEnd = false) {
|
|
1448
1930
|
try {
|
|
1449
|
-
return arrowFollowsParameters(source, open, n);
|
|
1931
|
+
return arrowFollowsParameters(source, open, n, jsx, needsConditionalEnd);
|
|
1450
1932
|
}
|
|
1451
1933
|
catch (error) {
|
|
1452
1934
|
if (error instanceof ModuleSpecifierError) {
|
|
@@ -1504,68 +1986,184 @@ function skipTemplateLiteral(source, start) {
|
|
|
1504
1986
|
}
|
|
1505
1987
|
throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', start);
|
|
1506
1988
|
}
|
|
1989
|
+
/** Probe template types with the same type boundaries as their interpolated operands. */
|
|
1990
|
+
function skipTemplateType(source, start, n, bounds) {
|
|
1991
|
+
let i = start + 1;
|
|
1992
|
+
while (i < n) {
|
|
1993
|
+
if (source[i] === '\\')
|
|
1994
|
+
i += 2;
|
|
1995
|
+
else if (source[i] === '`')
|
|
1996
|
+
return i + 1;
|
|
1997
|
+
else if (source[i] === '$' && source[i + 1] === '{') {
|
|
1998
|
+
i = readJsxTypeArguments(source, i + 1, n, [], true, bounds, '}');
|
|
1999
|
+
}
|
|
2000
|
+
else
|
|
2001
|
+
i++;
|
|
2002
|
+
}
|
|
2003
|
+
throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', start);
|
|
2004
|
+
}
|
|
2005
|
+
/** Only a complete type followed by a line break can begin another bodyless type member. */
|
|
2006
|
+
function typeMemberFollowsLineBreak(tokens, lineBreak) {
|
|
2007
|
+
const complete = (token) => {
|
|
2008
|
+
if (token === undefined)
|
|
2009
|
+
return false;
|
|
2010
|
+
if (token.kind === 'identifier')
|
|
2011
|
+
return !TYPE_OPERAND_KEYWORDS.has(token.value);
|
|
2012
|
+
return (token.kind === 'string' ||
|
|
2013
|
+
token.kind === 'number' ||
|
|
2014
|
+
token.kind === 'template' ||
|
|
2015
|
+
(token.kind === 'punct' &&
|
|
2016
|
+
([')', ']', '}', '>'].includes(token.value) || /^[0-9]$/.test(token.value))));
|
|
2017
|
+
};
|
|
2018
|
+
let index = tokens.length - 1;
|
|
2019
|
+
if (lineBreak && complete(tokens[index]))
|
|
2020
|
+
return true;
|
|
2021
|
+
// The line break may precede a modifier rather than the method name (e.g. get b()).
|
|
2022
|
+
while (index >= 0) {
|
|
2023
|
+
const token = tokens[index];
|
|
2024
|
+
if (token?.kind !== 'identifier' || !MEMBER_MODIFIERS.has(token.value))
|
|
2025
|
+
break;
|
|
2026
|
+
if (token.lineBreakBefore === true && complete(tokens[index - 1]))
|
|
2027
|
+
return true;
|
|
2028
|
+
index--;
|
|
2029
|
+
}
|
|
2030
|
+
return false;
|
|
2031
|
+
}
|
|
1507
2032
|
/**
|
|
1508
2033
|
* Lex only the boundaries of a component's type arguments. These tokens preserve literal imports
|
|
1509
2034
|
* without changing the surrounding JavaScript function/brace state or treating type braces as JSX.
|
|
1510
2035
|
*/
|
|
1511
|
-
function readJsxTypeArguments(source, open, n, tokens) {
|
|
1512
|
-
const closers = [
|
|
2036
|
+
function readJsxTypeArguments(source, open, n, tokens, expressionProbe = false, bounds, rootClose = '>') {
|
|
2037
|
+
const closers = [rootClose];
|
|
2038
|
+
const opens = [open];
|
|
2039
|
+
const conditions = [0];
|
|
1513
2040
|
let i = open + 1;
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
2041
|
+
try {
|
|
2042
|
+
while (i < n) {
|
|
2043
|
+
const trivia = readTrivia(source, i, n);
|
|
2044
|
+
i = trivia.end;
|
|
2045
|
+
const ch = source[i];
|
|
2046
|
+
if (ch === undefined) {
|
|
2047
|
+
break;
|
|
2048
|
+
}
|
|
2049
|
+
// A speculative expression probe must not borrow a > from a later statement
|
|
2050
|
+
// or conditional branch. Colons in conditional types still belong to the list.
|
|
2051
|
+
if (expressionProbe &&
|
|
2052
|
+
(closers[closers.length - 1] === '>' || (rootClose === '}' && closers.length === 1))) {
|
|
2053
|
+
const level = conditions.length - 1;
|
|
2054
|
+
if (ch === '?')
|
|
2055
|
+
conditions[level] = (conditions[level] ?? 0) + 1;
|
|
2056
|
+
else if (ch === ':' && (conditions[level] ?? 0) > 0)
|
|
2057
|
+
conditions[level] = (conditions[level] ?? 0) - 1;
|
|
2058
|
+
else if (ch === ';' ||
|
|
2059
|
+
ch === ':' ||
|
|
2060
|
+
(ch === '=' && source[i + 1] !== '>') ||
|
|
2061
|
+
([')', ']', '}'].includes(ch) && ch !== closers[closers.length - 1])) {
|
|
2062
|
+
throw new ModuleSpecifierError('Invalid type argument boundary', 'invalid-source', i);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
if (expressionProbe && ch === '/') {
|
|
2066
|
+
throw new ModuleSpecifierError('Runtime slash in type argument probe', 'invalid-source', i);
|
|
2067
|
+
}
|
|
2068
|
+
if (ch === "'" || ch === '"') {
|
|
2069
|
+
const callee = tokens[tokens.length - 2];
|
|
2070
|
+
if (expressionProbe &&
|
|
2071
|
+
isPunct(tokens[tokens.length - 1], '(') &&
|
|
2072
|
+
callee?.kind === 'identifier' &&
|
|
2073
|
+
callee.value !== 'import' &&
|
|
2074
|
+
!TYPE_OPERAND_KEYWORDS.has(callee.value)) {
|
|
2075
|
+
throw new ModuleSpecifierError('Literal method parameter in type argument probe', 'invalid-source', i);
|
|
2076
|
+
}
|
|
2077
|
+
const { value, next } = readString(source, i);
|
|
2078
|
+
tokens.push({
|
|
2079
|
+
kind: 'string',
|
|
2080
|
+
value,
|
|
2081
|
+
offset: i,
|
|
2082
|
+
endOffset: next,
|
|
2083
|
+
lineBreakBefore: trivia.lineBreak,
|
|
2084
|
+
});
|
|
2085
|
+
i = next;
|
|
2086
|
+
continue;
|
|
2087
|
+
}
|
|
2088
|
+
if (ch === '`') {
|
|
2089
|
+
if (expressionProbe) {
|
|
2090
|
+
const end = skipTemplateType(source, i, n, bounds);
|
|
2091
|
+
tokens.push({
|
|
2092
|
+
kind: 'template',
|
|
2093
|
+
offset: i,
|
|
2094
|
+
endOffset: end,
|
|
2095
|
+
lineBreakBefore: trivia.lineBreak,
|
|
2096
|
+
});
|
|
2097
|
+
i = end;
|
|
2098
|
+
continue;
|
|
2099
|
+
}
|
|
2100
|
+
throw new ModuleSpecifierError('Template literal types in JSX type arguments are not supported', 'invalid-source', i);
|
|
2101
|
+
}
|
|
2102
|
+
if (isIdentifierStartAt(source, i)) {
|
|
2103
|
+
const start = i;
|
|
2104
|
+
const identifier = readIdentifier(source, i);
|
|
2105
|
+
const next = skipTrivia(source, identifier.end, n);
|
|
2106
|
+
if (expressionProbe &&
|
|
2107
|
+
source[next] === '(' &&
|
|
2108
|
+
identifier.value !== 'import' &&
|
|
2109
|
+
!TYPE_OPERAND_KEYWORDS.has(identifier.value) &&
|
|
2110
|
+
!(closers[closers.length - 1] === '}' &&
|
|
2111
|
+
closers.length > 1 &&
|
|
2112
|
+
(memberDeclarationHead(tokens, tokens.length, false) !== undefined ||
|
|
2113
|
+
typeMemberFollowsLineBreak(tokens, trivia.lineBreak)))) {
|
|
2114
|
+
throw new ModuleSpecifierError('Runtime call in type argument probe', 'invalid-source', i);
|
|
2115
|
+
}
|
|
2116
|
+
i = identifier.end;
|
|
2117
|
+
tokens.push({
|
|
2118
|
+
kind: 'identifier',
|
|
2119
|
+
value: identifier.value,
|
|
2120
|
+
offset: start,
|
|
2121
|
+
endOffset: i,
|
|
2122
|
+
lineBreakBefore: trivia.lineBreak,
|
|
2123
|
+
});
|
|
2124
|
+
continue;
|
|
2125
|
+
}
|
|
2126
|
+
if (expressionProbe && ch === '{' && isPunct(tokens[tokens.length - 1], ')')) {
|
|
2127
|
+
throw new ModuleSpecifierError('Runtime body in type argument probe', 'invalid-source', i);
|
|
2128
|
+
}
|
|
2129
|
+
if (ch === '<' || ch === '(' || ch === '{' || ch === '[') {
|
|
2130
|
+
closers.push(ch === '<' ? '>' : ch === '(' ? ')' : ch === '{' ? '}' : ']');
|
|
2131
|
+
opens.push(i);
|
|
2132
|
+
conditions.push(0);
|
|
2133
|
+
}
|
|
2134
|
+
else if (ch === closers[closers.length - 1] && !(ch === '>' && source[i - 1] === '=')) {
|
|
2135
|
+
closers.pop();
|
|
2136
|
+
const opened = opens.pop();
|
|
2137
|
+
conditions.pop();
|
|
2138
|
+
if (expressionProbe && ch === '>' && opened !== undefined)
|
|
2139
|
+
bounds?.set(opened, i + 1);
|
|
2140
|
+
if (closers.length === 0) {
|
|
2141
|
+
return i + 1;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
1523
2144
|
tokens.push({
|
|
1524
|
-
kind: '
|
|
1525
|
-
value,
|
|
2145
|
+
kind: 'punct',
|
|
2146
|
+
value: ch,
|
|
1526
2147
|
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,
|
|
2148
|
+
endOffset: i + 1,
|
|
1545
2149
|
lineBreakBefore: trivia.lineBreak,
|
|
2150
|
+
...(ch === '=' ? classifyEquals(source, i, true) : {}),
|
|
1546
2151
|
});
|
|
1547
|
-
|
|
1548
|
-
}
|
|
1549
|
-
if (ch === '<' || ch === '(' || ch === '{' || ch === '[') {
|
|
1550
|
-
closers.push(ch === '<' ? '>' : ch === '(' ? ')' : ch === '{' ? '}' : ']');
|
|
2152
|
+
i++;
|
|
1551
2153
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
2154
|
+
throw new ModuleSpecifierError('Unterminated JSX type arguments', 'invalid-source', open);
|
|
2155
|
+
}
|
|
2156
|
+
catch (error) {
|
|
2157
|
+
if (expressionProbe && bounds !== undefined) {
|
|
2158
|
+
// One failed walk classifies every still-open angle encountered along the way.
|
|
2159
|
+
for (let level = 0; level < closers.length; level++) {
|
|
2160
|
+
const opened = opens[level];
|
|
2161
|
+
if (closers[level] === '>' && opened !== undefined)
|
|
2162
|
+
bounds.set(opened, null);
|
|
1556
2163
|
}
|
|
1557
2164
|
}
|
|
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++;
|
|
2165
|
+
throw error;
|
|
1567
2166
|
}
|
|
1568
|
-
throw new ModuleSpecifierError('Unterminated JSX type arguments', 'invalid-source', open);
|
|
1569
2167
|
}
|
|
1570
2168
|
/**
|
|
1571
2169
|
* Whether the source between two offsets contains a line terminator.
|
|
@@ -1634,7 +2232,7 @@ const TYPE_OPERAND_KEYWORDS = new Set([
|
|
|
1634
2232
|
* (`(): (x: number) => void => {}`); once the return type is over, the next significant token is the
|
|
1635
2233
|
* body (`{`), a statement terminator or a delimiter that closes the surrounding code.
|
|
1636
2234
|
*/
|
|
1637
|
-
function functionTypeArrowContinues(source, arrowEnd, n) {
|
|
2235
|
+
function functionTypeArrowContinues(source, arrowEnd, n, conditionalEnd = false) {
|
|
1638
2236
|
let depth = 0;
|
|
1639
2237
|
for (let i = arrowEnd + 1; i < n; i++) {
|
|
1640
2238
|
const ch = source[i];
|
|
@@ -1651,7 +2249,7 @@ function functionTypeArrowContinues(source, arrowEnd, n) {
|
|
|
1651
2249
|
depth--;
|
|
1652
2250
|
}
|
|
1653
2251
|
else if (depth === 0) {
|
|
1654
|
-
if (ch === '{' || ch === ';' || ch === ',') {
|
|
2252
|
+
if (ch === '{' || ch === ';' || ch === ',' || (conditionalEnd && ch === ':')) {
|
|
1655
2253
|
return false;
|
|
1656
2254
|
}
|
|
1657
2255
|
if (ch === '=' && source[i + 1] === '>') {
|
|
@@ -1661,6 +2259,43 @@ function functionTypeArrowContinues(source, arrowEnd, n) {
|
|
|
1661
2259
|
}
|
|
1662
2260
|
return false;
|
|
1663
2261
|
}
|
|
2262
|
+
/** Preserve type-argument identity while inspecting a parameter binding pattern. */
|
|
2263
|
+
function markParameterTypeArguments(tokens, openIndex) {
|
|
2264
|
+
if (!isPunct(tokens[openIndex - 1], '>')) {
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
const start = signatureNameIndex(tokens, openIndex) + 1;
|
|
2268
|
+
if (!isPunct(tokens[start], '<')) {
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
// A real JS destructuring entry between comparison operators needs a default or property
|
|
2272
|
+
// separator. Do not mistake that binding for a generic argument. Nested type members may
|
|
2273
|
+
// contain their own separators inside balanced (), [] and {}.
|
|
2274
|
+
let depth = 0;
|
|
2275
|
+
for (let index = start + 1; index < openIndex - 1; index++) {
|
|
2276
|
+
const token = tokens[index];
|
|
2277
|
+
if (token?.kind !== 'punct') {
|
|
2278
|
+
continue;
|
|
2279
|
+
}
|
|
2280
|
+
if (['(', '[', '{'].includes(token.value)) {
|
|
2281
|
+
depth++;
|
|
2282
|
+
}
|
|
2283
|
+
else if ([')', ']', '}'].includes(token.value)) {
|
|
2284
|
+
depth--;
|
|
2285
|
+
}
|
|
2286
|
+
else if (depth === 0 &&
|
|
2287
|
+
(token.operator === 'assignment' || token.value === ':' || token.value === ';')) {
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
for (let index = start; index < openIndex; index++) {
|
|
2292
|
+
const token = tokens[index];
|
|
2293
|
+
if (token !== undefined) {
|
|
2294
|
+
tokens[index] = { ...token, parameterType: true };
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return start;
|
|
2298
|
+
}
|
|
1664
2299
|
/**
|
|
1665
2300
|
* Index of the token that names a `(` parameter list. A generic signature keeps its type-parameter
|
|
1666
2301
|
* list between the name and the `(` (`async function f<T>(): T {}`, `async <T,>(value: T) => {}`,
|
|
@@ -2013,11 +2648,22 @@ function inferArrowContext(tokens, arrowIndex) {
|
|
|
2013
2648
|
const async = markerToken?.kind === 'identifier' &&
|
|
2014
2649
|
markerToken.value === 'async' &&
|
|
2015
2650
|
asyncModifierIsContinuous(tokens, markerIndex, parametersIndex);
|
|
2016
|
-
return {
|
|
2651
|
+
return (parametersStart.signatureContext ?? {
|
|
2017
2652
|
async,
|
|
2018
2653
|
generator: false,
|
|
2019
2654
|
isExpression: false,
|
|
2020
|
-
};
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
/** Parameter lists can start at expression/statement boundaries, never after a call's name. */
|
|
2658
|
+
function arrowParameterPrefix(token) {
|
|
2659
|
+
if (token === undefined) {
|
|
2660
|
+
return true;
|
|
2661
|
+
}
|
|
2662
|
+
if (token.kind === 'punct') {
|
|
2663
|
+
return (EXPRESSION_BEFORE_BRACE.has(token.value) || [':', '>', ';', '{', '/'].includes(token.value));
|
|
2664
|
+
}
|
|
2665
|
+
return (token.kind === 'identifier' &&
|
|
2666
|
+
['return', 'throw', 'yield', 'await', 'void', 'typeof', 'delete', 'default'].includes(token.value));
|
|
2021
2667
|
}
|
|
2022
2668
|
function parameterSignatureContext(tokens, pending, insideMethodContainer, insideClassBody, lineBreakBefore, memberStart) {
|
|
2023
2669
|
const openIndex = tokens.length;
|
|
@@ -2043,12 +2689,7 @@ function parameterSignatureContext(tokens, pending, insideMethodContainer, insid
|
|
|
2043
2689
|
}
|
|
2044
2690
|
const marker = tokens[nameIndex - 1];
|
|
2045
2691
|
// 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));
|
|
2692
|
+
const startsArrowParameters = arrowParameterPrefix(name) || (isIdentifier(name, 'async') && arrowParameterPrefix(marker));
|
|
2052
2693
|
if (startsArrowParameters) {
|
|
2053
2694
|
const asyncToken = name?.kind === 'identifier' && name.value === 'async' ? name : undefined;
|
|
2054
2695
|
const async = asyncToken !== undefined &&
|
|
@@ -2421,13 +3062,13 @@ const BLOCK_AFTER_KEYWORD = new Set(['else', 'do']);
|
|
|
2421
3062
|
// so a slash after its body `{}` divides) rather than statement position (a declaration, so a slash
|
|
2422
3063
|
// after its body may start a regex). The previous significant token must expect an expression on
|
|
2423
3064
|
// its right: an expression-starting punctuation, an expression keyword, or a regex.
|
|
2424
|
-
function functionIsExpressionPosition(prevKind, prevPunct,
|
|
3065
|
+
function functionIsExpressionPosition(prevKind, prevPunct, colonIsExpression, expressionExpected) {
|
|
3066
|
+
if (expressionExpected) {
|
|
3067
|
+
return true;
|
|
3068
|
+
}
|
|
2425
3069
|
if (prevKind === 'punct') {
|
|
2426
3070
|
return prevPunct === ':' ? colonIsExpression : EXPRESSION_BEFORE_BRACE.has(prevPunct ?? '');
|
|
2427
3071
|
}
|
|
2428
|
-
if (prevKind === 'identifier') {
|
|
2429
|
-
return REGEX_PRECEDING_KEYWORDS.has(previousIdentifier ?? '');
|
|
2430
|
-
}
|
|
2431
3072
|
return prevKind === 'regex';
|
|
2432
3073
|
}
|
|
2433
3074
|
/**
|
|
@@ -2500,6 +3141,29 @@ function isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword, afterControl
|
|
|
2500
3141
|
function interpret(tokens) {
|
|
2501
3142
|
const occurrences = [];
|
|
2502
3143
|
const n = tokens.length;
|
|
3144
|
+
let closingDelimiters;
|
|
3145
|
+
const closingDelimiter = (openIndex) => {
|
|
3146
|
+
closingDelimiters ??= indexClosingDelimiters(tokens);
|
|
3147
|
+
return closingDelimiters.get(openIndex);
|
|
3148
|
+
};
|
|
3149
|
+
const resolvedRequireScopes = new Map();
|
|
3150
|
+
const requireIsShadowed = (scope) => {
|
|
3151
|
+
const path = [];
|
|
3152
|
+
let current = scope;
|
|
3153
|
+
while (current !== undefined && !resolvedRequireScopes.has(current)) {
|
|
3154
|
+
path.push(current);
|
|
3155
|
+
current = current.parent;
|
|
3156
|
+
}
|
|
3157
|
+
let shadowed = current === undefined ? false : (resolvedRequireScopes.get(current) ?? false);
|
|
3158
|
+
for (let index = path.length - 1; index >= 0; index--) {
|
|
3159
|
+
const owner = path[index];
|
|
3160
|
+
if (owner !== undefined) {
|
|
3161
|
+
shadowed ||= owner.bindings.some((range) => bindingHasRequire(tokens, range.start, range.end, closingDelimiter));
|
|
3162
|
+
resolvedRequireScopes.set(owner, shadowed);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
return shadowed;
|
|
3166
|
+
};
|
|
2503
3167
|
let i = 0;
|
|
2504
3168
|
while (i < n) {
|
|
2505
3169
|
const token = tokens[i];
|
|
@@ -2508,7 +3172,7 @@ function interpret(tokens) {
|
|
|
2508
3172
|
continue;
|
|
2509
3173
|
}
|
|
2510
3174
|
if (token.value === 'import') {
|
|
2511
|
-
i = handleImport(tokens, i, occurrences);
|
|
3175
|
+
i = handleImport(tokens, i, occurrences, closingDelimiter);
|
|
2512
3176
|
continue;
|
|
2513
3177
|
}
|
|
2514
3178
|
if (token.value === 'export') {
|
|
@@ -2516,6 +3180,10 @@ function interpret(tokens) {
|
|
|
2516
3180
|
continue;
|
|
2517
3181
|
}
|
|
2518
3182
|
if (token.value === 'require') {
|
|
3183
|
+
if (token.requireScope !== undefined && requireIsShadowed(token.requireScope)) {
|
|
3184
|
+
i++;
|
|
3185
|
+
continue;
|
|
3186
|
+
}
|
|
2519
3187
|
i = handleRequire(tokens, i, occurrences);
|
|
2520
3188
|
continue;
|
|
2521
3189
|
}
|
|
@@ -2529,7 +3197,7 @@ function interpret(tokens) {
|
|
|
2529
3197
|
* - `obj.import(...)` is ignored (a member call, preceded by `.`).
|
|
2530
3198
|
* - `import.meta` is ignored (treated as `import` followed by `.`).
|
|
2531
3199
|
* - `import 'pkg'` and `import ... from 'pkg'` are `import`.
|
|
2532
|
-
* -
|
|
3200
|
+
* - Dynamic import accepts a literal source with optional bounded object-literal options.
|
|
2533
3201
|
*/
|
|
2534
3202
|
/**
|
|
2535
3203
|
* Whether the identifier at `i` is a property name (`obj.require`), which is never the global
|
|
@@ -2551,7 +3219,7 @@ function isMemberName(tokens, i) {
|
|
|
2551
3219
|
third.value === '.';
|
|
2552
3220
|
return !isSpread;
|
|
2553
3221
|
}
|
|
2554
|
-
function handleImport(tokens, i, occurrences) {
|
|
3222
|
+
function handleImport(tokens, i, occurrences, closingDelimiter) {
|
|
2555
3223
|
const next = tokens[i + 1];
|
|
2556
3224
|
// `obj.import(...)`: a member call, not the import keyword.
|
|
2557
3225
|
if (isMemberName(tokens, i)) {
|
|
@@ -2566,79 +3234,118 @@ function handleImport(tokens, i, occurrences) {
|
|
|
2566
3234
|
occurrences.push({ specifier: next.value, kind: 'import' });
|
|
2567
3235
|
return i + 2;
|
|
2568
3236
|
}
|
|
2569
|
-
//
|
|
3237
|
+
// Keep literal specifiers with sole-argument or bounded object-literal options forms.
|
|
2570
3238
|
if (next?.kind === 'punct' && next.value === '(') {
|
|
2571
|
-
const specifier =
|
|
3239
|
+
const specifier = dynamicImportSpecifier(tokens, i + 2, closingDelimiter);
|
|
2572
3240
|
if (specifier !== undefined) {
|
|
2573
3241
|
occurrences.push({ specifier, kind: 'dynamic-import' });
|
|
2574
3242
|
return i + 3;
|
|
2575
3243
|
}
|
|
2576
3244
|
return i + 1;
|
|
2577
3245
|
}
|
|
2578
|
-
// Static `import ... from 'pkg'`:
|
|
3246
|
+
// Static `import ... from 'pkg'`: consume only the supported clause.
|
|
2579
3247
|
return scanImportSource(tokens, i, occurrences, 'import');
|
|
2580
3248
|
}
|
|
2581
3249
|
/**
|
|
2582
3250
|
* Handle an `export` identifier. `export ... from 'pkg'` is `export-from`.
|
|
2583
3251
|
*/
|
|
2584
3252
|
function handleExport(tokens, i, occurrences) {
|
|
3253
|
+
// Member names share the same boundary rule as import and require.
|
|
3254
|
+
if (isMemberName(tokens, i)) {
|
|
3255
|
+
return i + 1;
|
|
3256
|
+
}
|
|
2585
3257
|
return scanImportSource(tokens, i, occurrences, 'export-from');
|
|
2586
3258
|
}
|
|
3259
|
+
/** Read only the supported clause; unrelated statements cannot supply its source. */
|
|
3260
|
+
function scanImportSource(tokens, i, occurrences, kind) {
|
|
3261
|
+
const sourceIndex = kind === 'export-from' ? exportSourceIndex(tokens, i) : importSourceIndex(tokens, i);
|
|
3262
|
+
const source = sourceIndex === undefined ? undefined : tokens[sourceIndex];
|
|
3263
|
+
if (source?.kind === 'string' && sourceIndex !== undefined) {
|
|
3264
|
+
occurrences.push({ specifier: source.value, kind });
|
|
3265
|
+
return sourceIndex + 1;
|
|
3266
|
+
}
|
|
3267
|
+
return i + 1;
|
|
3268
|
+
}
|
|
2587
3269
|
/**
|
|
2588
|
-
*
|
|
2589
|
-
*
|
|
2590
|
-
*
|
|
2591
|
-
* current statement (e.g. `import { export as v } from 'pkg'`).
|
|
3270
|
+
* Consume a default binding and/or a namespace/named clause, then require `from` + literal.
|
|
3271
|
+
* Each property lookalike fails locally. Named clauses accept only specifiers, never arbitrary
|
|
3272
|
+
* brace contents, so failed candidates cannot repeatedly search an unrelated suffix.
|
|
2592
3273
|
*/
|
|
2593
|
-
function
|
|
2594
|
-
|
|
2595
|
-
if (
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
3274
|
+
function importSourceIndex(tokens, importIndex) {
|
|
3275
|
+
let index = importIndex + 1;
|
|
3276
|
+
if (isIdentifier(tokens[index], 'type') &&
|
|
3277
|
+
(isPunct(tokens[index + 1], '{') ||
|
|
3278
|
+
isPunct(tokens[index + 1], '*') ||
|
|
3279
|
+
(tokens[index + 1]?.kind === 'identifier' &&
|
|
3280
|
+
(!isIdentifier(tokens[index + 1], 'from') || tokens[index + 2]?.kind !== 'string') &&
|
|
3281
|
+
(!isIdentifier(tokens[index + 1], 'as') || isIdentifier(tokens[index + 2], 'from'))))) {
|
|
3282
|
+
index++;
|
|
3283
|
+
}
|
|
3284
|
+
if (tokens[index]?.kind === 'identifier') {
|
|
3285
|
+
index++;
|
|
3286
|
+
// Preserve the historical synthetic default-alias form supported by this scanner.
|
|
3287
|
+
if (isIdentifier(tokens[index], 'as') && tokens[index + 1]?.kind === 'identifier') {
|
|
3288
|
+
index += 2;
|
|
3289
|
+
}
|
|
3290
|
+
if (isPunct(tokens[index], ',')) {
|
|
3291
|
+
index++;
|
|
2599
3292
|
}
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
occurrences.push({ specifier: source.value, kind });
|
|
2603
|
-
return sourceIndex + 1;
|
|
3293
|
+
else {
|
|
3294
|
+
return importFromSourceIndex(tokens, index);
|
|
2604
3295
|
}
|
|
2605
|
-
return i + 1;
|
|
2606
3296
|
}
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
3297
|
+
if (isPunct(tokens[index], '*')) {
|
|
3298
|
+
if (!isIdentifier(tokens[index + 1], 'as') || tokens[index + 2]?.kind !== 'identifier') {
|
|
3299
|
+
return undefined;
|
|
3300
|
+
}
|
|
3301
|
+
index += 3;
|
|
3302
|
+
}
|
|
3303
|
+
else if (isPunct(tokens[index], '{')) {
|
|
3304
|
+
index++;
|
|
3305
|
+
while (!isPunct(tokens[index], '}')) {
|
|
3306
|
+
// `type as T` imports the value `type`, but `type as` imports the type `as`.
|
|
3307
|
+
// A second `as` plus an alias distinguishes `type as as Alias` from `type as as`.
|
|
3308
|
+
if (isIdentifier(tokens[index], 'type') &&
|
|
3309
|
+
tokens[index + 1]?.kind === 'identifier' &&
|
|
3310
|
+
(!isIdentifier(tokens[index + 1], 'as') ||
|
|
3311
|
+
isPunct(tokens[index + 2], ',') ||
|
|
3312
|
+
isPunct(tokens[index + 2], '}') ||
|
|
3313
|
+
(isIdentifier(tokens[index + 2], 'as') && tokens[index + 3]?.kind === 'identifier'))) {
|
|
3314
|
+
index++;
|
|
3315
|
+
}
|
|
3316
|
+
const imported = tokens[index];
|
|
3317
|
+
if (imported?.kind !== 'identifier' && imported?.kind !== 'string') {
|
|
3318
|
+
return undefined;
|
|
2617
3319
|
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
3320
|
+
index++;
|
|
3321
|
+
if (isIdentifier(tokens[index], 'as')) {
|
|
3322
|
+
if (tokens[index + 1]?.kind !== 'identifier') {
|
|
3323
|
+
return undefined;
|
|
3324
|
+
}
|
|
3325
|
+
index += 2;
|
|
2621
3326
|
}
|
|
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;
|
|
3327
|
+
else if (imported.kind === 'string') {
|
|
3328
|
+
return undefined;
|
|
2632
3329
|
}
|
|
2633
|
-
if (
|
|
2634
|
-
|
|
2635
|
-
return j + 2;
|
|
3330
|
+
if (!isPunct(tokens[index], ',')) {
|
|
3331
|
+
break;
|
|
2636
3332
|
}
|
|
3333
|
+
index++;
|
|
3334
|
+
}
|
|
3335
|
+
if (!isPunct(tokens[index], '}')) {
|
|
3336
|
+
return undefined;
|
|
2637
3337
|
}
|
|
2638
|
-
|
|
3338
|
+
index++;
|
|
2639
3339
|
}
|
|
2640
|
-
|
|
2641
|
-
|
|
3340
|
+
else {
|
|
3341
|
+
return undefined;
|
|
3342
|
+
}
|
|
3343
|
+
return importFromSourceIndex(tokens, index);
|
|
3344
|
+
}
|
|
3345
|
+
function importFromSourceIndex(tokens, index) {
|
|
3346
|
+
return isIdentifier(tokens[index], 'from') && tokens[index + 1]?.kind === 'string'
|
|
3347
|
+
? index + 1
|
|
3348
|
+
: undefined;
|
|
2642
3349
|
}
|
|
2643
3350
|
function exportSourceIndex(tokens, exportIndex) {
|
|
2644
3351
|
let index = exportIndex + 1;
|
|
@@ -2684,7 +3391,7 @@ function handleRequire(tokens, i, occurrences) {
|
|
|
2684
3391
|
return i + 1;
|
|
2685
3392
|
}
|
|
2686
3393
|
const next = tokens[i + 1];
|
|
2687
|
-
//
|
|
3394
|
+
// A sole string argument, with an optional trailing comma, is static; options are not supported.
|
|
2688
3395
|
if (next?.kind === 'punct' && next.value === '(') {
|
|
2689
3396
|
const specifier = soleStringSpecifier(tokens, i + 2);
|
|
2690
3397
|
if (specifier !== undefined) {
|
|
@@ -2694,15 +3401,160 @@ function handleRequire(tokens, i, occurrences) {
|
|
|
2694
3401
|
}
|
|
2695
3402
|
return i + 1;
|
|
2696
3403
|
}
|
|
3404
|
+
/** Inspect binding targets only, skipping property keys, types and initializer expressions. */
|
|
3405
|
+
function bindingHasRequire(tokens, start, end, closingDelimiter) {
|
|
3406
|
+
const targets = [{ start, end }];
|
|
3407
|
+
while (targets.length > 0) {
|
|
3408
|
+
const target = targets.pop();
|
|
3409
|
+
if (target === undefined) {
|
|
3410
|
+
break;
|
|
3411
|
+
}
|
|
3412
|
+
let index = bindingAfterRest(tokens, target.start);
|
|
3413
|
+
// Constructor parameter properties prefix the binding, rather than naming it.
|
|
3414
|
+
let modifier = tokens[index];
|
|
3415
|
+
while (index + 1 < target.end &&
|
|
3416
|
+
modifier?.kind === 'identifier' &&
|
|
3417
|
+
['public', 'private', 'protected', 'readonly', 'override'].includes(modifier.value)) {
|
|
3418
|
+
index++;
|
|
3419
|
+
modifier = tokens[index];
|
|
3420
|
+
}
|
|
3421
|
+
const token = tokens[index];
|
|
3422
|
+
if (token?.kind === 'identifier') {
|
|
3423
|
+
const next = tokens[index + 1];
|
|
3424
|
+
if (token.value === 'require' &&
|
|
3425
|
+
(index + 1 >= target.end ||
|
|
3426
|
+
isPunct(next, ',') ||
|
|
3427
|
+
isPunct(next, '?') ||
|
|
3428
|
+
(next?.kind === 'punct' && next.operator === 'assignment'))) {
|
|
3429
|
+
return true;
|
|
3430
|
+
}
|
|
3431
|
+
continue;
|
|
3432
|
+
}
|
|
3433
|
+
const object = isPunct(token, '{');
|
|
3434
|
+
if (!object && !isPunct(token, '[')) {
|
|
3435
|
+
continue;
|
|
3436
|
+
}
|
|
3437
|
+
const close = closingDelimiter(index);
|
|
3438
|
+
if (close === undefined || close >= target.end) {
|
|
3439
|
+
continue;
|
|
3440
|
+
}
|
|
3441
|
+
const frame = token?.parameterFrame;
|
|
3442
|
+
index++;
|
|
3443
|
+
while (index < close) {
|
|
3444
|
+
if (isPunct(tokens[index], ',')) {
|
|
3445
|
+
index++;
|
|
3446
|
+
continue;
|
|
3447
|
+
}
|
|
3448
|
+
index = bindingAfterRest(tokens, index);
|
|
3449
|
+
let binding = index;
|
|
3450
|
+
if (object) {
|
|
3451
|
+
const keyClose = isPunct(tokens[index], '[') ? closingDelimiter(index) : index;
|
|
3452
|
+
if (keyClose === undefined) {
|
|
3453
|
+
break;
|
|
3454
|
+
}
|
|
3455
|
+
if (isPunct(tokens[keyClose + 1], ':')) {
|
|
3456
|
+
binding = keyClose + 2;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
targets.push({ start: binding, end: close });
|
|
3460
|
+
index = bindingElementEnd(tokens, binding, close, frame, closingDelimiter);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
return false;
|
|
3464
|
+
}
|
|
3465
|
+
function bindingAfterRest(tokens, index) {
|
|
3466
|
+
return isPunct(tokens[index], '.') &&
|
|
3467
|
+
isPunct(tokens[index + 1], '.') &&
|
|
3468
|
+
isPunct(tokens[index + 2], '.')
|
|
3469
|
+
? index + 3
|
|
3470
|
+
: index;
|
|
3471
|
+
}
|
|
3472
|
+
function bindingElementEnd(tokens, start, end, frame, closingDelimiter) {
|
|
3473
|
+
let index = start;
|
|
3474
|
+
while (index < end) {
|
|
3475
|
+
const token = tokens[index];
|
|
3476
|
+
if (isPunct(token, ',') && token?.parameterFrame === frame && token?.parameterType !== true) {
|
|
3477
|
+
return index + 1;
|
|
3478
|
+
}
|
|
3479
|
+
if (isPunct(token, '(') || isPunct(token, '[') || isPunct(token, '{')) {
|
|
3480
|
+
const close = closingDelimiter(index);
|
|
3481
|
+
if (close !== undefined && close < end) {
|
|
3482
|
+
index = close + 1;
|
|
3483
|
+
continue;
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
index++;
|
|
3487
|
+
}
|
|
3488
|
+
return end;
|
|
3489
|
+
}
|
|
3490
|
+
/** Index balanced delimiters once, lazily, so nested option calls do not rescan suffixes. */
|
|
3491
|
+
function indexClosingDelimiters(tokens) {
|
|
3492
|
+
const closing = new Map();
|
|
3493
|
+
const stack = [];
|
|
3494
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
3495
|
+
const token = tokens[index];
|
|
3496
|
+
if (token?.kind !== 'punct') {
|
|
3497
|
+
continue;
|
|
3498
|
+
}
|
|
3499
|
+
if (token.value === '(' || token.value === '[' || token.value === '{') {
|
|
3500
|
+
stack.push(index);
|
|
3501
|
+
continue;
|
|
3502
|
+
}
|
|
3503
|
+
const opener = closerFor(token.value);
|
|
3504
|
+
if (opener === undefined) {
|
|
3505
|
+
continue;
|
|
3506
|
+
}
|
|
3507
|
+
const open = stack.pop();
|
|
3508
|
+
if (open === undefined || !isPunct(tokens[open], opener)) {
|
|
3509
|
+
// Do not pair an enclosing option object across a malformed nested delimiter.
|
|
3510
|
+
stack.length = 0;
|
|
3511
|
+
}
|
|
3512
|
+
else {
|
|
3513
|
+
closing.set(open, index);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
return closing;
|
|
3517
|
+
}
|
|
3518
|
+
/** The opener a closer matches, or undefined when the token is not a closer. */
|
|
3519
|
+
function closerFor(value) {
|
|
3520
|
+
if (value === ')') {
|
|
3521
|
+
return '(';
|
|
3522
|
+
}
|
|
3523
|
+
return value === ']' ? '[' : value === '}' ? '{' : undefined;
|
|
3524
|
+
}
|
|
3525
|
+
/**
|
|
3526
|
+
* Recognize the supported dynamic import arguments: a literal source plus either no further
|
|
3527
|
+
* argument or one balanced object-literal options expression. Option contents are never evaluated
|
|
3528
|
+
* and never contribute evidence. `require` keeps the stricter sole-literal rule.
|
|
3529
|
+
*/
|
|
3530
|
+
function dynamicImportSpecifier(tokens, argIndex, closingDelimiter) {
|
|
3531
|
+
const sole = soleStringSpecifier(tokens, argIndex);
|
|
3532
|
+
if (sole !== undefined) {
|
|
3533
|
+
return sole;
|
|
3534
|
+
}
|
|
3535
|
+
const specifier = tokens[argIndex];
|
|
3536
|
+
if (specifier?.kind !== 'string' ||
|
|
3537
|
+
!isPunct(tokens[argIndex + 1], ',') ||
|
|
3538
|
+
!isPunct(tokens[argIndex + 2], '{')) {
|
|
3539
|
+
return undefined;
|
|
3540
|
+
}
|
|
3541
|
+
const optionsClose = closingDelimiter(argIndex + 2);
|
|
3542
|
+
if (optionsClose === undefined) {
|
|
3543
|
+
return undefined;
|
|
3544
|
+
}
|
|
3545
|
+
const next = isPunct(tokens[optionsClose + 1], ',') ? optionsClose + 2 : optionsClose + 1;
|
|
3546
|
+
return isPunct(tokens[next], ')') ? specifier.value : undefined;
|
|
3547
|
+
}
|
|
2697
3548
|
/**
|
|
2698
3549
|
* The decoded string at `argIndex` when it is the only argument of the surrounding call, that is
|
|
2699
|
-
* when it is a string
|
|
3550
|
+
* when it is a string followed by an optional trailing comma and `)`. Returns undefined otherwise,
|
|
2700
3551
|
* so a dynamic argument (e.g. `'pkg-' + name`) or a call with extra arguments (e.g.
|
|
2701
3552
|
* `require('pkg', ctx)`) never yields a static specifier.
|
|
2702
3553
|
*/
|
|
2703
3554
|
function soleStringSpecifier(tokens, argIndex) {
|
|
2704
3555
|
const arg = tokens[argIndex];
|
|
2705
|
-
const
|
|
3556
|
+
const closingIndex = isPunct(tokens[argIndex + 1], ',') ? argIndex + 2 : argIndex + 1;
|
|
3557
|
+
const closing = tokens[closingIndex];
|
|
2706
3558
|
if (arg?.kind !== 'string') {
|
|
2707
3559
|
return undefined;
|
|
2708
3560
|
}
|
|
@@ -2725,7 +3577,88 @@ function skipBlockComment(source, start) {
|
|
|
2725
3577
|
}
|
|
2726
3578
|
throw new ModuleSpecifierError('Unterminated block comment', 'invalid-source', start);
|
|
2727
3579
|
}
|
|
3580
|
+
/** A dynamic import remains valid script syntax; only static forms and meta signal a module. */
|
|
3581
|
+
function importSignalsModule(source, end, atRoot) {
|
|
3582
|
+
const next = skipTrivia(source, end, source.length);
|
|
3583
|
+
if (source[next] === '.') {
|
|
3584
|
+
const meta = skipTrivia(source, next + 1, source.length);
|
|
3585
|
+
return ((isIdentifierStartAt(source, meta) || isUnicodeEscapeStart(source, meta)) &&
|
|
3586
|
+
readIdentifier(source, meta).value === 'meta');
|
|
3587
|
+
}
|
|
3588
|
+
return (atRoot &&
|
|
3589
|
+
(source[next] === "'" ||
|
|
3590
|
+
source[next] === '"' ||
|
|
3591
|
+
source[next] === '{' ||
|
|
3592
|
+
source[next] === '*' ||
|
|
3593
|
+
isIdentifierStartAt(source, next) ||
|
|
3594
|
+
isUnicodeEscapeStart(source, next)));
|
|
3595
|
+
}
|
|
3596
|
+
function exportSignalsModule(source, end) {
|
|
3597
|
+
const next = skipTrivia(source, end, source.length);
|
|
3598
|
+
return (source[next] === '{' ||
|
|
3599
|
+
source[next] === '*' ||
|
|
3600
|
+
(isIdentifierStartAt(source, next) &&
|
|
3601
|
+
['default', 'const', 'let', 'var', 'function', 'async', 'class'].includes(readIdentifier(source, next).value)));
|
|
3602
|
+
}
|
|
3603
|
+
/**
|
|
3604
|
+
* Probe top-level await without treating every identifier named await as a keyword. A complete
|
|
3605
|
+
* regex may be read opaquely during this pass even when its following operand makes it script
|
|
3606
|
+
* division. That protects apostrophes until a later static declaration settles the whole file.
|
|
3607
|
+
* Calls, indexing, tags, unary +/- and flag-like identifier divisors remain ambiguous script
|
|
3608
|
+
* expressions absent another signal.
|
|
3609
|
+
*/
|
|
3610
|
+
function probeAwaitOperand(source, end, inForHead) {
|
|
3611
|
+
const trivia = readTrivia(source, end, source.length);
|
|
3612
|
+
const next = trivia.end;
|
|
3613
|
+
const ch = source[next];
|
|
3614
|
+
if (ch === '/') {
|
|
3615
|
+
const finish = regexEnd(source, next);
|
|
3616
|
+
if (finish === undefined) {
|
|
3617
|
+
return { keyword: false, module: false };
|
|
3618
|
+
}
|
|
3619
|
+
const following = skipTrivia(source, finish, source.length);
|
|
3620
|
+
const tail = source[following];
|
|
3621
|
+
// Characters consumed as flags can instead be a complete identifier divisor (including a
|
|
3622
|
+
// valid flag such as g). Its following operator/terminator cannot establish module grammar.
|
|
3623
|
+
const hasIdentifierDivisor = source[finish - 1] !== '/';
|
|
3624
|
+
const comparisonKeyword = (isIdentifierStartAt(source, following) || isUnicodeEscapeStart(source, following)) &&
|
|
3625
|
+
['in', 'instanceof'].includes(readIdentifier(source, following).value);
|
|
3626
|
+
const unambiguous = !hasIdentifierDivisor &&
|
|
3627
|
+
(tail === undefined ||
|
|
3628
|
+
[';', ',', ')', ']', '}', '?'].includes(tail) ||
|
|
3629
|
+
(tail === '.' && !isNumberStart(source[following + 1] ?? '')) ||
|
|
3630
|
+
// These binary operators cannot start the missing right-hand divisor. Unary +/-,
|
|
3631
|
+
// calls, indexing, templates and decimal starts still admit script expressions.
|
|
3632
|
+
['<', '>', '*', '%', '&', '|', '^'].includes(tail) ||
|
|
3633
|
+
source.startsWith('==', following) ||
|
|
3634
|
+
source.startsWith('!=', following) ||
|
|
3635
|
+
comparisonKeyword);
|
|
3636
|
+
return { keyword: true, module: unambiguous };
|
|
3637
|
+
}
|
|
3638
|
+
// A newline also permits a bare await identifier statement followed by another statement.
|
|
3639
|
+
if (trivia.lineBreak) {
|
|
3640
|
+
return { keyword: false, module: false };
|
|
3641
|
+
}
|
|
3642
|
+
if (isIdentifierStartAt(source, next) || isUnicodeEscapeStart(source, next)) {
|
|
3643
|
+
const name = readIdentifier(source, next).value;
|
|
3644
|
+
const operand = !['in', 'instanceof'].includes(name) && !(name === 'of' && inForHead);
|
|
3645
|
+
return { keyword: operand, module: operand };
|
|
3646
|
+
}
|
|
3647
|
+
const operand = ch !== undefined &&
|
|
3648
|
+
(isNumberStart(ch) ||
|
|
3649
|
+
(ch === '.' && isNumberStart(source[next + 1] ?? '')) ||
|
|
3650
|
+
["'", '"', '{', '~'].includes(ch) ||
|
|
3651
|
+
(ch === '!' && source[next + 1] !== '='));
|
|
3652
|
+
return { keyword: operand, module: operand };
|
|
3653
|
+
}
|
|
2728
3654
|
function skipRegex(source, start) {
|
|
3655
|
+
const end = regexEnd(source, start);
|
|
3656
|
+
if (end === undefined) {
|
|
3657
|
+
throw new ModuleSpecifierError('Unterminated regex literal', 'invalid-source', start);
|
|
3658
|
+
}
|
|
3659
|
+
return end;
|
|
3660
|
+
}
|
|
3661
|
+
function regexEnd(source, start) {
|
|
2729
3662
|
const n = source.length;
|
|
2730
3663
|
let i = start + 1;
|
|
2731
3664
|
let inClass = false;
|
|
@@ -2734,7 +3667,7 @@ function skipRegex(source, start) {
|
|
|
2734
3667
|
// A regex literal cannot contain a raw line terminator. Rejecting it here stops a wrong regex
|
|
2735
3668
|
// start from swallowing the following lines.
|
|
2736
3669
|
if (ch === '\n' || ch === '\r' || ch === '\u2028' || ch === '\u2029') {
|
|
2737
|
-
|
|
3670
|
+
return undefined;
|
|
2738
3671
|
}
|
|
2739
3672
|
if (ch === '\\') {
|
|
2740
3673
|
i += 2;
|
|
@@ -2757,7 +3690,7 @@ function skipRegex(source, start) {
|
|
|
2757
3690
|
}
|
|
2758
3691
|
i++;
|
|
2759
3692
|
}
|
|
2760
|
-
|
|
3693
|
+
return undefined;
|
|
2761
3694
|
}
|
|
2762
3695
|
function skipRegexFlags(source, i) {
|
|
2763
3696
|
const n = source.length;
|