depvitals 0.2.0 → 0.4.0

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