depvitals 0.1.1 → 0.2.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 (62) hide show
  1. package/README.md +37 -54
  2. package/dist/cli/entry.d.ts +59 -0
  3. package/dist/cli/entry.d.ts.map +1 -0
  4. package/dist/cli/entry.js +95 -0
  5. package/dist/cli/entry.js.map +1 -0
  6. package/dist/cli/index.js +14 -18
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/notifier/eligibility.d.ts +65 -0
  9. package/dist/cli/notifier/eligibility.d.ts.map +1 -0
  10. package/dist/cli/notifier/eligibility.js +123 -0
  11. package/dist/cli/notifier/eligibility.js.map +1 -0
  12. package/dist/cli/notifier/registryLookup.d.ts +111 -0
  13. package/dist/cli/notifier/registryLookup.d.ts.map +1 -0
  14. package/dist/cli/notifier/registryLookup.js +211 -0
  15. package/dist/cli/notifier/registryLookup.js.map +1 -0
  16. package/dist/cli/notifier/updateCache.d.ts +101 -0
  17. package/dist/cli/notifier/updateCache.d.ts.map +1 -0
  18. package/dist/cli/notifier/updateCache.js +352 -0
  19. package/dist/cli/notifier/updateCache.js.map +1 -0
  20. package/dist/cli/notifier/updateCheck.d.ts +88 -0
  21. package/dist/cli/notifier/updateCheck.d.ts.map +1 -0
  22. package/dist/cli/notifier/updateCheck.js +166 -0
  23. package/dist/cli/notifier/updateCheck.js.map +1 -0
  24. package/dist/cli/notifier/versionCompare.d.ts +48 -0
  25. package/dist/cli/notifier/versionCompare.d.ts.map +1 -0
  26. package/dist/cli/notifier/versionCompare.js +89 -0
  27. package/dist/cli/notifier/versionCompare.js.map +1 -0
  28. package/dist/cli/runCli.d.ts +2 -0
  29. package/dist/cli/runCli.d.ts.map +1 -1
  30. package/dist/cli/runCli.js +30 -3
  31. package/dist/cli/runCli.js.map +1 -1
  32. package/dist/core/analyzeProject.d.ts +44 -6
  33. package/dist/core/analyzeProject.d.ts.map +1 -1
  34. package/dist/core/analyzeProject.js +110 -11
  35. package/dist/core/analyzeProject.js.map +1 -1
  36. package/dist/detectors/eslintManifestConfig.d.ts +111 -0
  37. package/dist/detectors/eslintManifestConfig.d.ts.map +1 -0
  38. package/dist/detectors/eslintManifestConfig.js +348 -0
  39. package/dist/detectors/eslintManifestConfig.js.map +1 -0
  40. package/dist/detectors/eslintRcJsonConfig.d.ts +85 -0
  41. package/dist/detectors/eslintRcJsonConfig.d.ts.map +1 -0
  42. package/dist/detectors/eslintRcJsonConfig.js +188 -0
  43. package/dist/detectors/eslintRcJsonConfig.js.map +1 -0
  44. package/dist/detectors/jestManifestConfig.d.ts +76 -0
  45. package/dist/detectors/jestManifestConfig.d.ts.map +1 -0
  46. package/dist/detectors/jestManifestConfig.js +292 -0
  47. package/dist/detectors/jestManifestConfig.js.map +1 -0
  48. package/dist/reporters/terminalReporter.js +27 -1
  49. package/dist/reporters/terminalReporter.js.map +1 -1
  50. package/dist/scanner/moduleSpecifiers.d.ts +9 -1
  51. package/dist/scanner/moduleSpecifiers.d.ts.map +1 -1
  52. package/dist/scanner/moduleSpecifiers.js +2352 -173
  53. package/dist/scanner/moduleSpecifiers.js.map +1 -1
  54. package/dist/scanner/projectManifest.d.ts +14 -0
  55. package/dist/scanner/projectManifest.d.ts.map +1 -1
  56. package/dist/scanner/projectManifest.js +40 -0
  57. package/dist/scanner/projectManifest.js.map +1 -1
  58. package/dist/scanner/scriptUsage.d.ts +11 -5
  59. package/dist/scanner/scriptUsage.d.ts.map +1 -1
  60. package/dist/scanner/scriptUsage.js +320 -16
  61. package/dist/scanner/scriptUsage.js.map +1 -1
  62. package/package.json +7 -7
