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.
Files changed (54) hide show
  1. package/README.md +36 -40
  2. package/dist/cli/runCli.d.ts.map +1 -1
  3. package/dist/cli/runCli.js +9 -4
  4. package/dist/cli/runCli.js.map +1 -1
  5. package/dist/config/ignoreDependencies.d.ts +7 -0
  6. package/dist/config/ignoreDependencies.d.ts.map +1 -0
  7. package/dist/config/ignoreDependencies.js +18 -0
  8. package/dist/config/ignoreDependencies.js.map +1 -0
  9. package/dist/config/ignorePaths.d.ts +13 -0
  10. package/dist/config/ignorePaths.d.ts.map +1 -0
  11. package/dist/config/ignorePaths.js +47 -0
  12. package/dist/config/ignorePaths.js.map +1 -0
  13. package/dist/config/projectConfigError.d.ts +7 -0
  14. package/dist/config/projectConfigError.d.ts.map +1 -0
  15. package/dist/config/projectConfigError.js +10 -0
  16. package/dist/config/projectConfigError.js.map +1 -0
  17. package/dist/config/readProjectConfig.d.ts +23 -0
  18. package/dist/config/readProjectConfig.d.ts.map +1 -0
  19. package/dist/config/readProjectConfig.js +106 -0
  20. package/dist/config/readProjectConfig.js.map +1 -0
  21. package/dist/core/analyzeProject.d.ts +5 -1
  22. package/dist/core/analyzeProject.d.ts.map +1 -1
  23. package/dist/core/analyzeProject.js +37 -29
  24. package/dist/core/analyzeProject.js.map +1 -1
  25. package/dist/detectors/eslintManifestConfig.d.ts.map +1 -1
  26. package/dist/detectors/eslintManifestConfig.js +7 -4
  27. package/dist/detectors/eslintManifestConfig.js.map +1 -1
  28. package/dist/detectors/eslintRcJsonConfig.d.ts.map +1 -1
  29. package/dist/detectors/eslintRcJsonConfig.js +2 -1
  30. package/dist/detectors/eslintRcJsonConfig.js.map +1 -1
  31. package/dist/detectors/jestManifestConfig.d.ts.map +1 -1
  32. package/dist/detectors/jestManifestConfig.js +6 -4
  33. package/dist/detectors/jestManifestConfig.js.map +1 -1
  34. package/dist/scanner/moduleSpecifiers.d.ts +2 -2
  35. package/dist/scanner/moduleSpecifiers.d.ts.map +1 -1
  36. package/dist/scanner/moduleSpecifiers.js +1111 -178
  37. package/dist/scanner/moduleSpecifiers.js.map +1 -1
  38. package/dist/scanner/packageSpecifier.d.ts +26 -2
  39. package/dist/scanner/packageSpecifier.d.ts.map +1 -1
  40. package/dist/scanner/packageSpecifier.js +70 -2
  41. package/dist/scanner/packageSpecifier.js.map +1 -1
  42. package/dist/scanner/projectManifest.d.ts +19 -3
  43. package/dist/scanner/projectManifest.d.ts.map +1 -1
  44. package/dist/scanner/projectManifest.js +51 -12
  45. package/dist/scanner/projectManifest.js.map +1 -1
  46. package/dist/scanner/scriptUsage.d.ts +2 -1
  47. package/dist/scanner/scriptUsage.d.ts.map +1 -1
  48. package/dist/scanner/scriptUsage.js +182 -37
  49. package/dist/scanner/scriptUsage.js.map +1 -1
  50. package/dist/scanner/sourceFiles.d.ts +16 -2
  51. package/dist/scanner/sourceFiles.d.ts.map +1 -1
  52. package/dist/scanner/sourceFiles.js +12 -6
  53. package/dist/scanner/sourceFiles.js.map +1 -1
  54. 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
