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.
- package/README.md +37 -54
- package/dist/cli/entry.d.ts +59 -0
- package/dist/cli/entry.d.ts.map +1 -0
- package/dist/cli/entry.js +95 -0
- package/dist/cli/entry.js.map +1 -0
- package/dist/cli/index.js +14 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/notifier/eligibility.d.ts +65 -0
- package/dist/cli/notifier/eligibility.d.ts.map +1 -0
- package/dist/cli/notifier/eligibility.js +123 -0
- package/dist/cli/notifier/eligibility.js.map +1 -0
- package/dist/cli/notifier/registryLookup.d.ts +111 -0
- package/dist/cli/notifier/registryLookup.d.ts.map +1 -0
- package/dist/cli/notifier/registryLookup.js +211 -0
- package/dist/cli/notifier/registryLookup.js.map +1 -0
- package/dist/cli/notifier/updateCache.d.ts +101 -0
- package/dist/cli/notifier/updateCache.d.ts.map +1 -0
- package/dist/cli/notifier/updateCache.js +352 -0
- package/dist/cli/notifier/updateCache.js.map +1 -0
- package/dist/cli/notifier/updateCheck.d.ts +88 -0
- package/dist/cli/notifier/updateCheck.d.ts.map +1 -0
- package/dist/cli/notifier/updateCheck.js +166 -0
- package/dist/cli/notifier/updateCheck.js.map +1 -0
- package/dist/cli/notifier/versionCompare.d.ts +48 -0
- package/dist/cli/notifier/versionCompare.d.ts.map +1 -0
- package/dist/cli/notifier/versionCompare.js +89 -0
- package/dist/cli/notifier/versionCompare.js.map +1 -0
- package/dist/cli/runCli.d.ts +2 -0
- package/dist/cli/runCli.d.ts.map +1 -1
- package/dist/cli/runCli.js +30 -3
- package/dist/cli/runCli.js.map +1 -1
- package/dist/core/analyzeProject.d.ts +44 -6
- package/dist/core/analyzeProject.d.ts.map +1 -1
- package/dist/core/analyzeProject.js +110 -11
- package/dist/core/analyzeProject.js.map +1 -1
- package/dist/detectors/eslintManifestConfig.d.ts +111 -0
- package/dist/detectors/eslintManifestConfig.d.ts.map +1 -0
- package/dist/detectors/eslintManifestConfig.js +348 -0
- package/dist/detectors/eslintManifestConfig.js.map +1 -0
- package/dist/detectors/eslintRcJsonConfig.d.ts +85 -0
- package/dist/detectors/eslintRcJsonConfig.d.ts.map +1 -0
- package/dist/detectors/eslintRcJsonConfig.js +188 -0
- package/dist/detectors/eslintRcJsonConfig.js.map +1 -0
- package/dist/detectors/jestManifestConfig.d.ts +76 -0
- package/dist/detectors/jestManifestConfig.d.ts.map +1 -0
- package/dist/detectors/jestManifestConfig.js +292 -0
- package/dist/detectors/jestManifestConfig.js.map +1 -0
- package/dist/reporters/terminalReporter.js +27 -1
- package/dist/reporters/terminalReporter.js.map +1 -1
- package/dist/scanner/moduleSpecifiers.d.ts +9 -1
- package/dist/scanner/moduleSpecifiers.d.ts.map +1 -1
- package/dist/scanner/moduleSpecifiers.js +2352 -173
- package/dist/scanner/moduleSpecifiers.js.map +1 -1
- package/dist/scanner/projectManifest.d.ts +14 -0
- package/dist/scanner/projectManifest.d.ts.map +1 -1
- package/dist/scanner/projectManifest.js +40 -0
- package/dist/scanner/projectManifest.js.map +1 -1
- package/dist/scanner/scriptUsage.d.ts +11 -5
- package/dist/scanner/scriptUsage.d.ts.map +1 -1
- package/dist/scanner/scriptUsage.js +320 -16
- package/dist/scanner/scriptUsage.js.map +1 -1
- 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
|
|
76
|
-
// the `}` that closes the interpolation from a `}` belonging to an object literal, a
|
|
77
|
-
// function body inside it. JavaScript inside `${...}` is lexed like ordinary source.
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
99
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
289
|
-
prevKind = 'punct';
|
|
290
|
-
prevPunct = ch;
|
|
291
|
-
i++;
|
|
2299
|
+
i += codePointLength(source, i);
|
|
292
2300
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
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
|
|
359
|
-
*
|
|
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. `
|
|
383
|
-
*
|
|
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,
|
|
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
|
|
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 &&
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
816
|
-
|
|
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
|
|
819
|
-
|
|
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);
|