@@ -42,14 +42,14 @@ export class ModuleSpecifierError extends Error {
42
42
  * @throws {ModuleSpecifierError} when the source reaches an unterminated or malformed lexical state
43
43
  * from which the scanner cannot safely continue.
44
44
  */
45
- export function extractModuleSpecifiers(source) {
46
- return interpret(tokenize(source));
45
+ export function extractModuleSpecifiers(source, options = {}) {
46
+ return interpret(tokenize(source, options.jsx ?? false, options.module ?? false));
47
47
  }
48
48
  /**
49
49
  * Turn source into a stream of significant tokens, skipping whitespace, line/block comments, regex
50
50
  * literals and template literals.
51
51
  */
52
- function tokenize(source) {
52
+ function tokenize(source, jsx = false, module = false) {
53
53
  const tokens = [];
54
54
  const n = source.length;
55
55
  let i = 0;
@@ -70,22 +70,57 @@ function tokenize(source) {
70
70
  // The value of the previous identifier token, when the previous token was an identifier. Which
71
71
  // identifier it was matters for one decision: `return {` opens an object literal, `else {` not.
72
72
  let prevIdentifier;
73
+ // Preserve the position before an `async` modifier for a following `function` keyword.
74
+ let asyncFunctionIsExpression = false;
75
+ // A line terminator in trivia may end an expression-bodied arrow through ASI. It is consumed
76
+ // only when the next significant token cannot continue the preceding expression.
77
+ let lineTerminatorSinceLastToken = false;
78
+ let tokenLineBreakBefore = false;
79
+ let htmlCloseCommentAllowed = true;
80
+ const emit = (token) => {
81
+ const significant = tokenLineBreakBefore ? { ...token, lineBreakBefore: true } : token;
82
+ if (memberAtToken !== undefined) {
83
+ advanceMember(memberAtToken, significant, tokens[tokens.length - 1]);
84
+ }
85
+ tokens.push(significant);
86
+ if (token.kind !== 'punct' || token.syntheticBoundary !== true) {
87
+ htmlCloseCommentAllowed = false;
88
+ }
89
+ tokenLineBreakBefore = false;
90
+ lineTerminatorSinceLastToken = false;
91
+ };
73
92
  // Template literal state. `inText` is true while scanning template text that must not be lexed as
74
93
  // JavaScript; a backtick toggles it, so nested templates are handled by the toggle. Every open
75
- // `${...}` expression pushes its own brace depth onto `braceDepths`, which is what distinguishes
76
- // the `}` that closes the interpolation from a `}` belonging to an object literal, a block or a
77
- // function body inside it. JavaScript inside `${...}` is lexed like ordinary source.
78
- const braceDepths = [];
94
+ // `${...}` expression pushes a `template-expr` frame carrying its own brace depth, which is what
95
+ // distinguishes the `}` that closes the interpolation from a `}` belonging to an object literal, a
96
+ // block or a function body inside it. JavaScript inside `${...}` is lexed like ordinary source.
97
+ //
98
+ // The same stack carries the JSX overlay frames (`jsx-tag`, `jsx-text` and the `jsx-expr` of a
99
+ // `{...}` container), so markup, expression containers, templates and nested elements interleave
100
+ // in a single pass without any state leaking between them.
101
+ const frames = [];
79
102
  let inText = false;
80
103
  // Offset of the opening backtick of the current template, for unterminated-template errors.
81
104
  let templateStart = 0;
82
- // Brace context. Each `{` pushes whether it opened an expression (an object literal or a
83
- // destructuring pattern) or a statement block / function body, and its matching `}` pops that
84
- // entry. A `}` only ends an expression when it closed an object literal, which is what separates
85
- // `{}` in `{} / require('pkg') / 2`, where both slashes divide, from the `}` of
86
- // `if (ok) { ... }`, after which a `/` may legitimately start a regex. Anything unrecognized is
87
- // treated as a block.
105
+ const templateStarts = [];
88
106
  const braceContexts = [];
107
+ let memberAtToken;
108
+ // Function context of every open function body, tracked independently of expression frame resets
109
+ // so `${}` boundaries cannot change the nearest await/yield grammar.
110
+ const functionContextStack = [];
111
+ // The nearest function body owns the await/yield grammar. An ordinary nested function therefore
112
+ // suspends an async/generator context from an outer function instead of inheriting it.
113
+ // A default callback may open and finish while an outer signature is still reading parameters.
114
+ // Only a signature with closed parameters at its original depth can own the next body brace.
115
+ const pendingFunctionContexts = [];
116
+ const pendingClassContexts = [];
117
+ const conditionalContexts = [];
118
+ let colonIsExpression = false;
119
+ const expressionFunctionContexts = [];
120
+ let typeAnnotation;
121
+ const parameterContexts = [];
122
+ let closedParameterSignature;
123
+ let bracketDepth = 0;
89
124
  // Whether the most recent `}` closed an object literal. It is only consulted while that `}` is
90
125
  // the previous token, exactly like `prevPunct`.
91
126
  let closedObjectBrace = false;
@@ -95,10 +130,19 @@ function tokenize(source) {
95
130
  // The control keyword pending a `(` (if/while/for/with), so the matching `)` can be recognised.
96
131
  // Only whitespace and comments may appear between the keyword and the `(`.
97
132
  let pendingControlKeyword;
98
- // How many `(` are currently open, and the depth at which the open control-statement head sits,
99
- // so only the `)` that closes the head itself (not a nested one) enables the heuristic.
133
+ // Parameter/control parentheses carry the signature they opened, rather than recovering one
134
+ // from an arbitrary `)` when a colon or body brace appears later.
100
135
  let parenDepth = 0;
101
- let controlParenDepth;
136
+ const atLexicalDepth = (context) => parenDepth === context.parenDepth &&
137
+ bracketDepth === context.bracketDepth &&
138
+ braceContexts.length === context.braceDepth &&
139
+ frames.length === context.frameDepth;
140
+ const insideObjectLiteral = () => braceContexts[braceContexts.length - 1]?.role === 'object';
141
+ const insideClassBody = () => braceContexts[braceContexts.length - 1]?.role === 'class';
142
+ const currentClass = () => {
143
+ const brace = braceContexts[braceContexts.length - 1];
144
+ return brace?.role === 'class' ? brace.state : undefined;
145
+ };
102
146
  // Reset the expression-start state at the beginning of a fresh `${...}` expression.
103
147
  const resetExpressionState = () => {
104
148
  prevKind = 'start';
@@ -108,13 +152,54 @@ function tokenize(source) {
108
152
  afterControlHead = false;
109
153
  pendingControlKeyword = undefined;
110
154
  closedObjectBrace = false;
155
+ lineTerminatorSinceLastToken = false;
156
+ // Pending signatures retain their own depth across template/JSX expressions in parameters.
157
+ };
158
+ // A finished JSX element leaves an expression value behind: a following `/` divides, and the
159
+ // markup itself never opens a statement context.
160
+ const endJsxElement = () => {
161
+ prevKind = 'template';
162
+ prevPunct = undefined;
163
+ prevIdentifier = undefined;
164
+ prevIsExpressionKeyword = false;
165
+ afterControlHead = false;
166
+ pendingControlKeyword = undefined;
167
+ closedObjectBrace = false;
168
+ };
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
171
+ // does not exist in source (for example `<p>{import}{('pkg')}</p>`).
172
+ const pushBoundary = (offset) => {
173
+ emit({
174
+ kind: 'punct',
175
+ value: ';',
176
+ offset,
177
+ endOffset: offset + 1,
178
+ syntheticBoundary: true,
179
+ });
111
180
  };
112
181
  while (i < n) {
182
+ memberAtToken = undefined;
113
183
  const ch = source[i];
184
+ let typedArrowContext;
185
+ let inType = typeAnnotation !== undefined;
186
+ let closedClassMemberBoundary = false;
187
+ let closedExpressionBrace = false;
188
+ let opensClassMemberBoundary = false;
189
+ let matchingOpen;
114
190
  if (ch === undefined) {
115
191
  i++;
116
192
  continue;
117
193
  }
194
+ let conditional = conditionalContexts[conditionalContexts.length - 1];
195
+ while (conditional !== undefined &&
196
+ (conditional.parenDepth > parenDepth ||
197
+ conditional.bracketDepth > bracketDepth ||
198
+ conditional.braceDepth > braceContexts.length ||
199
+ conditional.frameDepth > frames.length)) {
200
+ conditionalContexts.pop();
201
+ conditional = conditionalContexts[conditionalContexts.length - 1];
202
+ }
118
203
  // Template literal text: skip everything until `${`, a nested `` ` ``, or the closing `` ` ``.
119
204
  // A backtick toggles out of text mode; the matching toggle happens below in code mode.
120
205
  if (inText) {
@@ -125,6 +210,9 @@ function tokenize(source) {
125
210
  if (ch === '`') {
126
211
  // Closing backtick of the current (or nested) template literal.
127
212
  inText = false;
213
+ const start = templateStarts.pop() ?? templateStart;
214
+ emit({ kind: 'template', offset: start, endOffset: i + 1 });
215
+ templateStart = templateStarts[templateStarts.length - 1] ?? start;
128
216
  prevKind = 'template';
129
217
  i++;
130
218
  continue;
@@ -132,7 +220,14 @@ function tokenize(source) {
132
220
  if (ch === '$' && source[i + 1] === '{') {
133
221
  // Start of a `${...}` expression: lex the JavaScript inside like ordinary source. The frame
134
222
  // starts at brace depth 0, so the next unmatched `}` closes the interpolation.
135
- braceDepths.push(0);
223
+ frames.push({
224
+ kind: 'template-expr',
225
+ depth: 0,
226
+ offset: i,
227
+ ...(typeAnnotation !== undefined
228
+ ? { typeOwner: { annotation: typeAnnotation, depth: { ...typeAnnotation } } }
229
+ : {}),
230
+ });
136
231
  inText = false;
137
232
  resetExpressionState();
138
233
  i += 2;
@@ -141,161 +236,2084 @@ function tokenize(source) {
141
236
  i++;
142
237
  continue;
143
238
  }
144
- if (isWhitespace(ch)) {
145
- i++;
146
- continue;
239
+ // The innermost frame decides how the next character is read.
240
+ const frame = frames[frames.length - 1];
241
+ // JSX children: inert text up to the matching close tag. Quotes, backticks and comment-looking
242
+ // sequences are text here, so only `<` (markup) and `{` (an expression container) leave it.
243
+ if (frame?.kind === 'jsx-text') {
244
+ if (ch === '{') {
245
+ frames.push({ kind: 'jsx-expr', depth: 0, offset: i });
246
+ pushBoundary(i);
247
+ resetExpressionState();
248
+ i++;
249
+ continue;
250
+ }
251
+ if (ch === '<') {
252
+ const after = source[i + 1];
253
+ // Close tag `</name>` or fragment close `</>`: this element ends here.
254
+ if (after === '/') {
255
+ let k = i + 2;
256
+ while (k < n && source[k] !== '>') {
257
+ k++;
258
+ }
259
+ if (k >= n) {
260
+ throw new ModuleSpecifierError('Unterminated JSX element', 'invalid-source', frame.offset);
261
+ }
262
+ frames.pop();
263
+ pushBoundary(i);
264
+ endJsxElement();
265
+ i = k + 1;
266
+ continue;
267
+ }
268
+ // Nested fragment `<>`.
269
+ if (after === '>') {
270
+ frames.push({ kind: 'jsx-text', name: null, offset: i });
271
+ i += 2;
272
+ continue;
273
+ }
274
+ // Nested element `<name ...`.
275
+ if (isIdentifierStartAt(source, i + 1)) {
276
+ const tag = readJsxTagName(source, i + 1, n);
277
+ frames.push({ kind: 'jsx-tag', name: tag.name, offset: i, nameEnd: tag.end });
278
+ i = tag.end;
279
+ continue;
280
+ }
281
+ // A `<` that is not markup (`a < b` written inside JSX text) is ordinary text.
282
+ i++;
283
+ continue;
284
+ }
285
+ i++;
286
+ continue;
287
+ }
288
+ // JSX open tag: attribute names, `=` and namespaces are inert; quoted values are inert text and
289
+ // `{...}` returns to real JavaScript.
290
+ if (frame?.kind === 'jsx-tag') {
291
+ if (ch === '<' && skipTrivia(source, frame.nameEnd, n) === i) {
292
+ pushBoundary(i);
293
+ i = readJsxTypeArguments(source, i, n, tokens);
294
+ pushBoundary(i);
295
+ continue;
296
+ }
297
+ if (ch === '{') {
298
+ frames.push({ kind: 'jsx-expr', depth: 0, offset: i });
299
+ pushBoundary(i);
300
+ resetExpressionState();
301
+ i++;
302
+ continue;
303
+ }
304
+ if (ch === '"' || ch === "'") {
305
+ i = skipJsxAttributeValue(source, i, n);
306
+ continue;
307
+ }
308
+ // Self-closing tag `<name ... />`: the element ends without children.
309
+ if (ch === '/' && source[i + 1] === '>') {
310
+ frames.pop();
311
+ pushBoundary(i);
312
+ endJsxElement();
313
+ i += 2;
314
+ continue;
315
+ }
316
+ // End of the open tag: its children follow until the matching close tag.
317
+ if (ch === '>') {
318
+ frames.pop();
319
+ frames.push({ kind: 'jsx-text', name: frame.name, offset: frame.offset });
320
+ i++;
321
+ continue;
322
+ }
323
+ i++;
324
+ continue;
325
+ }
326
+ if (!module &&
327
+ (source.startsWith('<!--', i) || (source.startsWith('-->', i) && htmlCloseCommentAllowed))) {
328
+ while (i < n && !isLineTerminator(source[i])) {
329
+ i++;
330
+ }
331
+ continue;
332
+ }
333
+ if (isWhitespace(ch) || (ch === '/' && (source[i + 1] === '/' || source[i + 1] === '*'))) {
334
+ const trivia = readTrivia(source, i, n);
335
+ i = trivia.end;
336
+ lineTerminatorSinceLastToken ||= trivia.lineBreak;
337
+ htmlCloseCommentAllowed ||= trivia.lineBreak;
338
+ continue;
339
+ }
340
+ tokenLineBreakBefore = lineTerminatorSinceLastToken;
341
+ const previousSignature = closedParameterSignature;
342
+ closedParameterSignature = undefined;
343
+ memberAtToken = undefined;
344
+ const classState = currentClass();
345
+ const identifier = isIdentifierStartAt(source, i) || isUnicodeEscapeStart(source, i)
346
+ ? readIdentifier(source, i)
347
+ : undefined;
348
+ if (typeAnnotation?.role === 'assertion' &&
349
+ atLexicalDepth(typeAnnotation.owner) &&
350
+ atTypeRoot(typeAnnotation) &&
351
+ !(ch === '>' && isArrowHead(tokens[tokens.length - 1], i)) &&
352
+ !assertionTypeContinues(typeAnnotation, ch, identifier?.value, tokenLineBreakBefore, source[i + 1])) {
353
+ // Complete the expression before class-field ASI and slash classification consume it.
354
+ // The token keeps its type delimiter identity; no runtime brace is invented or popped.
355
+ const previous = tokens[tokens.length - 1];
356
+ if (previous !== undefined) {
357
+ tokens[tokens.length - 1] = { ...previous, assertionEnd: true };
358
+ }
359
+ prevIsExpressionKeyword = false;
360
+ afterControlHead = false;
361
+ typeAnnotation = undefined;
362
+ inType = false;
363
+ }
364
+ // An assertion must release its type context before the same runtime delimiter can retire
365
+ // an enclosing arrow. Consume the delimiter only after restoring the nearest function grammar.
366
+ let expressionFunctionContext = expressionFunctionContexts[expressionFunctionContexts.length - 1];
367
+ while (expressionFunctionContext !== undefined &&
368
+ typeAnnotation === undefined &&
369
+ frames[frames.length - 1]?.kind !== 'jsx-text' &&
370
+ frames[frames.length - 1]?.kind !== 'jsx-tag' &&
371
+ expressionFunctionBodyEnded(ch, parenDepth, bracketDepth, braceContexts.length, frames.length, functionContextStack.length, conditionalContexts.length, expressionFunctionContext)) {
372
+ expressionFunctionContexts.pop();
373
+ expressionFunctionContext = expressionFunctionContexts[expressionFunctionContexts.length - 1];
374
+ }
375
+ if (typeAnnotation?.role === 'return' &&
376
+ tokenLineBreakBefore &&
377
+ atLexicalDepth(typeAnnotation.signature.owner) &&
378
+ atTypeRoot(typeAnnotation) &&
379
+ !typeAnnotation.typeOperandExpected &&
380
+ identifier !== undefined &&
381
+ (!TYPE_OPERAND_KEYWORDS.has(identifier.value) || identifier.value === 'const')) {
382
+ const signature = typeAnnotation.signature;
383
+ if (pendingFunctionContexts[pendingFunctionContexts.length - 1] === signature.context) {
384
+ pendingFunctionContexts.pop();
385
+ }
386
+ if (classState !== undefined && signature.context.isClassMember === true) {
387
+ classState.member = undefined;
388
+ }
389
+ typeAnnotation = undefined;
390
+ inType = false;
391
+ }
392
+ if (classState !== undefined &&
393
+ atLexicalDepth(classState) &&
394
+ (typeAnnotation === undefined ||
395
+ (typeAnnotation.role === 'field' && atTypeRoot(typeAnnotation)) ||
396
+ (typeAnnotation.role === 'return' &&
397
+ atTypeRoot(typeAnnotation) &&
398
+ (ch === ';' || ch === '}')))) {
399
+ if (tokenLineBreakBefore &&
400
+ classState.member !== undefined &&
401
+ typeAnnotation?.typeOperandExpected !== true &&
402
+ classFieldCanEnd(classState.member, tokens[tokens.length - 1], ch, identifier?.value)) {
403
+ classState.member = undefined;
404
+ typeAnnotation = undefined;
405
+ inType = false;
406
+ }
407
+ if (ch === ';' || ch === '}') {
408
+ classState.member = undefined;
409
+ if (typeAnnotation?.role === 'field') {
410
+ typeAnnotation = undefined;
411
+ inType = false;
412
+ }
413
+ }
414
+ else {
415
+ classState.member ??= {
416
+ start: tokens.length,
417
+ functionDepth: functionContextStack.length,
418
+ phase: 'head',
419
+ };
420
+ memberAtToken = classState.member;
421
+ }
422
+ }
423
+ if (lineTerminatorSinceLastToken && typeAnnotation === undefined) {
424
+ let context = expressionFunctionContexts[expressionFunctionContexts.length - 1];
425
+ while (context !== undefined &&
426
+ frames[frames.length - 1]?.kind !== 'jsx-text' &&
427
+ frames[frames.length - 1]?.kind !== 'jsx-tag' &&
428
+ 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();
430
+ context = expressionFunctionContexts[expressionFunctionContexts.length - 1];
431
+ }
432
+ lineTerminatorSinceLastToken = false;
433
+ }
434
+ const equalsOperator = ch === '='
435
+ ? classifyEquals(source, i, typeAnnotation !== undefined && atTypeRoot(typeAnnotation))
436
+ : undefined;
437
+ if (ch === '<' && typeAnnotation === undefined) {
438
+ const classHeader = pendingClassContexts[pendingClassContexts.length - 1];
439
+ if (classHeader !== undefined &&
440
+ atLexicalDepth(classHeader) &&
441
+ (tokens[tokens.length - 1]?.kind === 'identifier' ||
442
+ isPunct(tokens[tokens.length - 1], ')') ||
443
+ isPunct(tokens[tokens.length - 1], ']') ||
444
+ isPunct(tokens[tokens.length - 1], '>'))) {
445
+ typeAnnotation = {
446
+ role: 'class-header',
447
+ owner: classHeader,
448
+ parenDepth: 0,
449
+ bracketDepth: 0,
450
+ braceDepth: 0,
451
+ angleDepth: 0,
452
+ typeOperandExpected: true,
453
+ };
454
+ inType = true;
455
+ }
456
+ }
457
+ // A backtick in code mode opens a (possibly nested) template literal. Inside an annotation
458
+ // that is a template-literal type: its text stays inert and the annotation keeps running until the
459
+ // type itself ends.
460
+ if (ch === '`') {
461
+ templateStart = i;
462
+ templateStarts.push(i);
463
+ inText = true;
464
+ prevKind = 'template';
465
+ if (typeAnnotation !== undefined) {
466
+ typeAnnotation.typeOperandExpected = false;
467
+ }
468
+ i++;
469
+ continue;
470
+ }
471
+ // JSX overlay (opt-in via the `jsx` option). A `<` in expression position that is followed by a
472
+ // tag name (or `>` for a fragment) opens JSX markup: its text, tags and attributes are inert,
473
+ // while the JavaScript inside `{...}` containers is lexed normally. Expression position is the
474
+ // same test that decides a regex literal, so a comparison (`a < b`, `f() < x`) is never JSX, and
475
+ // a TypeScript type-argument list (`<T,>`, `<T extends U>`) is excluded explicitly.
476
+ if (jsx &&
477
+ typeAnnotation === undefined &&
478
+ ch === '<' &&
479
+ i + 1 < n &&
480
+ isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword, afterControlHead, closedObjectBrace, tokens[tokens.length - 1])) {
481
+ const after = source[i + 1];
482
+ if (after === '>') {
483
+ frames.push({ kind: 'jsx-text', name: null, offset: i });
484
+ pushBoundary(i);
485
+ i += 2;
486
+ continue;
487
+ }
488
+ if (isIdentifierStartAt(source, i + 1) && !jsxTagIsGeneric(source, i + 1, n)) {
489
+ const tag = readJsxTagName(source, i + 1, n);
490
+ frames.push({ kind: 'jsx-tag', name: tag.name, offset: i, nameEnd: tag.end });
491
+ pushBoundary(i);
492
+ i = tag.end;
493
+ continue;
494
+ }
495
+ }
496
+ // Type delimiters belong to the field, parameter or return signature, never to runtime code.
497
+ // Only that owner's boundary or initializer/body can end the annotation.
498
+ if (typeAnnotation !== undefined) {
499
+ const annotation = typeAnnotation;
500
+ const typeFrame = frame?.kind === 'template-expr' && frame.typeOwner?.annotation === annotation
501
+ ? frame.typeOwner
502
+ : undefined;
503
+ const atRoot = typeFrame === undefined && atTypeRoot(annotation);
504
+ const isTypeArrow = ch === '>' && isArrowHead(tokens[tokens.length - 1], i);
505
+ if (annotation.role === 'assertion' && atRoot) {
506
+ if (ch === '?' && annotation.conditionalOperand) {
507
+ annotation.conditionalDepth++;
508
+ annotation.conditionalOperand = false;
509
+ }
510
+ else if (ch === ':' && annotation.conditionalDepth > 0) {
511
+ annotation.conditionalDepth--;
512
+ }
513
+ if (ch === '(') {
514
+ annotation.importCallPending = false;
515
+ }
516
+ }
517
+ // Interpolations own depths relative to the surrounding type, which may itself be an object.
518
+ if (ch === '}' &&
519
+ (typeFrame !== undefined
520
+ ? sameTypeDepth(annotation, typeFrame.depth)
521
+ : annotation.braceDepth === 0) &&
522
+ (frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr') &&
523
+ frame.depth === 0) {
524
+ frames.pop();
525
+ pushBoundary(i);
526
+ i++;
527
+ if (frame.kind === 'template-expr') {
528
+ annotation.typeOperandExpected = false;
529
+ inText = true;
530
+ resetExpressionState();
531
+ }
532
+ else {
533
+ // Back to the enclosing JSX tag or text frame, which reads the next character itself.
534
+ endJsxElement();
535
+ }
536
+ continue;
537
+ }
538
+ if ((annotation.role === 'field' || annotation.role === 'parameter') &&
539
+ atRoot &&
540
+ ch === '=' &&
541
+ equalsOperator?.operator === 'assignment') {
542
+ if (annotation.role === 'field') {
543
+ annotation.member.phase = 'initializer';
544
+ }
545
+ else {
546
+ annotation.owner.phase = 'initializer';
547
+ }
548
+ typeAnnotation = undefined;
549
+ inType = false;
550
+ }
551
+ else if (annotation.role === 'return' &&
552
+ ch === '{' &&
553
+ atRoot &&
554
+ !annotation.typeOperandExpected) {
555
+ // A complete type was read, so this brace opens the runtime body.
556
+ typeAnnotation = undefined;
557
+ inType = false;
558
+ lineTerminatorSinceLastToken = false;
559
+ if (!annotation.signature.isFunctionKeyword) {
560
+ pendingFunctionContexts.push({
561
+ ...annotation.signature.context,
562
+ ...annotation.signature.owner,
563
+ parameterDepth: undefined,
564
+ phase: 'awaiting-body',
565
+ });
566
+ }
567
+ }
568
+ else if (isTypeArrow &&
569
+ atRoot &&
570
+ annotation.role === 'return' &&
571
+ annotation.signature.isArrowSignature &&
572
+ !functionTypeArrowContinues(source, i, n)) {
573
+ // The signature's own `=>`: everything after it is the body, not the return type.
574
+ typedArrowContext = annotation.signature.context;
575
+ typeAnnotation = undefined;
576
+ inType = false;
577
+ lineTerminatorSinceLastToken = false;
578
+ }
579
+ else if (atRoot && (ch === ';' || ch === ',' || ch === ')' || ch === ']' || ch === '}')) {
580
+ // A signature with no body of its own. Neither the annotation nor a pending signature may
581
+ // outlive it, otherwise later code (a JSX text `<`, another method body, an async arrow) is
582
+ // misread.
583
+ typeAnnotation = undefined;
584
+ inType = false;
585
+ }
586
+ else {
587
+ if (ch === '{') {
588
+ annotation.braceDepth++;
589
+ annotation.typeOperandExpected = true;
590
+ }
591
+ else if (ch === '}') {
592
+ annotation.braceDepth = Math.max(0, annotation.braceDepth - 1);
593
+ annotation.typeOperandExpected = false;
594
+ }
595
+ else if (ch === '(') {
596
+ annotation.parenDepth++;
597
+ annotation.typeOperandExpected = true;
598
+ }
599
+ else if (ch === ')') {
600
+ annotation.parenDepth = Math.max(0, annotation.parenDepth - 1);
601
+ annotation.typeOperandExpected = false;
602
+ }
603
+ else if (ch === '[') {
604
+ annotation.bracketDepth++;
605
+ annotation.typeOperandExpected = true;
606
+ }
607
+ else if (ch === ']') {
608
+ annotation.bracketDepth = Math.max(0, annotation.bracketDepth - 1);
609
+ annotation.typeOperandExpected = false;
610
+ }
611
+ else if (ch === '<') {
612
+ annotation.angleDepth++;
613
+ annotation.typeOperandExpected = true;
614
+ }
615
+ else if (isTypeArrow) {
616
+ // A function type's own arrow (`(x: A) => B`) still expects a type on its right.
617
+ annotation.typeOperandExpected = true;
618
+ }
619
+ else if (ch === '>' && annotation.angleDepth > 0) {
620
+ annotation.angleDepth--;
621
+ annotation.typeOperandExpected = false;
622
+ }
623
+ else if (ch === ':' || ch === '?' || ch === '.' || ch === '|' || ch === '&') {
624
+ annotation.typeOperandExpected = true;
625
+ }
626
+ if (annotation.role === 'class-header' &&
627
+ ch === '>' &&
628
+ !isTypeArrow &&
629
+ atTypeRoot(annotation)) {
630
+ typeAnnotation = undefined;
631
+ }
632
+ }
633
+ }
634
+ // A `}` in code mode closes the innermost open `${...}` or JSX `{...}` expression only when that
635
+ // expression has no open braces of its own; otherwise it is an ordinary brace inside it.
636
+ if (ch === '}' && typeAnnotation === undefined) {
637
+ if (frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr') {
638
+ if (frame.depth === 0) {
639
+ frames.pop();
640
+ pushBoundary(i);
641
+ i++;
642
+ if (frame.kind === 'template-expr') {
643
+ inText = true;
644
+ resetExpressionState();
645
+ }
646
+ else {
647
+ // Back to the enclosing JSX tag or text frame, which reads the next character itself.
648
+ endJsxElement();
649
+ }
650
+ continue;
651
+ }
652
+ frame.depth -= 1;
653
+ }
654
+ // Operand completion for class-field ASI is separate from the regex/division decision.
655
+ // A function/class body can finish an initializer without closing an object literal.
656
+ const closed = braceContexts.pop();
657
+ const closedMethod = closed?.role === 'function' && closed.context.isClassMember === true;
658
+ closedClassMemberBoundary = closedMethod || closed?.role === 'static-block';
659
+ closedObjectBrace =
660
+ closed?.role === 'object' ||
661
+ (closed?.role === 'function' && closed.context.isExpression) ||
662
+ (closed?.role === 'class' && closed.isExpression);
663
+ closedExpressionBrace =
664
+ closedObjectBrace ||
665
+ closedMethod ||
666
+ closed?.role === 'function' ||
667
+ closed?.role === 'class';
668
+ if (closed?.role === 'function') {
669
+ functionContextStack.pop();
670
+ }
671
+ if ((closed?.role === 'function' || closed?.role === 'static-block') &&
672
+ closed.owner !== undefined) {
673
+ closed.owner.member = undefined;
674
+ }
675
+ }
676
+ // `/` is a regex when it can start an expression, otherwise it is division.
677
+ if (ch === '/') {
678
+ if (isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword ||
679
+ (tokenLineBreakBefore &&
680
+ (isIdentifier(tokens[tokens.length - 1], 'break') ||
681
+ isIdentifier(tokens[tokens.length - 1], 'continue'))), afterControlHead, closedObjectBrace, tokens[tokens.length - 1])) {
682
+ const start = i;
683
+ i = skipRegex(source, i);
684
+ emit({ kind: 'regex', offset: start, endOffset: i });
685
+ prevKind = 'regex';
686
+ afterControlHead = false;
687
+ pendingControlKeyword = undefined;
688
+ continue;
689
+ }
690
+ emit({ kind: 'punct', value: '/', offset: i, endOffset: i + 1 });
691
+ prevKind = 'punct';
692
+ prevPunct = '/';
693
+ afterControlHead = false;
694
+ pendingControlKeyword = undefined;
695
+ i++;
696
+ continue;
697
+ }
698
+ // String literals (single or double quoted).
699
+ if (ch === "'" || ch === '"') {
700
+ const { value, next } = readString(source, i);
701
+ emit({ kind: 'string', value, offset: i, endOffset: next });
702
+ if (typeAnnotation !== undefined) {
703
+ typeAnnotation.typeOperandExpected = false;
704
+ }
705
+ prevKind = 'string';
706
+ afterControlHead = false;
707
+ pendingControlKeyword = undefined;
708
+ i = next;
709
+ continue;
710
+ }
711
+ // Identifiers.
712
+ if (identifier !== undefined) {
713
+ const start = i;
714
+ i = identifier.end;
715
+ const value = identifier.value;
716
+ if (typeAnnotation !== undefined) {
717
+ if (typeAnnotation.role === 'assertion') {
718
+ if (atLexicalDepth(typeAnnotation.owner) &&
719
+ atTypeRoot(typeAnnotation) &&
720
+ value === 'extends' &&
721
+ !typeAnnotation.typeOperandExpected) {
722
+ typeAnnotation.conditionalOperand = true;
723
+ }
724
+ typeAnnotation.importCallPending = value === 'import';
725
+ }
726
+ // `as const` completes an assertion, while prefix keywords still expect an operand.
727
+ typeAnnotation.typeOperandExpected =
728
+ TYPE_OPERAND_KEYWORDS.has(value) &&
729
+ !(typeAnnotation.role === 'assertion' && value === 'const');
730
+ }
731
+ const previousIdentifier = prevIdentifier;
732
+ const startsAssertion = (value === 'as' || value === 'satisfies') &&
733
+ !inType &&
734
+ // TypeScript permits trivia after these operators, but a preceding newline ends them.
735
+ !tokenLineBreakBefore &&
736
+ !(prevKind === 'punct' && prevPunct === '.') &&
737
+ !(prevKind === 'punct' &&
738
+ prevPunct === '}' &&
739
+ !closedObjectBrace &&
740
+ tokens[tokens.length - 1]?.assertionEnd !== true) &&
741
+ !afterControlHead &&
742
+ !prevIsExpressionKeyword &&
743
+ canStartTypeAssertion(tokens[tokens.length - 1]);
744
+ if (value === 'async') {
745
+ asyncFunctionIsExpression = functionIsExpressionPosition(prevKind, prevPunct, previousIdentifier, colonIsExpression);
746
+ if (isIdentifier(tokens[tokens.length - 1], 'default') &&
747
+ isIdentifier(tokens[tokens.length - 2], 'export')) {
748
+ asyncFunctionIsExpression = false;
749
+ }
750
+ }
751
+ prevIdentifier = value;
752
+ const isMemberName = prevKind === 'punct' && prevPunct === '.';
753
+ const classMemberName = value === 'class' &&
754
+ (insideObjectLiteral() || insideClassBody()) &&
755
+ memberDeclarationHead(tokens, tokens.length, insideClassBody(), currentClass()?.member?.start) !== undefined;
756
+ const nextSignificant = skipTrivia(source, i, n);
757
+ const classPropertyName = value === 'class' &&
758
+ (insideObjectLiteral() || insideClassBody()) &&
759
+ [':', '=', '(', ',', '}'].includes(source[nextSignificant] ?? '');
760
+ if (value === 'class' &&
761
+ !isMemberName &&
762
+ !classMemberName &&
763
+ !classPropertyName &&
764
+ typeAnnotation === undefined) {
765
+ const isExportDefault = isIdentifier(tokens[tokens.length - 1], 'default') &&
766
+ isIdentifier(tokens[tokens.length - 2], 'export');
767
+ pendingClassContexts.push({
768
+ parenDepth,
769
+ bracketDepth,
770
+ braceDepth: braceContexts.length,
771
+ 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]))),
776
+ });
777
+ }
778
+ // A keyword after `.` is a property name (e.g. `x.return`), not an expression start.
779
+ const forIterationSeparator = (value === 'of' || value === 'in') &&
780
+ !isMemberName &&
781
+ consumeForIterationSeparator(tokens, parameterContexts, {
782
+ parenDepth,
783
+ bracketDepth,
784
+ braceDepth: braceContexts.length,
785
+ frameDepth: frames.length,
786
+ });
787
+ let isExpressionKeyword = (REGEX_PRECEDING_KEYWORDS.has(value) || forIterationSeparator) && !isMemberName;
788
+ // `await`/`yield` are keywords only inside an async / generator function body. A member name
789
+ // (`obj.await`) is always a property access, never a keyword, so a following `/` divides.
790
+ if (!isMemberName) {
791
+ const expressionContext = expressionFunctionContexts[expressionFunctionContexts.length - 1];
792
+ const bodyContext = functionContextStack[functionContextStack.length - 1];
793
+ const parameterOwner = nearestFunctionParameter(parameterContexts);
794
+ const classMemberOwner = nearestClassField(braceContexts);
795
+ const parameterGrammarSuspended = parameterOwner !== undefined &&
796
+ functionContextStack.length <= parameterOwner.functionDepth &&
797
+ expressionContext?.parameterOwner !== parameterOwner;
798
+ const fieldGrammarSuspended = classMemberOwner !== undefined &&
799
+ functionContextStack.length <= classMemberOwner.functionDepth &&
800
+ expressionContext?.classMemberOwner !== classMemberOwner;
801
+ const expressionContextIsCurrent = expressionContext !== undefined &&
802
+ functionContextStack.length <= expressionContext.functionDepth
803
+ ? expressionContext
804
+ : undefined;
805
+ const functionContext = parameterGrammarSuspended || fieldGrammarSuspended
806
+ ? undefined
807
+ : (expressionContextIsCurrent ?? bodyContext);
808
+ if (value === 'await' &&
809
+ (functionContext?.async === true ||
810
+ (functionContext === undefined &&
811
+ module &&
812
+ !parameterGrammarSuspended &&
813
+ !fieldGrammarSuspended))) {
814
+ isExpressionKeyword = true;
815
+ }
816
+ else if (value === 'yield' && functionContext?.generator === true) {
817
+ isExpressionKeyword = true;
818
+ }
819
+ }
820
+ prevIsExpressionKeyword = isExpressionKeyword;
821
+ // Remember a control-statement keyword until its `(` is seen so only that head's `)` may be
822
+ // followed by a regex literal.
823
+ pendingControlKeyword = pendingControlKeywordAfter(value, isMemberName || insideObjectLiteral() || insideClassBody(), pendingControlKeyword);
824
+ // A `function` keyword (not a property name) starts a function signature. Track whether it is
825
+ // an expression (`const f = function(){}`) or a declaration (`function f(){}`), and whether it
826
+ // is async (`async function`, i.e. preceded by `async`) or a generator (`function*`, resolved
827
+ // when a following `*` is seen). The context is consumed at the body `{`.
828
+ const methodName = value === 'function' &&
829
+ (insideObjectLiteral() || insideClassBody()) &&
830
+ memberDeclarationHead(tokens, tokens.length, insideClassBody(), currentClass()?.member?.start) !== undefined;
831
+ if (value === 'function' && !isMemberName && !methodName && typeAnnotation === undefined) {
832
+ // `async function` is only async without a line terminator between the two words: a line
833
+ // break ends the `async` statement, so the declaration that follows is an ordinary one.
834
+ const isAsync = previousIdentifier === 'async' && prevKind === 'identifier' && !tokenLineBreakBefore;
835
+ pendingFunctionContexts.push({
836
+ async: isAsync,
837
+ generator: false,
838
+ isExpression: isAsync
839
+ ? asyncFunctionIsExpression
840
+ : !(isIdentifier(tokens[tokens.length - 1], 'default') &&
841
+ isIdentifier(tokens[tokens.length - 2], 'export')) &&
842
+ functionIsExpressionPosition(prevKind, prevPunct, previousIdentifier, colonIsExpression),
843
+ parenDepth,
844
+ bracketDepth,
845
+ braceDepth: braceContexts.length,
846
+ frameDepth: frames.length,
847
+ parameterDepth: undefined,
848
+ phase: 'awaiting-parameters',
849
+ });
850
+ }
851
+ emit({
852
+ kind: 'identifier',
853
+ value,
854
+ offset: start,
855
+ endOffset: i,
856
+ ...(isMemberName ? { propertyName: true } : {}),
857
+ });
858
+ if (startsAssertion) {
859
+ typeAnnotation = {
860
+ role: 'assertion',
861
+ conditionalDepth: 0,
862
+ conditionalOperand: false,
863
+ importCallPending: false,
864
+ owner: {
865
+ parenDepth,
866
+ bracketDepth,
867
+ braceDepth: braceContexts.length,
868
+ frameDepth: frames.length,
869
+ },
870
+ parenDepth: 0,
871
+ bracketDepth: 0,
872
+ braceDepth: 0,
873
+ angleDepth: 0,
874
+ typeOperandExpected: true,
875
+ };
876
+ }
877
+ prevKind = 'identifier';
878
+ afterControlHead = false;
879
+ continue;
880
+ }
881
+ // Numbers end an expression, so they affect the regex/division decision.
882
+ if (isNumberStart(ch)) {
883
+ const start = i;
884
+ i++;
885
+ while (i < n) {
886
+ const part = source[i];
887
+ if (part === undefined || !(isIdentifierPartAt(source, i) || part === '.')) {
888
+ break;
889
+ }
890
+ i++;
891
+ }
892
+ emit({ kind: 'number', offset: start, endOffset: i });
893
+ if (typeAnnotation !== undefined) {
894
+ typeAnnotation.typeOperandExpected = false;
895
+ }
896
+ prevKind = 'number';
897
+ afterControlHead = false;
898
+ pendingControlKeyword = undefined;
899
+ continue;
900
+ }
901
+ if (ch === ':' && typeAnnotation === undefined) {
902
+ const parameter = parameterContexts[parameterContexts.length - 1];
903
+ if (previousSignature !== undefined && atLexicalDepth(previousSignature.owner)) {
904
+ typeAnnotation = {
905
+ role: 'return',
906
+ signature: previousSignature,
907
+ parenDepth: 0,
908
+ bracketDepth: 0,
909
+ braceDepth: 0,
910
+ angleDepth: 0,
911
+ typeOperandExpected: true,
912
+ };
913
+ inType = true;
914
+ }
915
+ else if (classState !== undefined &&
916
+ memberAtToken !== undefined &&
917
+ (memberAtToken.phase === 'field' || memberAtToken.phase === 'head')) {
918
+ typeAnnotation = {
919
+ role: 'field',
920
+ owner: classState,
921
+ member: memberAtToken,
922
+ parenDepth: 0,
923
+ bracketDepth: 0,
924
+ braceDepth: 0,
925
+ angleDepth: 0,
926
+ typeOperandExpected: true,
927
+ };
928
+ memberAtToken.phase = 'type';
929
+ inType = true;
930
+ }
931
+ else if (parameter?.signature !== undefined &&
932
+ parameter.phase === 'binding' &&
933
+ atLexicalDepth(parameter.depth)) {
934
+ typeAnnotation = {
935
+ role: 'parameter',
936
+ owner: parameter,
937
+ parenDepth: 0,
938
+ bracketDepth: 0,
939
+ braceDepth: 0,
940
+ angleDepth: 0,
941
+ typeOperandExpected: true,
942
+ };
943
+ parameter.phase = 'type';
944
+ inType = true;
945
+ }
946
+ }
947
+ // Any other single character is punctuation. This (and the `:`/`;`/`}` handlers below) is the
948
+ // only place that pushes a conditionalContext, and it is reached solely while scanning real
949
+ // JavaScript: template text, JSX markup and `${...}`/`{...}` expression bodies are consumed by
950
+ // the text-mode gates above, so inert punctuation inside them never contributes a conditional.
951
+ // A `?` in template text is a plain character, not a ternary, so it must not leak here.
952
+ if (!inType &&
953
+ ch === '?' &&
954
+ (memberAtToken === undefined || memberAtToken.phase === 'initializer') &&
955
+ source[i + 1] !== '?' &&
956
+ source[i - 1] !== '?' &&
957
+ source[i + 1] !== '.') {
958
+ conditionalContexts.push({
959
+ parenDepth,
960
+ bracketDepth,
961
+ braceDepth: braceContexts.length,
962
+ frameDepth: frames.length,
963
+ });
964
+ }
965
+ else if (!inType && ch === ':') {
966
+ const isConditional = conditional !== undefined && atLexicalDepth(conditional);
967
+ if (isConditional) {
968
+ conditionalContexts.pop();
969
+ }
970
+ colonIsExpression = isConditional || insideObjectLiteral();
971
+ }
972
+ else if (!inType && ch === ';' && conditional !== undefined && atLexicalDepth(conditional)) {
973
+ conditionalContexts.pop();
974
+ }
975
+ if (!inType && ch === '{') {
976
+ // Record what this brace opens, so its `}` knows whether an expression or a statement ended.
977
+ let isObject = opensObjectLiteral(prevKind, prevPunct, prevIdentifier, prevIsExpressionKeyword, frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr', colonIsExpression, tokenLineBreakBefore);
978
+ const pending = pendingFunctionContexts[pendingFunctionContexts.length - 1];
979
+ let context;
980
+ if (pending?.phase === 'awaiting-body' && atLexicalDepth(pending)) {
981
+ context = pendingFunctionContexts.pop();
982
+ }
983
+ if (context !== undefined) {
984
+ isObject = false;
985
+ }
986
+ const classExpression = !isObject && context === undefined
987
+ ? takeClassBodyContext(pendingClassContexts, {
988
+ parenDepth,
989
+ bracketDepth,
990
+ braceDepth: braceContexts.length,
991
+ frameDepth: frames.length,
992
+ })
993
+ : undefined;
994
+ const isClassBody = classExpression !== undefined;
995
+ opensClassMemberBoundary = isClassBody;
996
+ const isStaticBlock = !isObject &&
997
+ !isClassBody &&
998
+ insideClassBody() &&
999
+ isIdentifier(tokens[tokens.length - 1], 'static');
1000
+ // A statement-block brace that consumes a pending function signature is a function body. A
1001
+ // function expression used as a value ends an expression (so a following `/` divides), while a
1002
+ // declaration in statement position starts a block (so a following `/` may be a regex).
1003
+ //
1004
+ // Methods consume the signature captured at their parameter opener. Arrows use their
1005
+ // contiguous operator; neither a control head nor an unrelated call can claim this brace.
1006
+ if (context === undefined && !isObject && !isClassBody) {
1007
+ const last = tokens[tokens.length - 1];
1008
+ context =
1009
+ isPunct(last, '>') && isArrowHead(tokens[tokens.length - 2])
1010
+ ? inferArrowContext(tokens, tokens.length - 2)
1011
+ : previousSignature !== undefined &&
1012
+ !previousSignature.isArrowSignature &&
1013
+ atLexicalDepth(previousSignature.owner)
1014
+ ? previousSignature.context
1015
+ : undefined;
1016
+ }
1017
+ if (!isObject && context !== undefined) {
1018
+ functionContextStack.push(context);
1019
+ }
1020
+ const owner = currentClass();
1021
+ if (context !== undefined) {
1022
+ braceContexts.push({
1023
+ role: 'function',
1024
+ context,
1025
+ ...(context.isClassMember === true && owner !== undefined ? { owner } : {}),
1026
+ });
1027
+ }
1028
+ else if (isClassBody) {
1029
+ braceContexts.push({
1030
+ role: 'class',
1031
+ isExpression: classExpression.isExpression,
1032
+ state: {
1033
+ parenDepth,
1034
+ bracketDepth,
1035
+ braceDepth: braceContexts.length + 1,
1036
+ frameDepth: frames.length,
1037
+ member: undefined,
1038
+ },
1039
+ });
1040
+ }
1041
+ else if (isStaticBlock && owner !== undefined) {
1042
+ braceContexts.push({ role: 'static-block', owner });
1043
+ }
1044
+ else {
1045
+ braceContexts.push({ role: isObject ? 'object' : 'block' });
1046
+ }
1047
+ // Count the brace inside the innermost open `${...}` or JSX `{...}` expression so its closing
1048
+ // `}` is found.
1049
+ if (frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr') {
1050
+ frame.depth += 1;
1051
+ }
1052
+ }
1053
+ else if (!inType && ch === '(') {
1054
+ // A `(` after a control keyword opens a control head; only its matching `)` may then be
1055
+ // followed by a regex literal, so the nesting depth of every `(` has to be tracked.
1056
+ 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)
1065
+ ? undefined
1066
+ : signatureCandidate;
1067
+ parameterContexts.push({
1068
+ openIndex: tokens.length,
1069
+ signature: signature === undefined
1070
+ ? undefined
1071
+ : {
1072
+ ...signature,
1073
+ owner: {
1074
+ parenDepth,
1075
+ bracketDepth,
1076
+ braceDepth: braceContexts.length,
1077
+ frameDepth: frames.length,
1078
+ },
1079
+ },
1080
+ control: pendingControlKeyword !== undefined,
1081
+ controlKeyword: pendingControlKeyword,
1082
+ functionDepth: functionContextStack.length,
1083
+ depth: {
1084
+ parenDepth: parenDepth + 1,
1085
+ bracketDepth,
1086
+ braceDepth: braceContexts.length,
1087
+ frameDepth: frames.length,
1088
+ },
1089
+ phase: 'binding',
1090
+ ...(pendingControlKeyword === 'for' ? { forPhase: 'binding' } : {}),
1091
+ });
1092
+ if (pending?.phase === 'awaiting-parameters' &&
1093
+ atLexicalDepth(pending) &&
1094
+ signature?.isFunctionKeyword === true) {
1095
+ pending.parameterDepth = parenDepth + 1;
1096
+ pending.phase = 'parameters';
1097
+ }
1098
+ parenDepth++;
1099
+ pendingControlKeyword = undefined;
1100
+ afterControlHead = false;
1101
+ }
1102
+ else if (!inType && ch === '[') {
1103
+ bracketDepth++;
1104
+ }
1105
+ else if (!inType && ch === ')') {
1106
+ const pending = pendingFunctionContexts[pendingFunctionContexts.length - 1];
1107
+ if (pending?.parameterDepth === parenDepth &&
1108
+ bracketDepth === pending.bracketDepth &&
1109
+ braceContexts.length === pending.braceDepth &&
1110
+ frames.length === pending.frameDepth) {
1111
+ pending.phase = 'awaiting-body';
1112
+ }
1113
+ const parameters = parameterContexts.pop();
1114
+ matchingOpen = parameters?.openIndex;
1115
+ closedParameterSignature = parameters?.signature;
1116
+ afterControlHead = parameters?.control === true;
1117
+ if (parenDepth > 0) {
1118
+ parenDepth--;
1119
+ }
1120
+ }
1121
+ else if (!inType && ch === ']') {
1122
+ if (bracketDepth > 0) {
1123
+ bracketDepth--;
1124
+ }
1125
+ }
1126
+ if (!inType && (ch === '+' || ch === '-') && source[i + 1] === ch) {
1127
+ const previous = tokens[tokens.length - 1];
1128
+ const postfix = !tokenLineBreakBefore &&
1129
+ previous !== undefined &&
1130
+ isMemberExpressionEnd(previous) &&
1131
+ !prevIsExpressionKeyword;
1132
+ emit({
1133
+ kind: 'punct',
1134
+ value: ch + ch,
1135
+ offset: i,
1136
+ endOffset: i + 2,
1137
+ expressionEnd: postfix,
1138
+ });
1139
+ prevKind = 'punct';
1140
+ prevPunct = ch + ch;
1141
+ i += 2;
1142
+ continue;
1143
+ }
1144
+ // An arrow's expression body has no brace where its function context can be consumed. Record
1145
+ // its bounded context after `=>`; a following delimiter removes it again.
1146
+ if (!inType && ch === '>' && isArrowHead(tokens[tokens.length - 1], i)) {
1147
+ const context = typedArrowContext ?? inferArrowContext(tokens, tokens.length - 1);
1148
+ const next = skipTrivia(source, i + 1, n);
1149
+ if (context !== undefined) {
1150
+ 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
+ }
1162
+ }
1163
+ else {
1164
+ const parameterOwner = nearestFunctionParameter(parameterContexts);
1165
+ const classMemberOwner = nearestClassField(braceContexts);
1166
+ expressionFunctionContexts.push({
1167
+ ...context,
1168
+ isExpression: true,
1169
+ parenDepth,
1170
+ bracketDepth,
1171
+ braceDepth: braceContexts.length,
1172
+ frameDepth: frames.length,
1173
+ functionDepth: functionContextStack.length,
1174
+ conditionalDepth: conditionalContexts.length,
1175
+ ...(parameterOwner === undefined ? {} : { parameterOwner }),
1176
+ ...(classMemberOwner === undefined ? {} : { classMemberOwner }),
1177
+ });
1178
+ }
1179
+ }
1180
+ }
1181
+ // A `*` right after `function` (whitespace/comments aside) marks a generator: `function*` and
1182
+ // `function *`. It is recorded on the pending signature, consumed at the body `{`.
1183
+ const pending = pendingFunctionContexts[pendingFunctionContexts.length - 1];
1184
+ const parameter = parameterContexts[parameterContexts.length - 1];
1185
+ if (ch === ';' && parameter?.forPhase !== undefined && atLexicalDepth(parameter.depth)) {
1186
+ parameter.forPhase = 'regular';
1187
+ }
1188
+ if (!inType && parameter !== undefined && atLexicalDepth(parameter.depth)) {
1189
+ if (ch === ',') {
1190
+ parameter.phase = 'binding';
1191
+ }
1192
+ else if (equalsOperator?.operator === 'assignment') {
1193
+ parameter.phase = 'initializer';
1194
+ }
1195
+ }
1196
+ if (!inType &&
1197
+ ch === '*' &&
1198
+ prevKind === 'identifier' &&
1199
+ prevIdentifier === 'function' &&
1200
+ pending !== undefined) {
1201
+ pendingFunctionContexts[pendingFunctionContexts.length - 1] = {
1202
+ ...pending,
1203
+ generator: true,
1204
+ };
1205
+ }
1206
+ // A callback's statements cannot invalidate a signature outside its body or parameter list.
1207
+ if (!inType && ch === ';' && pending !== undefined && atLexicalDepth(pending)) {
1208
+ pendingFunctionContexts.pop();
1209
+ }
1210
+ const previousToken = tokens[tokens.length - 1];
1211
+ emit({
1212
+ kind: 'punct',
1213
+ value: ch,
1214
+ offset: i,
1215
+ endOffset: i + 1,
1216
+ ...(ch === '!' &&
1217
+ source[i + 1] !== '=' &&
1218
+ previousToken !== undefined &&
1219
+ isMemberExpressionEnd(previousToken) &&
1220
+ !prevIsExpressionKeyword
1221
+ ? { expressionEnd: true }
1222
+ : {}),
1223
+ ...equalsOperator,
1224
+ ...(matchingOpen !== undefined ? { matchingOpen } : {}),
1225
+ ...(closedClassMemberBoundary || opensClassMemberBoundary || (ch === ';' && insideClassBody())
1226
+ ? { memberSeparator: true }
1227
+ : {}),
1228
+ ...(closedExpressionBrace ? { expressionEnd: true } : {}),
1229
+ });
1230
+ prevKind = 'punct';
1231
+ prevPunct = ch;
1232
+ i++;
1233
+ }
1234
+ // Reaching the end of the source with an open frame means the template literal, the JSX element or
1235
+ // the JSX expression container was never closed. The innermost open frame is the one that failed.
1236
+ const openFrame = frames[frames.length - 1];
1237
+ if (inText || openFrame?.kind === 'template-expr') {
1238
+ throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', inText ? templateStart : (openFrame?.offset ?? templateStart));
1239
+ }
1240
+ if (openFrame?.kind === 'jsx-expr') {
1241
+ throw new ModuleSpecifierError('Unterminated JSX expression', 'invalid-source', openFrame.offset);
1242
+ }
1243
+ if (openFrame !== undefined) {
1244
+ throw new ModuleSpecifierError('Unterminated JSX element', 'invalid-source', openFrame.offset);
1245
+ }
1246
+ return tokens;
1247
+ }
1248
+ // A `<` followed by an identifier that begins a generic type-argument list (e.g. `<T,>`, `<T = X>`,
1249
+ // `<T extends U>`, `<const T,>`) or a generic function signature (`<T>(x: T) => T`) is not a JSX
1250
+ // element. `<T>` alone in expression position remains ambiguous and is treated as JSX.
1251
+ function jsxTagIsGeneric(source, i, n) {
1252
+ try {
1253
+ if (!isIdentifierStartAt(source, i)) {
1254
+ return false;
1255
+ }
1256
+ const identifier = readIdentifier(source, i);
1257
+ let k = identifier.end;
1258
+ const name = identifier.value;
1259
+ k = skipTrivia(source, k, n);
1260
+ const next = source[k];
1261
+ if (next === ',' || next === '=') {
1262
+ return true;
1263
+ }
1264
+ if (next === '>') {
1265
+ const j = skipTrivia(source, k + 1, n);
1266
+ if (source[j] === '(') {
1267
+ return arrowFollowsParameters(source, j, n);
1268
+ }
1269
+ }
1270
+ let end = k;
1271
+ while (end < n && isIdentifierPartAt(source, end)) {
1272
+ end++;
1273
+ }
1274
+ if (source.slice(k, end) === 'extends') {
1275
+ // `extends` is a generic constraint only when it introduces a constraint type that closes
1276
+ // the type-parameter list. `implements` is not a TypeScript type-parameter constraint and
1277
+ // remains a JSX boolean attribute.
1278
+ const j = skipTrivia(source, end, n);
1279
+ const after = source[j];
1280
+ if (after !== '=' &&
1281
+ after !== '"' &&
1282
+ after !== "'" &&
1283
+ after !== '`' &&
1284
+ after !== '>' &&
1285
+ after !== '/') {
1286
+ return true;
1287
+ }
1288
+ }
1289
+ // `const T, ...`: a TypeScript const type parameter, whose real name follows the modifier.
1290
+ if (name === 'const' && k < n && isIdentifierStartAt(source, k)) {
1291
+ return jsxTagIsGeneric(source, k, n);
1292
+ }
1293
+ return false;
1294
+ }
1295
+ catch (error) {
1296
+ if (error instanceof ModuleSpecifierError) {
1297
+ // This is speculative: an apostrophe or `/*` may be inert JSX children, not broken JS.
1298
+ // Let the normal JSX path scan it; genuinely incomplete markup will still fail there.
1299
+ return false;
1300
+ }
1301
+ throw error;
1302
+ }
1303
+ }
1304
+ // Scan a generic function signature's parameter list starting at its opening `(` (offset `open`).
1305
+ // Returns true only when the list is closed and `=>` follows, so callers can treat `<T>(...) =>` as
1306
+ // a generic signature rather than a JSX element.
1307
+ //
1308
+ // Brackets inside strings, template literals and regex literals are inert: a `)` inside any of them
1309
+ // must not be counted as the parameter-list close. Template interpolations are skipped opaquely
1310
+ // because a template literal is a single argument value, so its internal parentheses never shape
1311
+ // the parameter list. Regex-vs-division tracking mirrors the main tokenizer so a regex default
1312
+ // (for example `= /a/)`) is skipped instead of read as division.
1313
+ function arrowFollowsParameters(source, open, n) {
1314
+ let depth = 0;
1315
+ let prevKind = 'start';
1316
+ let prevPunct;
1317
+ let k = open;
1318
+ while (k < n) {
1319
+ k = skipTrivia(source, k, n);
1320
+ const ch = source[k];
1321
+ if (ch === undefined) {
1322
+ break;
1323
+ }
1324
+ // String literals: their brackets and slashes are inert text, so skip the whole value.
1325
+ if (ch === "'" || ch === '"') {
1326
+ k = readString(source, k).next;
1327
+ prevKind = 'string';
1328
+ continue;
1329
+ }
1330
+ // Template literals: skip their text and interpolation so a `)` inside a template is not
1331
+ // counted as the parameter-list close.
1332
+ if (ch === '`') {
1333
+ k = skipTemplateLiteral(source, k);
1334
+ prevKind = 'template';
1335
+ continue;
1336
+ }
1337
+ // Regex literals: a `/` that begins a regex must be skipped so its `)` stays inside the pattern
1338
+ // rather than closing the parameter list.
1339
+ if (ch === '/' && isRegexStart(prevKind, prevPunct, false, false, false)) {
1340
+ k = skipRegex(source, k);
1341
+ prevKind = 'regex';
1342
+ continue;
1343
+ }
1344
+ if (ch === '(') {
1345
+ depth++;
1346
+ prevKind = 'punct';
1347
+ prevPunct = '(';
1348
+ }
1349
+ else if (ch === ')') {
1350
+ depth--;
1351
+ prevKind = 'punct';
1352
+ prevPunct = ')';
1353
+ if (depth === 0) {
1354
+ const j = skipTrivia(source, k + 1, n);
1355
+ return ((source[j] === '=' && source[j + 1] === '>') ||
1356
+ (source[j] === ':' && arrowFollowsReturnType(source, j + 1, n)));
1357
+ }
1358
+ }
1359
+ else if (isIdentifierStartAt(source, k)) {
1360
+ const identifier = readIdentifier(source, k);
1361
+ k = identifier.end - 1;
1362
+ prevKind = 'identifier';
1363
+ prevPunct = undefined;
1364
+ }
1365
+ else {
1366
+ prevKind = 'punct';
1367
+ prevPunct = ch;
1368
+ }
1369
+ k++;
1370
+ }
1371
+ return false;
1372
+ }
1373
+ function arrowFollowsReturnType(source, start, n) {
1374
+ let parenDepth = 0;
1375
+ let bracketDepth = 0;
1376
+ let braceDepth = 0;
1377
+ let angleDepth = 0;
1378
+ let index = start;
1379
+ while (index < n) {
1380
+ index = skipTrivia(source, index, n);
1381
+ const ch = source[index];
1382
+ if (ch === undefined) {
1383
+ return false;
1384
+ }
1385
+ if (ch === "'" || ch === '"') {
1386
+ index = readString(source, index).next;
1387
+ continue;
1388
+ }
1389
+ if (ch === '`') {
1390
+ index = skipTemplateLiteral(source, index);
1391
+ continue;
1392
+ }
1393
+ if (ch === '=' && source[index + 1] === '>') {
1394
+ if (parenDepth === 0 && bracketDepth === 0 && braceDepth === 0 && angleDepth === 0) {
1395
+ return true;
1396
+ }
1397
+ index += 2;
1398
+ continue;
1399
+ }
1400
+ if (ch === '(') {
1401
+ parenDepth++;
1402
+ }
1403
+ else if (ch === ')') {
1404
+ if (parenDepth === 0) {
1405
+ return false;
1406
+ }
1407
+ parenDepth--;
1408
+ }
1409
+ else if (ch === '[') {
1410
+ bracketDepth++;
1411
+ }
1412
+ else if (ch === ']') {
1413
+ if (bracketDepth === 0) {
1414
+ return false;
1415
+ }
1416
+ bracketDepth--;
1417
+ }
1418
+ else if (ch === '{') {
1419
+ braceDepth++;
1420
+ }
1421
+ else if (ch === '}') {
1422
+ if (braceDepth === 0) {
1423
+ return false;
1424
+ }
1425
+ braceDepth--;
1426
+ }
1427
+ else if (ch === '<') {
1428
+ angleDepth++;
1429
+ }
1430
+ else if (ch === '>') {
1431
+ if (angleDepth === 0) {
1432
+ return false;
1433
+ }
1434
+ angleDepth--;
1435
+ }
1436
+ else if (parenDepth === 0 &&
1437
+ bracketDepth === 0 &&
1438
+ braceDepth === 0 &&
1439
+ angleDepth === 0 &&
1440
+ (ch === ';' || ch === ',')) {
1441
+ return false;
1442
+ }
1443
+ index++;
1444
+ }
1445
+ return false;
1446
+ }
1447
+ function arrowFollowsParametersSafely(source, open, n) {
1448
+ try {
1449
+ return arrowFollowsParameters(source, open, n);
1450
+ }
1451
+ catch (error) {
1452
+ if (error instanceof ModuleSpecifierError) {
1453
+ return false;
1454
+ }
1455
+ throw error;
1456
+ }
1457
+ }
1458
+ // Skip a template literal starting at its opening backtick (offset `start`). Returns the index just
1459
+ // past the closing backtick.
1460
+ //
1461
+ // `${...}` interpolations are skipped opaquely by brace depth: the caller only needs this region to
1462
+ // be invisible so its brackets never reach the parameter-list accounting, which is why the body is
1463
+ // not re-lexed here.
1464
+ //
1465
+ // @throws {ModuleSpecifierError} on an unterminated template literal.
1466
+ function skipTemplateLiteral(source, start) {
1467
+ const n = source.length;
1468
+ let i = start + 1; // past the opening backtick
1469
+ while (i < n) {
1470
+ const ch = source[i];
1471
+ // A backslash escapes the next character (including a nested backtick or `${`).
1472
+ if (ch === '\\') {
1473
+ i += 2;
1474
+ continue;
1475
+ }
1476
+ if (ch === '`') {
1477
+ return i + 1;
1478
+ }
1479
+ if (ch === '$' && source[i + 1] === '{') {
1480
+ // Skip the interpolation until its braces balance. Nested templates/braces are handled by the
1481
+ // depth counter; a raw line terminator is invalid inside a template and stops the scan.
1482
+ let depth = 1;
1483
+ i += 2;
1484
+ while (i < n && depth > 0) {
1485
+ const inner = source[i];
1486
+ if (inner === '\n' || inner === '\r') {
1487
+ throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', start);
1488
+ }
1489
+ if (inner === '\\') {
1490
+ i += 2;
1491
+ continue;
1492
+ }
1493
+ if (inner === '{') {
1494
+ depth++;
1495
+ }
1496
+ else if (inner === '}') {
1497
+ depth--;
1498
+ }
1499
+ i++;
1500
+ }
1501
+ continue;
1502
+ }
1503
+ i++;
1504
+ }
1505
+ throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', start);
1506
+ }
1507
+ /**
1508
+ * Lex only the boundaries of a component's type arguments. These tokens preserve literal imports
1509
+ * without changing the surrounding JavaScript function/brace state or treating type braces as JSX.
1510
+ */
1511
+ function readJsxTypeArguments(source, open, n, tokens) {
1512
+ const closers = ['>'];
1513
+ 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);
1523
+ tokens.push({
1524
+ kind: 'string',
1525
+ value,
1526
+ 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,
1545
+ lineBreakBefore: trivia.lineBreak,
1546
+ });
1547
+ continue;
1548
+ }
1549
+ if (ch === '<' || ch === '(' || ch === '{' || ch === '[') {
1550
+ closers.push(ch === '<' ? '>' : ch === '(' ? ')' : ch === '{' ? '}' : ']');
1551
+ }
1552
+ else if (ch === closers[closers.length - 1] && !(ch === '>' && source[i - 1] === '=')) {
1553
+ closers.pop();
1554
+ if (closers.length === 0) {
1555
+ return i + 1;
1556
+ }
1557
+ }
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++;
1567
+ }
1568
+ throw new ModuleSpecifierError('Unterminated JSX type arguments', 'invalid-source', open);
1569
+ }
1570
+ /**
1571
+ * Whether the source between two offsets contains a line terminator.
1572
+ */
1573
+ function hasLineTerminator(source, from, to) {
1574
+ for (let k = from; k < to; k++) {
1575
+ if (isLineTerminator(source[k])) {
1576
+ return true;
1577
+ }
1578
+ }
1579
+ return false;
1580
+ }
1581
+ /** Skip whitespace and comments without interpreting any target-project code. */
1582
+ function skipTrivia(source, start, n) {
1583
+ return readTrivia(source, start, n).end;
1584
+ }
1585
+ function readTrivia(source, start, n) {
1586
+ let i = start;
1587
+ let lineBreak = false;
1588
+ while (i < n) {
1589
+ if (isWhitespace(source[i])) {
1590
+ lineBreak ||= isLineTerminator(source[i]);
1591
+ i++;
1592
+ continue;
1593
+ }
1594
+ if (source[i] === '/' && source[i + 1] === '/') {
1595
+ i += 2;
1596
+ while (i < n && !isLineTerminator(source[i])) {
1597
+ i++;
1598
+ }
1599
+ continue;
1600
+ }
1601
+ if (source[i] === '/' && source[i + 1] === '*') {
1602
+ const start = i;
1603
+ i = skipBlockComment(source, i);
1604
+ lineBreak ||= hasLineTerminator(source, start, i);
1605
+ continue;
1606
+ }
1607
+ break;
1608
+ }
1609
+ return { end: i, lineBreak };
1610
+ }
1611
+ /**
1612
+ * Type keywords that still expect a type after them, so a `{` following one opens an object type and
1613
+ * not a function body (`T extends { ok: boolean } ? A : B`).
1614
+ */
1615
+ const TYPE_OPERAND_KEYWORDS = new Set([
1616
+ 'abstract',
1617
+ 'as',
1618
+ 'asserts',
1619
+ 'await',
1620
+ 'const',
1621
+ 'extends',
1622
+ 'in',
1623
+ 'infer',
1624
+ 'is',
1625
+ 'keyof',
1626
+ 'new',
1627
+ 'readonly',
1628
+ 'satisfies',
1629
+ 'typeof',
1630
+ ]);
1631
+ /**
1632
+ * Whether a root-level `=>` inside a return annotation is an arrow of the return type itself rather
1633
+ * than the signature's runtime arrow. A function type keeps a second arrow at the same nesting level
1634
+ * (`(): (x: number) => void => {}`); once the return type is over, the next significant token is the
1635
+ * body (`{`), a statement terminator or a delimiter that closes the surrounding code.
1636
+ */
1637
+ function functionTypeArrowContinues(source, arrowEnd, n) {
1638
+ let depth = 0;
1639
+ for (let i = arrowEnd + 1; i < n; i++) {
1640
+ const ch = source[i];
1641
+ if (ch === '(' || ch === '[' || ch === '<') {
1642
+ depth++;
1643
+ }
1644
+ else if (ch === '>' && depth > 0) {
1645
+ depth--;
1646
+ }
1647
+ else if (ch === ')' || ch === ']') {
1648
+ if (depth === 0) {
1649
+ return false;
1650
+ }
1651
+ depth--;
1652
+ }
1653
+ else if (depth === 0) {
1654
+ if (ch === '{' || ch === ';' || ch === ',') {
1655
+ return false;
1656
+ }
1657
+ if (ch === '=' && source[i + 1] === '>') {
1658
+ return true;
1659
+ }
1660
+ }
1661
+ }
1662
+ return false;
1663
+ }
1664
+ /**
1665
+ * Index of the token that names a `(` parameter list. A generic signature keeps its type-parameter
1666
+ * list between the name and the `(` (`async function f<T>(): T {}`, `async <T,>(value: T) => {}`,
1667
+ * `async check<T>(): T {}`), so a balanced `<...>` directly before the `(` is skipped. The index of
1668
+ * the token directly before the `(` is returned unchanged when no such list precedes it.
1669
+ */
1670
+ function signatureNameIndex(tokens, openIndex) {
1671
+ let index = openIndex - 1;
1672
+ const angle = tokens[index];
1673
+ if (angle?.kind !== 'punct' || angle.value !== '>') {
1674
+ return index;
1675
+ }
1676
+ const closers = [];
1677
+ while (index >= 0) {
1678
+ const token = tokens[index];
1679
+ if (token === undefined) {
1680
+ break;
1681
+ }
1682
+ if (token.kind !== 'punct') {
1683
+ index--;
1684
+ continue;
1685
+ }
1686
+ if (token.value === '>' && isArrowHead(tokens[index - 1], token.offset)) {
1687
+ index -= 2;
1688
+ continue;
1689
+ }
1690
+ if (['>', ')', ']', '}'].includes(token.value)) {
1691
+ closers.push(token.value);
1692
+ }
1693
+ else if (['<', '(', '[', '{'].includes(token.value)) {
1694
+ const expected = token.value === '<' ? '>' : token.value === '(' ? ')' : token.value === '[' ? ']' : '}';
1695
+ if (closers.pop() !== expected) {
1696
+ break;
1697
+ }
1698
+ if (closers.length === 0) {
1699
+ return index - 1;
1700
+ }
1701
+ }
1702
+ else if (token.value === ';' &&
1703
+ token.syntheticBoundary !== true &&
1704
+ closers[closers.length - 1] === '>') {
1705
+ break;
1706
+ }
1707
+ index--;
1708
+ }
1709
+ // The `>` does not close a balanced type-parameter list, so the token before `(` names the list.
1710
+ return openIndex - 1;
1711
+ }
1712
+ /**
1713
+ * Modifiers that may sit between a member separator and a method name.
1714
+ *
1715
+ * `new`, `typeof`, `void` and `await` are absent on purpose: they are operators, so an identifier
1716
+ * after them belongs to an expression and never starts a member declaration.
1717
+ */
1718
+ const MEMBER_MODIFIERS = new Set([
1719
+ 'abstract',
1720
+ 'async',
1721
+ 'get',
1722
+ 'override',
1723
+ 'private',
1724
+ 'protected',
1725
+ 'public',
1726
+ 'set',
1727
+ 'static',
1728
+ ]);
1729
+ /** Punctuation that can start a member declaration inside an object literal, class body or interface. */
1730
+ const MEMBER_SEPARATORS = new Set(['{', ',', ';']);
1731
+ /**
1732
+ * Index of the `@` that starts the decorator ending at `index`, or -1 when the tokens ending there
1733
+ * are not a decorator. Only the tail of a decorator is known from the member side, so the
1734
+ * LeftHandSideExpression of `@dec`, `@dec(1)`, `@a.b` and `@(dec)` is scanned backwards.
1735
+ */
1736
+ function decoratorStartIndex(tokens, index) {
1737
+ let depth = 0;
1738
+ for (let k = index; k >= 0; k--) {
1739
+ const token = tokens[k];
1740
+ if (token === undefined) {
1741
+ return -1;
1742
+ }
1743
+ if (token.kind !== 'punct') {
1744
+ continue;
1745
+ }
1746
+ if (token.value === ')' || token.value === ']' || token.value === '}') {
1747
+ depth++;
1748
+ }
1749
+ else if (token.value === '(' || token.value === '[' || token.value === '{') {
1750
+ // An unmatched opener means the scan left the decorator expression.
1751
+ if (depth === 0) {
1752
+ return -1;
1753
+ }
1754
+ depth--;
1755
+ }
1756
+ else if (depth === 0) {
1757
+ if (token.value === '@') {
1758
+ return k;
1759
+ }
1760
+ // Only property access (`@a.b`, `@a?.b`) continues a decorator name.
1761
+ if (token.value !== '.' && token.value !== '?') {
1762
+ return -1;
1763
+ }
1764
+ }
1765
+ }
1766
+ return -1;
1767
+ }
1768
+ /**
1769
+ * The async/generator flags of the member declaration whose name is the token at `nameIndex`, or
1770
+ * `undefined` when no member declaration starts there.
1771
+ *
1772
+ * Class headers are bounded by the current member's forward-tracked start. Objects use explicit
1773
+ * separators, never class-field ASI. Only modifiers and decorators may precede the name.
1774
+ */
1775
+ function memberDeclarationHead(tokens, nameIndex, insideClassBody, memberStart) {
1776
+ // A computed member (`[key]() {}`) is named by the `]` that closes its key.
1777
+ let index = nameIndex - 1;
1778
+ if (isPunct(tokens[nameIndex], ']')) {
1779
+ const open = matchingOpenBracket(tokens, nameIndex);
1780
+ if (open < 0) {
1781
+ return undefined;
1782
+ }
1783
+ index = open - 1;
1784
+ }
1785
+ let async = false;
1786
+ let generator = false;
1787
+ for (;;) {
1788
+ while (index >= (memberStart ?? 0)) {
1789
+ const token = tokens[index];
1790
+ if (isPunct(token, '*') || isPunct(token, '#')) {
1791
+ generator ||= isPunct(token, '*');
1792
+ index--;
1793
+ continue;
1794
+ }
1795
+ if (token?.kind === 'identifier') {
1796
+ const nextToken = tokens[index + 1];
1797
+ const startsMethodModifier = nextToken !== undefined && nextToken.lineBreakBefore !== true;
1798
+ if (token.value === 'async' && startsMethodModifier) {
1799
+ async = true;
1800
+ index--;
1801
+ continue;
1802
+ }
1803
+ if (MEMBER_MODIFIERS.has(token.value) && startsMethodModifier) {
1804
+ index--;
1805
+ continue;
1806
+ }
1807
+ }
1808
+ break;
1809
+ }
1810
+ if (insideClassBody && memberStart !== undefined && index < memberStart) {
1811
+ return { async, generator };
1812
+ }
1813
+ const start = tokens[index];
1814
+ if (start?.kind === 'punct' &&
1815
+ (MEMBER_SEPARATORS.has(start.value) ||
1816
+ (start.value === '}' && start.memberSeparator === true))) {
1817
+ return { async, generator };
1818
+ }
1819
+ // Decorators precede the modifiers they apply to, and each one ends just before them.
1820
+ const decorator = decoratorStartIndex(tokens, index);
1821
+ if (decorator < 0) {
1822
+ return undefined;
1823
+ }
1824
+ index = decorator - 1;
1825
+ }
1826
+ }
1827
+ function isPunct(token, value) {
1828
+ return token?.kind === 'punct' && token.value === value;
1829
+ }
1830
+ function classifyEquals(source, offset, atTypeBoundary = false) {
1831
+ const next = source[offset + 1];
1832
+ const previous = source[offset - 1];
1833
+ if (next === '>') {
1834
+ return { operator: 'arrow', operatorEndOffset: offset + 2 };
1835
+ }
1836
+ if (next === '=' ||
1837
+ (previous !== undefined &&
1838
+ '=!<>+-*/%&|^?'.includes(previous) &&
1839
+ !(atTypeBoundary && previous === '>'))) {
1840
+ let end = offset + 1;
1841
+ while (source[end] === '=') {
1842
+ end++;
1843
+ }
1844
+ return { operator: 'other', operatorEndOffset: end };
1845
+ }
1846
+ return { operator: 'assignment', operatorEndOffset: offset + 1 };
1847
+ }
1848
+ function readIdentifier(source, start) {
1849
+ let end = start;
1850
+ let value = '';
1851
+ let first = true;
1852
+ while (end < source.length) {
1853
+ const escaped = isUnicodeEscapeStart(source, end);
1854
+ const segment = escaped ? readUnicodeEscape(source, end) : readCodePoint(source, end);
1855
+ if (segment === undefined) {
1856
+ break;
1857
+ }
1858
+ const allowed = first
1859
+ ? isIdentifierStartCharacter(segment.character)
1860
+ : isIdentifierPartCharacter(segment.character);
1861
+ if (!allowed) {
1862
+ if (escaped) {
1863
+ throw new ModuleSpecifierError('Invalid Unicode escape in identifier', 'invalid-source', end);
1864
+ }
1865
+ break;
1866
+ }
1867
+ value += segment.character;
1868
+ end = segment.end;
1869
+ first = false;
1870
+ }
1871
+ if (first) {
1872
+ throw new ModuleSpecifierError('Invalid identifier', 'invalid-source', start);
1873
+ }
1874
+ return { value, end };
1875
+ }
1876
+ function isArrowHead(token, greaterOffset) {
1877
+ return (token?.kind === 'punct' &&
1878
+ token.operator === 'arrow' &&
1879
+ (greaterOffset === undefined || token.operatorEndOffset === greaterOffset + 1));
1880
+ }
1881
+ function lineBreakBetween(tokens, start, end) {
1882
+ for (let index = start + 1; index <= end; index++) {
1883
+ if (tokens[index]?.lineBreakBefore === true) {
1884
+ return true;
1885
+ }
1886
+ }
1887
+ return false;
1888
+ }
1889
+ function isMemberExpressionEnd(token) {
1890
+ if (token.assertionEnd === true) {
1891
+ return true;
1892
+ }
1893
+ if (token.kind === 'identifier') {
1894
+ return (token.propertyName === true ||
1895
+ (!REGEX_PRECEDING_KEYWORDS.has(token.value) && !['await', 'yield'].includes(token.value)));
1896
+ }
1897
+ return (EXPRESSION_ENDING.has(token.kind) ||
1898
+ isPunct(token, ')') ||
1899
+ isPunct(token, ']') ||
1900
+ (token.kind === 'punct' && token.expressionEnd === true));
1901
+ }
1902
+ function advanceMember(member, token, previous) {
1903
+ if (member.phase === 'initializer' || member.phase === 'parameters') {
1904
+ return;
1905
+ }
1906
+ if (isPunct(token, '@')) {
1907
+ member.phase = 'decorator';
1908
+ return;
1909
+ }
1910
+ if (member.phase === 'decorator') {
1911
+ if (token.lineBreakBefore !== true &&
1912
+ !isPunct(previous, ')') &&
1913
+ !(previous?.kind === 'identifier' && token.kind === 'identifier')) {
1914
+ return;
147
1915
  }
148
- // Line comment: it ends at any ECMAScript line terminator (LF, CR, LS or PS), not only `\n`.
149
- if (ch === '/' && source[i + 1] === '/') {
150
- i += 2;
151
- while (i < n && !isLineTerminator(source[i])) {
152
- i++;
153
- }
154
- continue;
1916
+ member.phase = 'head';
1917
+ }
1918
+ if (token.kind === 'punct' && token.operator === 'assignment') {
1919
+ member.phase = 'initializer';
1920
+ }
1921
+ else if (isPunct(token, ':')) {
1922
+ member.phase = 'type';
1923
+ }
1924
+ else if (isPunct(token, '(') && member.phase !== 'type') {
1925
+ member.phase = 'parameters';
1926
+ }
1927
+ else if (member.phase === 'head' &&
1928
+ ((token.kind === 'identifier' &&
1929
+ !MEMBER_MODIFIERS.has(token.value) &&
1930
+ !['declare', 'readonly'].includes(token.value)) ||
1931
+ token.kind === 'string' ||
1932
+ token.kind === 'number' ||
1933
+ isPunct(token, '['))) {
1934
+ member.phase = 'field';
1935
+ }
1936
+ }
1937
+ function classFieldCanEnd(member, previous, next, nextIdentifier) {
1938
+ if (previous === undefined || member.phase === 'parameters' || member.phase === 'decorator') {
1939
+ return false;
1940
+ }
1941
+ const canEnd = isMemberExpressionEnd(previous) ||
1942
+ ((member.phase === 'field' || member.phase === 'head') &&
1943
+ (previous.kind === 'identifier' || isPunct(previous, '?') || isPunct(previous, '!'))) ||
1944
+ (member.phase === 'type' &&
1945
+ ((previous.kind === 'identifier' && !TYPE_OPERAND_KEYWORDS.has(previous.value)) ||
1946
+ isPunct(previous, '>') ||
1947
+ isPunct(previous, '}')));
1948
+ if (!canEnd) {
1949
+ return false;
1950
+ }
1951
+ if (member.phase === 'initializer') {
1952
+ return !expressionContinues(next, nextIdentifier);
1953
+ }
1954
+ // An uninitialized field cannot continue by multiplication. Type operators and suffixes can.
1955
+ return !UNINITIALIZED_FIELD_CONTINUATIONS.has(next);
1956
+ }
1957
+ const UNINITIALIZED_FIELD_CONTINUATIONS = new Set([
1958
+ '(',
1959
+ '[',
1960
+ '.',
1961
+ '?',
1962
+ '!',
1963
+ '=',
1964
+ ':',
1965
+ '<',
1966
+ '>',
1967
+ '|',
1968
+ '&',
1969
+ ',',
1970
+ ]);
1971
+ function atTypeRoot(type) {
1972
+ return (type.parenDepth === 0 &&
1973
+ type.bracketDepth === 0 &&
1974
+ type.braceDepth === 0 &&
1975
+ type.angleDepth === 0);
1976
+ }
1977
+ function sameTypeDepth(left, right) {
1978
+ return (left.parenDepth === right.parenDepth &&
1979
+ left.bracketDepth === right.bracketDepth &&
1980
+ left.braceDepth === right.braceDepth &&
1981
+ left.angleDepth === right.angleDepth);
1982
+ }
1983
+ /**
1984
+ * Infer an arrow signature using the index of `=` in `=>`, regardless of whether `>` is emitted.
1985
+ */
1986
+ function inferArrowContext(tokens, arrowIndex) {
1987
+ const arrow = tokens[arrowIndex];
1988
+ if (!isArrowHead(arrow)) {
1989
+ return undefined;
1990
+ }
1991
+ const beforeArrow = arrowIndex - 1;
1992
+ const parameters = tokens[beforeArrow];
1993
+ let parametersIndex;
1994
+ if (parameters?.kind === 'punct' && parameters.value === ')') {
1995
+ parametersIndex = matchingOpenParen(tokens, beforeArrow);
1996
+ }
1997
+ else if (parameters?.kind === 'identifier') {
1998
+ parametersIndex = beforeArrow;
1999
+ }
2000
+ else {
2001
+ return undefined;
2002
+ }
2003
+ const parametersStart = parametersIndex >= 0 ? tokens[parametersIndex] : undefined;
2004
+ if (parametersStart?.kind !== 'identifier' && parametersStart?.kind !== 'punct') {
2005
+ return undefined;
2006
+ }
2007
+ // Only trivia before the parameters can break the async modifier; newlines inside `(...)` cannot.
2008
+ // A generic arrow keeps its type-parameter list between `async` and the parameter list.
2009
+ const markerIndex = parametersStart.kind === 'punct' && parametersStart.value === '('
2010
+ ? signatureNameIndex(tokens, parametersIndex)
2011
+ : parametersIndex - 1;
2012
+ const markerToken = tokens[markerIndex];
2013
+ const async = markerToken?.kind === 'identifier' &&
2014
+ markerToken.value === 'async' &&
2015
+ asyncModifierIsContinuous(tokens, markerIndex, parametersIndex);
2016
+ return {
2017
+ async,
2018
+ generator: false,
2019
+ isExpression: false,
2020
+ };
2021
+ }
2022
+ function parameterSignatureContext(tokens, pending, insideMethodContainer, insideClassBody, lineBreakBefore, memberStart) {
2023
+ const openIndex = tokens.length;
2024
+ const nameIndex = signatureNameIndex(tokens, openIndex);
2025
+ const name = tokens[nameIndex];
2026
+ const head = insideMethodContainer && isMethodName(name)
2027
+ ? memberDeclarationHead(tokens, nameIndex, insideClassBody, memberStart)
2028
+ : undefined;
2029
+ if (head !== undefined) {
2030
+ return {
2031
+ context: {
2032
+ ...head,
2033
+ isExpression: false,
2034
+ isClassMember: insideClassBody,
2035
+ },
2036
+ isFunctionKeyword: false,
2037
+ isArrowSignature: false,
2038
+ };
2039
+ }
2040
+ if (pending?.phase === 'awaiting-parameters' &&
2041
+ (name?.kind === 'identifier' || isPunct(name, '*'))) {
2042
+ return { context: pending, isFunctionKeyword: true, isArrowSignature: false };
2043
+ }
2044
+ const marker = tokens[nameIndex - 1];
2045
+ // 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));
2052
+ if (startsArrowParameters) {
2053
+ const asyncToken = name?.kind === 'identifier' && name.value === 'async' ? name : undefined;
2054
+ const async = asyncToken !== undefined &&
2055
+ !lineBreakBefore &&
2056
+ asyncModifierIsContinuous(tokens, nameIndex, openIndex - 1);
2057
+ return {
2058
+ context: { async, generator: false, isExpression: false },
2059
+ isFunctionKeyword: false,
2060
+ isArrowSignature: true,
2061
+ };
2062
+ }
2063
+ // Without a `function` keyword the parameter list must start a real member declaration, so the
2064
+ // `:` of a conditional expression or of an unrelated expression never claims a return annotation.
2065
+ return undefined;
2066
+ }
2067
+ function asyncModifierIsContinuous(tokens, modifierIndex, parameterIndex) {
2068
+ const genericOpen = isPunct(tokens[modifierIndex + 1], '<') ? modifierIndex + 1 : undefined;
2069
+ return !lineBreakBetween(tokens, modifierIndex, genericOpen ?? parameterIndex);
2070
+ }
2071
+ function nearestFunctionParameter(parameters) {
2072
+ for (let index = parameters.length - 1; index >= 0; index--) {
2073
+ const parameter = parameters[index];
2074
+ if (parameter?.signature !== undefined) {
2075
+ return parameter;
155
2076
  }
156
- // Block comment. Comments are transparent to the regex/division heuristic, so a comment between
157
- // a control keyword and its `(` behaves exactly like whitespace.
158
- if (ch === '/' && source[i + 1] === '*') {
159
- i = skipBlockComment(source, i);
160
- continue;
2077
+ }
2078
+ return undefined;
2079
+ }
2080
+ function nearestClassField(braces) {
2081
+ for (let index = braces.length - 1; index >= 0; index--) {
2082
+ const brace = braces[index];
2083
+ if (brace?.role === 'class' && brace.state.member?.phase === 'initializer') {
2084
+ return brace.state.member;
161
2085
  }
162
- // A backtick in code mode opens a (possibly nested) template literal.
163
- if (ch === '`') {
164
- templateStart = i;
165
- inText = true;
166
- prevKind = 'template';
167
- i++;
2086
+ }
2087
+ return undefined;
2088
+ }
2089
+ function consumeForIterationSeparator(tokens, parameters, depth) {
2090
+ const control = parameters[parameters.length - 1];
2091
+ if (control?.forPhase !== 'binding' ||
2092
+ control.depth.parenDepth !== depth.parenDepth ||
2093
+ control.depth.bracketDepth !== depth.bracketDepth ||
2094
+ control.depth.braceDepth !== depth.braceDepth ||
2095
+ control.depth.frameDepth !== depth.frameDepth ||
2096
+ tokens.length <= control.openIndex) {
2097
+ return false;
2098
+ }
2099
+ const previous = tokens[tokens.length - 1];
2100
+ const isSeparator = previous !== undefined &&
2101
+ isMemberExpressionEnd(previous) &&
2102
+ !isIdentifier(previous, 'const') &&
2103
+ !isIdentifier(previous, 'let') &&
2104
+ !isIdentifier(previous, 'var');
2105
+ if (isSeparator) {
2106
+ control.forPhase = 'iteration';
2107
+ }
2108
+ return isSeparator;
2109
+ }
2110
+ // At the owner's root, a finished operand can still have a suffix or compose with another
2111
+ // type. Only a runtime boundary retires the assertion; internal closers never do so themselves.
2112
+ function assertionTypeContinues(annotation, next, identifier, lineBreak, after) {
2113
+ if (next === '-' && annotation.typeOperandExpected && after !== '-') {
2114
+ return true;
2115
+ }
2116
+ if (identifier !== undefined) {
2117
+ return annotation.typeOperandExpected || (identifier === 'extends' && !lineBreak);
2118
+ }
2119
+ if (next === '?') {
2120
+ return annotation.conditionalOperand;
2121
+ }
2122
+ if (next === ':') {
2123
+ return annotation.conditionalDepth > 0;
2124
+ }
2125
+ if (next === '(') {
2126
+ return annotation.typeOperandExpected || annotation.importCallPending;
2127
+ }
2128
+ if (next === '{' || next === '`' || next === "'" || next === '"') {
2129
+ return annotation.typeOperandExpected;
2130
+ }
2131
+ if (next === '<' || next === '[') {
2132
+ return after !== next && after !== '=' && (annotation.typeOperandExpected || !lineBreak);
2133
+ }
2134
+ if (next === '|' || next === '&') {
2135
+ return after !== next && after !== '=';
2136
+ }
2137
+ if (next === '.') {
2138
+ return true;
2139
+ }
2140
+ if (next === '=' && after === '>') {
2141
+ return true;
2142
+ }
2143
+ return (annotation.typeOperandExpected &&
2144
+ !['/', '+', '-', '*', '%', '!', ';', ',', ')', ']', '}', '=', '>'].includes(next));
2145
+ }
2146
+ function canStartTypeAssertion(previous) {
2147
+ if (previous?.assertionEnd === true) {
2148
+ return true;
2149
+ }
2150
+ return (previous !== undefined &&
2151
+ isMemberExpressionEnd(previous) &&
2152
+ !(previous.kind === 'identifier' &&
2153
+ previous.propertyName !== true &&
2154
+ [
2155
+ 'const',
2156
+ 'let',
2157
+ 'var',
2158
+ 'function',
2159
+ 'class',
2160
+ 'type',
2161
+ 'interface',
2162
+ 'namespace',
2163
+ 'import',
2164
+ 'export',
2165
+ 'extends',
2166
+ 'implements',
2167
+ ].includes(previous.value)));
2168
+ }
2169
+ function isMethodName(token) {
2170
+ return (token?.kind === 'identifier' ||
2171
+ token?.kind === 'string' ||
2172
+ token?.kind === 'number' ||
2173
+ isPunct(token, ']'));
2174
+ }
2175
+ function expressionFunctionBodyEnded(ch, parenDepth, bracketDepth, braceDepth, frameDepth, functionDepth, conditionalDepth, context) {
2176
+ if (functionDepth > context.functionDepth ||
2177
+ braceDepth > context.braceDepth ||
2178
+ frameDepth !== context.frameDepth) {
2179
+ return false;
2180
+ }
2181
+ if (ch === ':' &&
2182
+ context.conditionalDepth > 0 &&
2183
+ conditionalDepth === context.conditionalDepth &&
2184
+ parenDepth === context.parenDepth &&
2185
+ bracketDepth === context.bracketDepth &&
2186
+ braceDepth === context.braceDepth) {
2187
+ return true;
2188
+ }
2189
+ if (ch === ';' || ch === ',') {
2190
+ return (parenDepth === context.parenDepth &&
2191
+ bracketDepth === context.bracketDepth &&
2192
+ braceDepth === context.braceDepth);
2193
+ }
2194
+ if (ch === ')' || ch === ']' || ch === '}') {
2195
+ return (parenDepth === context.parenDepth &&
2196
+ bracketDepth === context.bracketDepth &&
2197
+ braceDepth === context.braceDepth);
2198
+ }
2199
+ return false;
2200
+ }
2201
+ function lineTerminatorEndsExpressionFunctionBody(next, prevKind, prevPunct, previous, nextIdentifier, nextIsUpdate, parenDepth, bracketDepth, braceDepth, frameDepth, functionDepth, context) {
2202
+ if (functionDepth > context.functionDepth ||
2203
+ parenDepth !== context.parenDepth ||
2204
+ bracketDepth !== context.bracketDepth ||
2205
+ braceDepth !== context.braceDepth ||
2206
+ frameDepth !== context.frameDepth) {
2207
+ return false;
2208
+ }
2209
+ const previousEndsExpression = previous?.assertionEnd === true ||
2210
+ EXPRESSION_ENDING.has(prevKind) ||
2211
+ (prevKind === 'punct' &&
2212
+ ([')', ']', '}'].includes(prevPunct ?? '') ||
2213
+ (previous?.kind === 'punct' && previous.expressionEnd === true)));
2214
+ return previousEndsExpression && (nextIsUpdate || !expressionContinues(next, nextIdentifier));
2215
+ }
2216
+ function expressionContinues(next, identifier) {
2217
+ return (EXPRESSION_CONTINUATION_CHARACTERS.has(next) ||
2218
+ identifier === 'in' ||
2219
+ identifier === 'instanceof');
2220
+ }
2221
+ /** Whether a token is the given identifier. */
2222
+ function isIdentifier(token, value) {
2223
+ return token?.kind === 'identifier' && token.value === value;
2224
+ }
2225
+ /**
2226
+ * The index of the `[` matching the `]` at `closeIndex`, or `-1` when the source has no match.
2227
+ */
2228
+ function matchingOpenBracket(tokens, closeIndex) {
2229
+ let depth = 0;
2230
+ for (let k = closeIndex; k >= 0; k--) {
2231
+ const token = tokens[k];
2232
+ if (token?.kind !== 'punct') {
168
2233
  continue;
169
2234
  }
170
- // A `}` in code mode closes the innermost open `${...}` expression only when that expression has
171
- // no open braces of its own; otherwise it is an ordinary brace inside the expression.
172
- if (ch === '}') {
173
- const depth = braceDepths[braceDepths.length - 1];
174
- if (depth !== undefined) {
175
- if (depth === 0) {
176
- braceDepths.pop();
177
- i++;
178
- inText = true;
179
- resetExpressionState();
180
- continue;
181
- }
182
- braceDepths[braceDepths.length - 1] = depth - 1;
183
- }
184
- // Otherwise `}` is an ordinary brace: it ends an expression only when it closed an object
185
- // literal. An unmatched `}` (an empty context) keeps the statement-block behaviour.
186
- closedObjectBrace = braceContexts.pop() === true;
2235
+ if (token.value === ']') {
2236
+ depth++;
187
2237
  }
188
- // `/` is a regex when it can start an expression, otherwise it is division.
189
- if (ch === '/') {
190
- if (isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword, afterControlHead, closedObjectBrace)) {
191
- i = skipRegex(source, i);
192
- prevKind = 'regex';
193
- afterControlHead = false;
194
- pendingControlKeyword = undefined;
195
- continue;
2238
+ else if (token.value === '[') {
2239
+ depth--;
2240
+ if (depth === 0) {
2241
+ return k;
196
2242
  }
197
- tokens.push({ kind: 'punct', value: '/', offset: i });
198
- prevKind = 'punct';
199
- prevPunct = '/';
200
- afterControlHead = false;
201
- pendingControlKeyword = undefined;
202
- i++;
203
- continue;
204
- }
205
- // String literals (single or double quoted).
206
- if (ch === "'" || ch === '"') {
207
- const { value, next } = readString(source, i);
208
- tokens.push({ kind: 'string', value, offset: i });
209
- prevKind = 'string';
210
- afterControlHead = false;
211
- pendingControlKeyword = undefined;
212
- i = next;
213
- continue;
214
2243
  }
215
- // Identifiers.
216
- if (isIdentifierStart(ch)) {
217
- const start = i;
218
- i++;
219
- while (i < n) {
220
- const part = source[i];
221
- if (part === undefined || !isIdentifierPart(part)) {
222
- break;
223
- }
224
- i++;
225
- }
226
- const value = source.slice(start, i);
227
- prevIdentifier = value;
228
- // A keyword after `.` is a property name (e.g. `x.return`), not an expression start.
229
- prevIsExpressionKeyword =
230
- REGEX_PRECEDING_KEYWORDS.has(value) && !(prevKind === 'punct' && prevPunct === '.');
231
- // Remember a control-statement keyword until its `(` is seen so only that head's `)` may be
232
- // followed by a regex literal.
233
- pendingControlKeyword = pendingControlKeywordAfter(value,
234
- // After `.` the identifier is a property name (e.g. `obj.if(...)`), never a control head.
235
- prevKind === 'punct' && prevPunct === '.', pendingControlKeyword);
236
- tokens.push({ kind: 'identifier', value, offset: start });
237
- prevKind = 'identifier';
238
- afterControlHead = false;
2244
+ }
2245
+ return -1;
2246
+ }
2247
+ /**
2248
+ * The index of the `(` matching the `)` at `closeIndex`, or `-1` when the source has no match.
2249
+ */
2250
+ function matchingOpenParen(tokens, closeIndex) {
2251
+ const close = tokens[closeIndex];
2252
+ if (close?.kind === 'punct' && close.matchingOpen !== undefined) {
2253
+ return close.matchingOpen;
2254
+ }
2255
+ let depth = 0;
2256
+ for (let k = closeIndex; k >= 0; k--) {
2257
+ const token = tokens[k];
2258
+ if (token?.kind !== 'punct') {
239
2259
  continue;
240
2260
  }
241
- // Numbers end an expression, so they affect the regex/division decision.
242
- if (isNumberStart(ch)) {
243
- const start = i;
244
- i++;
245
- while (i < n) {
246
- const part = source[i];
247
- if (part === undefined || !(isIdentifierPart(part) || part === '.')) {
248
- break;
249
- }
250
- i++;
251
- }
252
- tokens.push({ kind: 'number', offset: start });
253
- prevKind = 'number';
254
- afterControlHead = false;
255
- pendingControlKeyword = undefined;
256
- continue;
2261
+ if (token.value === ')') {
2262
+ depth++;
257
2263
  }
258
- // Any other single character is punctuation.
259
- if (ch === '{') {
260
- // Record what this brace opens, so its `}` knows whether an expression or a statement ended.
261
- // The enclosing entry is inspected before pushing, so a `:` knows if it separates a property.
262
- braceContexts.push(opensObjectLiteral(prevKind, prevPunct, prevIdentifier, prevIsExpressionKeyword, braceDepths.length > 0, braceContexts[braceContexts.length - 1] === true));
263
- // Count the brace inside the innermost open `${...}` expression so its closing `}` is found.
264
- const depth = braceDepths[braceDepths.length - 1];
265
- if (depth !== undefined) {
266
- braceDepths[braceDepths.length - 1] = depth + 1;
2264
+ else if (token.value === '(') {
2265
+ depth--;
2266
+ if (depth === 0) {
2267
+ return k;
267
2268
  }
268
2269
  }
269
- else if (ch === '(') {
270
- // A `(` after a control keyword opens a control head; only its matching `)` may then be
271
- // followed by a regex literal, so the nesting depth of every `(` has to be tracked.
272
- parenDepth++;
273
- if (pendingControlKeyword !== undefined) {
274
- controlParenDepth = parenDepth;
275
- pendingControlKeyword = undefined;
276
- }
277
- afterControlHead = false;
2270
+ }
2271
+ return -1;
2272
+ }
2273
+ /** Claim the most recent class header at this exact depth; nested heritage classes claim first. */
2274
+ function takeClassBodyContext(contexts, depth) {
2275
+ for (let index = contexts.length - 1; index >= 0; index--) {
2276
+ const context = contexts[index];
2277
+ if (context?.parenDepth === depth.parenDepth &&
2278
+ context.bracketDepth === depth.bracketDepth &&
2279
+ context.braceDepth === depth.braceDepth &&
2280
+ context.frameDepth === depth.frameDepth) {
2281
+ contexts.splice(index, 1);
2282
+ return context;
278
2283
  }
279
- else if (ch === ')') {
280
- afterControlHead = controlParenDepth !== undefined && parenDepth === controlParenDepth;
281
- if (afterControlHead) {
282
- controlParenDepth = undefined;
283
- }
284
- if (parenDepth > 0) {
285
- parenDepth--;
286
- }
2284
+ }
2285
+ return undefined;
2286
+ }
2287
+ /**
2288
+ * Read a JSX tag name starting at `i` (the first character after `<`). Member expressions
2289
+ * (`<UI.Panel>`), namespaced names (`<svg:rect>`) and dashed names (`<my-tag>`) are part of the name.
2290
+ */
2291
+ function readJsxTagName(source, i, n) {
2292
+ const start = i;
2293
+ while (i < n) {
2294
+ const ch = source[i];
2295
+ if (ch === undefined ||
2296
+ !(isIdentifierPartAt(source, i) || ch === '.' || ch === '-' || ch === ':')) {
2297
+ break;
287
2298
  }
288
- tokens.push({ kind: 'punct', value: ch, offset: i });
289
- prevKind = 'punct';
290
- prevPunct = ch;
291
- i++;
2299
+ i += codePointLength(source, i);
292
2300
  }
293
- // Reaching the end of the source while inside a template text or an open `${...}` expression means
294
- // the template literal was never closed.
295
- if (inText || braceDepths.length > 0) {
296
- throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', templateStart);
2301
+ return { name: source.slice(start, i), end: i };
2302
+ }
2303
+ /**
2304
+ * Skip a quoted JSX attribute value starting at its opening quote. The value is inert text: it ends
2305
+ * at the matching quote and backslashes are not escapes, so it can never contribute a specifier.
2306
+ */
2307
+ function skipJsxAttributeValue(source, i, n) {
2308
+ const quote = source[i];
2309
+ let k = i + 1;
2310
+ while (k < n && source[k] !== quote) {
2311
+ k++;
297
2312
  }
298
- return tokens;
2313
+ if (k >= n) {
2314
+ throw new ModuleSpecifierError('Unterminated JSX attribute value', 'invalid-source', i);
2315
+ }
2316
+ return k + 1;
299
2317
  }
300
2318
  /**
301
2319
  * Significant token kinds that clearly terminate an expression. After one of these, `/` is
@@ -308,6 +2326,29 @@ const EXPRESSION_ENDING = new Set([
308
2326
  'template',
309
2327
  'regex',
310
2328
  ]);
2329
+ // A token beginning with one of these characters may continue an expression across a line
2330
+ // terminator, so ASI must not retire an expression-bodied arrow before it.
2331
+ const EXPRESSION_CONTINUATION_CHARACTERS = new Set([
2332
+ '.',
2333
+ '(',
2334
+ '[',
2335
+ '?',
2336
+ ':',
2337
+ ',',
2338
+ '+',
2339
+ '-',
2340
+ '*',
2341
+ '/',
2342
+ '%',
2343
+ '<',
2344
+ '>',
2345
+ '=',
2346
+ '!',
2347
+ '&',
2348
+ '|',
2349
+ '^',
2350
+ '`',
2351
+ ]);
311
2352
  /**
312
2353
  * Expression-starting keywords. After one of these, a `/` begins a regex literal even though the
313
2354
  * keyword itself is tokenized as an identifier. `of`, `yield` and `await` are intentionally
@@ -355,9 +2396,8 @@ function pendingControlKeywordAfter(value, isMemberName, pending) {
355
2396
  * expression on its right: assignment (`= {`), an argument or element list (`f({`, `[{`) and the
356
2397
  * separators and binary/logical operators. `>` is absent because it usually ends an arrow function
357
2398
  * (`() => {}`), whose `{` opens a function body, and `)`/`}` are absent because they close
358
- * statement heads and blocks. `:` is decided separately, in {@link opensObjectLiteral}, because it
359
- * is a property separator inside an object literal but a label or `case`/`default` separator
360
- * otherwise.
2399
+ * statement heads and blocks. `:` is classified separately using property/conditional depth,
2400
+ * because labels and `case`/`default` clauses instead introduce statements.
361
2401
  */
362
2402
  const EXPRESSION_BEFORE_BRACE = new Set([
363
2403
  '=',
@@ -377,26 +2417,41 @@ const EXPRESSION_BEFORE_BRACE = new Set([
377
2417
  ]);
378
2418
  // Keywords that start an expression but are still followed by a statement block.
379
2419
  const BLOCK_AFTER_KEYWORD = new Set(['else', 'do']);
2420
+ // Whether a `function` keyword sits in expression position (a function expression used as a value,
2421
+ // so a slash after its body `{}` divides) rather than statement position (a declaration, so a slash
2422
+ // after its body may start a regex). The previous significant token must expect an expression on
2423
+ // its right: an expression-starting punctuation, an expression keyword, or a regex.
2424
+ function functionIsExpressionPosition(prevKind, prevPunct, previousIdentifier, colonIsExpression) {
2425
+ if (prevKind === 'punct') {
2426
+ return prevPunct === ':' ? colonIsExpression : EXPRESSION_BEFORE_BRACE.has(prevPunct ?? '');
2427
+ }
2428
+ if (prevKind === 'identifier') {
2429
+ return REGEX_PRECEDING_KEYWORDS.has(previousIdentifier ?? '');
2430
+ }
2431
+ return prevKind === 'regex';
2432
+ }
380
2433
  /**
381
2434
  * Whether a `{` opens an expression (an object literal or a destructuring pattern) instead of a
382
- * statement block or function body. `enclosingIsObjectLiteral` reports what the enclosing `{`, if
383
- * any, opened. Statement position is the default, so an unknown context keeps the existing
2435
+ * statement block or function body. `colonIsExpression` distinguishes property/conditional colons
2436
+ * from statement separators. Statement position is the default, so an unknown context keeps the existing
384
2437
  * behaviour of allowing a regex literal after the matching `}`.
385
2438
  */
386
- function opensObjectLiteral(prevKind, prevPunct, prevIdentifier, prevIsExpressionKeyword, inTemplateExpression, enclosingIsObjectLiteral) {
2439
+ function opensObjectLiteral(prevKind, prevPunct, prevIdentifier, prevIsExpressionKeyword, inTemplateExpression, colonIsExpression, lineBreakBefore) {
387
2440
  if (prevKind === 'punct') {
388
2441
  // Inside an object literal a `:` separates a property from its value (`{ scale: {} }`, where a
389
2442
  // following `/` divides), while at statement level the same `:` follows a label
390
2443
  // (`label: {}`) or a `case`/`default` clause, whose `{` opens a block.
391
2444
  if (prevPunct === ':') {
392
- return enclosingIsObjectLiteral;
2445
+ return colonIsExpression;
393
2446
  }
394
2447
  return EXPRESSION_BEFORE_BRACE.has(prevPunct ?? '');
395
2448
  }
396
2449
  if (prevKind === 'identifier') {
397
2450
  // `return {` is an expression, `else {`/`do {` are blocks and every other identifier
398
2451
  // (`function f`, `class C`) sits in statement position.
399
- return prevIsExpressionKeyword && !BLOCK_AFTER_KEYWORD.has(prevIdentifier ?? '');
2452
+ return (prevIsExpressionKeyword &&
2453
+ !BLOCK_AFTER_KEYWORD.has(prevIdentifier ?? '') &&
2454
+ !(prevIdentifier === 'return' && lineBreakBefore));
400
2455
  }
401
2456
  // Right after `${` an expression is expected, so a `{` opens an object literal.
402
2457
  return inTemplateExpression && prevKind === 'start';
@@ -409,7 +2464,16 @@ function opensObjectLiteral(prevKind, prevPunct, prevIdentifier, prevIsExpressio
409
2464
  * what it closed: an object literal ends an expression, so the `/` divides, while a statement block
410
2465
  * leaves the expression-start position, where a regex literal may begin.
411
2466
  */
412
- function isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword, afterControlHead, closedObjectBrace) {
2467
+ function isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword, afterControlHead, closedObjectBrace, previous) {
2468
+ if (previous?.assertionEnd === true) {
2469
+ return false;
2470
+ }
2471
+ if (previous?.kind === 'punct' && previous.value !== '}' && previous.expressionEnd === true) {
2472
+ return false;
2473
+ }
2474
+ if (previous?.kind === 'punct' && (previous.value === '++' || previous.value === '--')) {
2475
+ return previous.expressionEnd !== true;
2476
+ }
413
2477
  if (prevKind === 'identifier' && prevIsExpressionKeyword) {
414
2478
  return true;
415
2479
  }
@@ -467,11 +2531,30 @@ function interpret(tokens) {
467
2531
  * - `import 'pkg'` and `import ... from 'pkg'` are `import`.
468
2532
  * - `import('pkg')` is `dynamic-import` only when the sole argument is a static string.
469
2533
  */
470
- function handleImport(tokens, i, occurrences) {
2534
+ /**
2535
+ * Whether the identifier at `i` is a property name (`obj.require`), which is never the global
2536
+ * `require`/`import`. A `.` that belongs to a spread (`...require('pkg')`) is not member access.
2537
+ */
2538
+ function isMemberName(tokens, i) {
471
2539
  const prev = tokens[i - 1];
2540
+ if (prev?.kind === 'punct' && prev.value === '#') {
2541
+ return true;
2542
+ }
2543
+ if (prev?.kind !== 'punct' || prev.value !== '.') {
2544
+ return false;
2545
+ }
2546
+ const second = tokens[i - 2];
2547
+ const third = tokens[i - 3];
2548
+ const isSpread = second?.kind === 'punct' &&
2549
+ second.value === '.' &&
2550
+ third?.kind === 'punct' &&
2551
+ third.value === '.';
2552
+ return !isSpread;
2553
+ }
2554
+ function handleImport(tokens, i, occurrences) {
472
2555
  const next = tokens[i + 1];
473
2556
  // `obj.import(...)`: a member call, not the import keyword.
474
- if (prev?.kind === 'punct' && prev.value === '.') {
2557
+ if (isMemberName(tokens, i)) {
475
2558
  return i + 1;
476
2559
  }
477
2560
  // `import.meta` / `import .foo`: not a module import.
@@ -509,6 +2592,18 @@ function handleExport(tokens, i, occurrences) {
509
2592
  */
510
2593
  function scanImportSource(tokens, i, occurrences, kind) {
511
2594
  const n = tokens.length;
2595
+ if (kind === 'export-from') {
2596
+ const sourceIndex = exportSourceIndex(tokens, i);
2597
+ if (sourceIndex === undefined) {
2598
+ return i + 1;
2599
+ }
2600
+ const source = tokens[sourceIndex];
2601
+ if (source?.kind === 'string') {
2602
+ occurrences.push({ specifier: source.value, kind });
2603
+ return sourceIndex + 1;
2604
+ }
2605
+ return i + 1;
2606
+ }
512
2607
  let j = i + 1;
513
2608
  // How many `{ ... }` named clauses of the current statement are open. `import`/`export` are legal
514
2609
  // binding names inside such a clause (e.g. `import { export as v } from 'pkg'`), so they may only
@@ -545,14 +2640,47 @@ function scanImportSource(tokens, i, occurrences, kind) {
545
2640
  // No source found; keep scanning from the next token.
546
2641
  return i + 1;
547
2642
  }
2643
+ function exportSourceIndex(tokens, exportIndex) {
2644
+ let index = exportIndex + 1;
2645
+ if (isIdentifier(tokens[index], 'type')) {
2646
+ index++;
2647
+ }
2648
+ if (isPunct(tokens[index], '*')) {
2649
+ index++;
2650
+ if (isIdentifier(tokens[index], 'as')) {
2651
+ index += 2;
2652
+ }
2653
+ }
2654
+ else if (isPunct(tokens[index], '{')) {
2655
+ let depth = 1;
2656
+ index++;
2657
+ while (index < tokens.length && depth > 0) {
2658
+ if (isPunct(tokens[index], '{')) {
2659
+ depth++;
2660
+ }
2661
+ else if (isPunct(tokens[index], '}')) {
2662
+ depth--;
2663
+ }
2664
+ index++;
2665
+ }
2666
+ if (depth !== 0) {
2667
+ return undefined;
2668
+ }
2669
+ }
2670
+ else {
2671
+ return undefined;
2672
+ }
2673
+ return isIdentifier(tokens[index], 'from') && tokens[index + 1]?.kind === 'string'
2674
+ ? index + 1
2675
+ : undefined;
2676
+ }
548
2677
  /**
549
2678
  * Handle a `require` identifier. `obj.require(...)` is ignored (preceded by `.`).
550
2679
  * `require('pkg')` is `require` only when the sole argument is a static string.
551
2680
  */
552
2681
  function handleRequire(tokens, i, occurrences) {
553
- const prev = tokens[i - 1];
554
2682
  // `obj.require(...)`: not the global CommonJS require.
555
- if (prev?.kind === 'punct' && prev.value === '.') {
2683
+ if (isMemberName(tokens, i)) {
556
2684
  return i + 1;
557
2685
  }
558
2686
  const next = tokens[i + 1];
@@ -803,7 +2931,7 @@ function isWhitespace(ch) {
803
2931
  ch === '\f' ||
804
2932
  ch === '\uFEFF' ||
805
2933
  isLineTerminator(ch) ||
806
- SPACE_SEPARATOR.test(ch));
2934
+ (ch !== undefined && SPACE_SEPARATOR.test(ch)));
807
2935
  }
808
2936
  /**
809
2937
  * The ECMAScript `LineTerminator` characters. Line comments and the hashbang end at any of them,
@@ -812,11 +2940,62 @@ function isWhitespace(ch) {
812
2940
  function isLineTerminator(ch) {
813
2941
  return ch === '\n' || ch === '\r' || ch === '\u2028' || ch === '\u2029';
814
2942
  }
815
- function isIdentifierStart(ch) {
816
- return /[A-Za-z_$]/.test(ch);
2943
+ const IDENTIFIER_START = /^[$_\p{ID_Start}]$/u;
2944
+ const IDENTIFIER_PART = /^[$_\u200C\u200D\p{ID_Continue}]$/u;
2945
+ function isIdentifierStartAt(source, index) {
2946
+ const codePoint = readCodePoint(source, index);
2947
+ return codePoint !== undefined && isIdentifierStartCharacter(codePoint.character);
2948
+ }
2949
+ function isIdentifierPartAt(source, index) {
2950
+ const codePoint = readCodePoint(source, index);
2951
+ return codePoint !== undefined && isIdentifierPartCharacter(codePoint.character);
2952
+ }
2953
+ function isIdentifierStartCharacter(character) {
2954
+ return IDENTIFIER_START.test(character);
2955
+ }
2956
+ function isIdentifierPartCharacter(character) {
2957
+ return IDENTIFIER_PART.test(character);
2958
+ }
2959
+ function codePointLength(source, index) {
2960
+ return (source.codePointAt(index) ?? 0) > 0xffff ? 2 : 1;
2961
+ }
2962
+ function readCodePoint(source, index) {
2963
+ const point = source.codePointAt(index);
2964
+ if (point === undefined) {
2965
+ return undefined;
2966
+ }
2967
+ const end = index + (point > 0xffff ? 2 : 1);
2968
+ return { character: String.fromCodePoint(point), end };
2969
+ }
2970
+ function isUnicodeEscapeStart(source, index) {
2971
+ return source[index] === '\\' && source[index + 1] === 'u';
817
2972
  }
818
- function isIdentifierPart(ch) {
819
- return /[A-Za-z0-9_$]/.test(ch);
2973
+ function readUnicodeEscape(source, start) {
2974
+ let end = start + 2;
2975
+ let digits;
2976
+ if (source[end] === '{') {
2977
+ const digitsStart = ++end;
2978
+ while (end < source.length && /[0-9A-Fa-f]/.test(source[end] ?? '')) {
2979
+ end++;
2980
+ }
2981
+ if (end === digitsStart || source[end] !== '}' || end - digitsStart > 6) {
2982
+ throw new ModuleSpecifierError('Invalid Unicode escape in identifier', 'invalid-source', start);
2983
+ }
2984
+ digits = source.slice(digitsStart, end);
2985
+ end++;
2986
+ }
2987
+ else {
2988
+ digits = source.slice(end, end + 4);
2989
+ if (digits.length !== 4 || !/^[0-9A-Fa-f]{4}$/.test(digits)) {
2990
+ throw new ModuleSpecifierError('Invalid Unicode escape in identifier', 'invalid-source', start);
2991
+ }
2992
+ end += 4;
2993
+ }
2994
+ const value = Number.parseInt(digits, 16);
2995
+ if (value > 0x10ffff) {
2996
+ throw new ModuleSpecifierError('Invalid Unicode escape in identifier', 'invalid-source', start);
2997
+ }
2998
+ return { character: String.fromCodePoint(value), end };
820
2999
  }
821
3000
  function isNumberStart(ch) {
822
3001
  return /[0-9]/.test(ch);