- return interpret(tokenize(source, options.jsx ?? false, options.module ?? false));
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
- tokens.push(significant);
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 expression containers are separate expressions. Emitting an explicit
170
- // boundary keeps the token stream from gluing two of them into an import/require sequence that
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
- (!TYPE_OPERAND_KEYWORDS.has(identifier.value) || identifier.value === 'const')) {
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] === signature.context) {
384
- pendingFunctionContexts.pop();
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 = functionIsExpressionPosition(prevKind, prevPunct, previousIdentifier, colonIsExpression);
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
- (insideObjectLiteral() || insideClassBody()) &&
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
- (insideObjectLiteral() || insideClassBody()) &&
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({ role: isObject ? 'object' : 'block' });
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 previousToken = tokens[tokens.length - 1];
1058
- const arrowBodyParenthesis = previousToken?.kind === 'punct' &&
1059
- previousToken.value === '>' &&
1060
- isArrowHead(tokens[tokens.length - 2], previousToken.offset);
1061
- const signatureCandidate = arrowBodyParenthesis
1062
- ? undefined
1063
- : parameterSignatureContext(tokens, pending !== undefined && atLexicalDepth(pending) ? pending : undefined, insideObjectLiteral() || insideClassBody(), insideClassBody(), tokenLineBreakBefore, currentClass()?.member?.start);
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
- if (typedArrowContext !== undefined) {
1152
- pendingFunctionContexts.push({
1153
- ...typedArrowContext,
1154
- parenDepth,
1155
- bracketDepth,
1156
- braceDepth: braceContexts.length,
1157
- frameDepth: frames.length,
1158
- parameterDepth: undefined,
1159
- phase: 'awaiting-body',
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
- k = skipTrivia(source, k, n);
1320
- const ch = source[k];
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
- return ((source[j] === '=' && source[j + 1] === '>') ||
1356
- (source[j] === ':' && arrowFollowsReturnType(source, j + 1, n)));
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
- function arrowFollowsReturnType(source, start, n) {
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 false;
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 true;
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 false;
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 false;
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 false;
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 false;
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 false;
1923
+ return undefined;
1442
1924
  }
1443
1925
  index++;
1444
1926
  }
1445
- return false;
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
- while (i < n) {
1515
- const trivia = readTrivia(source, i, n);
1516
- i = trivia.end;
1517
- const ch = source[i];
1518
- if (ch === undefined) {
1519
- break;
1520
- }
1521
- if (ch === "'" || ch === '"') {
1522
- const { value, next } = readString(source, i);
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: 'string',
1525
- value,
2145
+ kind: 'punct',
2146
+ value: ch,
1526
2147
  offset: i,
1527
- endOffset: next,
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
- continue;
1548
- }
1549
- if (ch === '<' || ch === '(' || ch === '{' || ch === '[') {
1550
- closers.push(ch === '<' ? '>' : ch === '(' ? ')' : ch === '{' ? '}' : ']');
2152
+ i++;
1551
2153
  }
1552
- else if (ch === closers[closers.length - 1] && !(ch === '>' && source[i - 1] === '=')) {
1553
- closers.pop();
1554
- if (closers.length === 0) {
1555
- return i + 1;
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
- tokens.push({
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 = openIndex === 0 ||
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, previousIdentifier, colonIsExpression) {
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
- * - `import('pkg')` is `dynamic-import` only when the sole argument is a static string.
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
- // `import('pkg')` / `import ('pkg')`: only a sole string argument closed by `)` is static.
3237
+ // Keep literal specifiers with sole-argument or bounded object-literal options forms.
2570
3238
  if (next?.kind === 'punct' && next.value === '(') {
2571
- const specifier = soleStringSpecifier(tokens, i + 2);
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'`: scan forward for `from` followed by a string.
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
- * Scan forward from `i` looking for the `from` keyword followed by a string, recording it with
2589
- * `kind`. Stops early at a nested `import`/`export`/`require` so a following construct is handled on
2590
- * its own, except for `import`/`export` used as binding names inside the `{ ... }` clause of the
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 scanImportSource(tokens, i, occurrences, kind) {
2594
- const n = tokens.length;
2595
- if (kind === 'export-from') {
2596
- const sourceIndex = exportSourceIndex(tokens, i);
2597
- if (sourceIndex === undefined) {
2598
- return i + 1;
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
- const source = tokens[sourceIndex];
2601
- if (source?.kind === 'string') {
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
- let j = i + 1;
2608
- // How many `{ ... }` named clauses of the current statement are open. `import`/`export` are legal
2609
- // binding names inside such a clause (e.g. `import { export as v } from 'pkg'`), so they may only
2610
- // start a new construct at depth 0, otherwise the search stops before the real `from` source.
2611
- let braceDepth = 0;
2612
- while (j < n) {
2613
- const token = tokens[j];
2614
- if (token?.kind === 'punct') {
2615
- if (token.value === '{') {
2616
- braceDepth++;
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
- else if (token.value === '}') {
2619
- // Clamped so an unmatched `}` cannot leave a negative depth that hides later boundaries.
2620
- braceDepth = braceDepth > 0 ? braceDepth - 1 : 0;
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
- if (token?.kind === 'identifier') {
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 (token.value === 'from' && next?.kind === 'string') {
2634
- occurrences.push({ specifier: next.value, kind });
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
- j++;
3338
+ index++;
2639
3339
  }
2640
- // No source found; keep scanning from the next token.
2641
- return i + 1;
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
- // `require('pkg')`: only a sole string argument closed by `)` is static.
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 and the very next token closes the call with `)`. Returns undefined otherwise,
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 closing = tokens[argIndex + 1];
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
- throw new ModuleSpecifierError('Unterminated regex literal', 'invalid-source', start);
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
- throw new ModuleSpecifierError('Unterminated regex literal', 'invalid-source', start);
3693
+ return undefined;
2761
3694
  }
2762
3695
  function skipRegexFlags(source, i) {
2763
3696
  const n = source.length;