depvitals 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -61
- 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 +36 -4
- package/dist/cli/runCli.js.map +1 -1
- package/dist/config/ignoreDependencies.d.ts +7 -0
- package/dist/config/ignoreDependencies.d.ts.map +1 -0
- package/dist/config/ignoreDependencies.js +18 -0
- package/dist/config/ignoreDependencies.js.map +1 -0
- package/dist/config/ignorePaths.d.ts +13 -0
- package/dist/config/ignorePaths.d.ts.map +1 -0
- package/dist/config/ignorePaths.js +47 -0
- package/dist/config/ignorePaths.js.map +1 -0
- package/dist/config/projectConfigError.d.ts +7 -0
- package/dist/config/projectConfigError.d.ts.map +1 -0
- package/dist/config/projectConfigError.js +10 -0
- package/dist/config/projectConfigError.js.map +1 -0
- package/dist/config/readProjectConfig.d.ts +23 -0
- package/dist/config/readProjectConfig.d.ts.map +1 -0
- package/dist/config/readProjectConfig.js +106 -0
- package/dist/config/readProjectConfig.js.map +1 -0
- package/dist/core/analyzeProject.d.ts +48 -6
- package/dist/core/analyzeProject.d.ts.map +1 -1
- package/dist/core/analyzeProject.js +123 -16
- 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 +351 -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 +189 -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 +294 -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 +3317 -205
- package/dist/scanner/moduleSpecifiers.js.map +1 -1
- package/dist/scanner/packageSpecifier.d.ts +26 -2
- package/dist/scanner/packageSpecifier.d.ts.map +1 -1
- package/dist/scanner/packageSpecifier.js +70 -2
- package/dist/scanner/packageSpecifier.js.map +1 -1
- package/dist/scanner/projectManifest.d.ts +31 -1
- package/dist/scanner/projectManifest.d.ts.map +1 -1
- package/dist/scanner/projectManifest.js +83 -4
- package/dist/scanner/projectManifest.js.map +1 -1
- package/dist/scanner/scriptUsage.d.ts +2 -1
- package/dist/scanner/scriptUsage.d.ts.map +1 -1
- package/dist/scanner/scriptUsage.js +216 -39
- package/dist/scanner/scriptUsage.js.map +1 -1
- package/dist/scanner/sourceFiles.d.ts +16 -2
- package/dist/scanner/sourceFiles.d.ts.map +1 -1
- package/dist/scanner/sourceFiles.js +12 -6
- package/dist/scanner/sourceFiles.js.map +1 -1
- package/package.json +7 -7
|
@@ -42,15 +42,22 @@ 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
|
-
|
|
45
|
+
export function extractModuleSpecifiers(source, options = {}) {
|
|
46
|
+
const jsx = options.jsx ?? false;
|
|
47
|
+
// Ambiguous input gets one non-interpreting lexical probe and one definitive extraction. No
|
|
48
|
+
// source reread, parser retry after an exception, or repeated per-statement suffix scans.
|
|
49
|
+
const module = options.module === 'detect'
|
|
50
|
+
? tokenize(source, jsx, false, true).some((token) => token.moduleSignal === true)
|
|
51
|
+
: (options.module ?? false);
|
|
52
|
+
return interpret(tokenize(source, jsx, module));
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
55
|
* Turn source into a stream of significant tokens, skipping whitespace, line/block comments, regex
|
|
50
56
|
* literals and template literals.
|
|
51
57
|
*/
|
|
52
|
-
function tokenize(source) {
|
|
58
|
+
function tokenize(source, jsx = false, module = false, detectModule = false) {
|
|
53
59
|
const tokens = [];
|
|
60
|
+
const typeArgumentBounds = new Map();
|
|
54
61
|
const n = source.length;
|
|
55
62
|
let i = 0;
|
|
56
63
|
// Whether the previous identifier token is an expression-starting keyword (return, typeof, ...).
|
|
@@ -70,22 +77,77 @@ function tokenize(source) {
|
|
|
70
77
|
// The value of the previous identifier token, when the previous token was an identifier. Which
|
|
71
78
|
// identifier it was matters for one decision: `return {` opens an object literal, `else {` not.
|
|
72
79
|
let prevIdentifier;
|
|
80
|
+
// Preserve the position before an `async` modifier for a following `function` keyword.
|
|
81
|
+
let asyncFunctionIsExpression = false;
|
|
82
|
+
// A line terminator in trivia may end an expression-bodied arrow through ASI. It is consumed
|
|
83
|
+
// only when the next significant token cannot continue the preceding expression.
|
|
84
|
+
let lineTerminatorSinceLastToken = false;
|
|
85
|
+
let tokenLineBreakBefore = false;
|
|
86
|
+
let htmlCloseCommentAllowed = true;
|
|
87
|
+
const emit = (token) => {
|
|
88
|
+
const significant = tokenLineBreakBefore ? { ...token, lineBreakBefore: true } : token;
|
|
89
|
+
if (memberAtToken !== undefined) {
|
|
90
|
+
advanceMember(memberAtToken, significant, tokens[tokens.length - 1]);
|
|
91
|
+
}
|
|
92
|
+
const scope = requireScopes[requireScopes.length - 1];
|
|
93
|
+
tokens.push(scope?.inParameters === true
|
|
94
|
+
? {
|
|
95
|
+
...significant,
|
|
96
|
+
parameterFrame: frames.length,
|
|
97
|
+
...(typeAnnotation === undefined ? {} : { parameterType: true }),
|
|
98
|
+
}
|
|
99
|
+
: significant);
|
|
100
|
+
if (token.kind !== 'punct' || token.syntheticBoundary !== true) {
|
|
101
|
+
htmlCloseCommentAllowed = false;
|
|
102
|
+
}
|
|
103
|
+
tokenLineBreakBefore = false;
|
|
104
|
+
lineTerminatorSinceLastToken = false;
|
|
105
|
+
};
|
|
73
106
|
// Template literal state. `inText` is true while scanning template text that must not be lexed as
|
|
74
107
|
// 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
|
-
|
|
108
|
+
// `${...}` expression pushes a `template-expr` frame carrying its own brace depth, which is what
|
|
109
|
+
// distinguishes the `}` that closes the interpolation from a `}` belonging to an object literal, a
|
|
110
|
+
// block or a function body inside it. JavaScript inside `${...}` is lexed like ordinary source.
|
|
111
|
+
//
|
|
112
|
+
// The same stack carries the JSX overlay frames (`jsx-tag`, `jsx-text` and the `jsx-expr` of a
|
|
113
|
+
// `{...}` container), so markup, expression containers, templates and nested elements interleave
|
|
114
|
+
// in a single pass without any state leaking between them.
|
|
115
|
+
const frames = [];
|
|
79
116
|
let inText = false;
|
|
80
117
|
// Offset of the opening backtick of the current template, for unterminated-template errors.
|
|
81
118
|
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.
|
|
119
|
+
const templateStarts = [];
|
|
88
120
|
const braceContexts = [];
|
|
121
|
+
let memberAtToken;
|
|
122
|
+
// Function context of every open function body, tracked independently of expression frame resets
|
|
123
|
+
// so `${}` boundaries cannot change the nearest await/yield grammar.
|
|
124
|
+
const functionContextStack = [];
|
|
125
|
+
const requireScopes = [];
|
|
126
|
+
const closeRequireScope = (scope) => {
|
|
127
|
+
if (scope !== undefined && requireScopes[requireScopes.length - 1] === scope) {
|
|
128
|
+
requireScopes.pop();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const recordRequireBinding = (parameters, end) => {
|
|
132
|
+
const scope = parameters.signature?.context.requireScope;
|
|
133
|
+
const bindingEnd = parameters.bindingEnd ?? end;
|
|
134
|
+
if (scope !== undefined && parameters.bindingStart < bindingEnd) {
|
|
135
|
+
scope.bindings.push({ start: parameters.bindingStart, end: bindingEnd });
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
// The nearest function body owns the await/yield grammar. An ordinary nested function therefore
|
|
139
|
+
// suspends an async/generator context from an outer function instead of inheriting it.
|
|
140
|
+
// A default callback may open and finish while an outer signature is still reading parameters.
|
|
141
|
+
// Only a signature with closed parameters at its original depth can own the next body brace.
|
|
142
|
+
const pendingFunctionContexts = [];
|
|
143
|
+
const pendingClassContexts = [];
|
|
144
|
+
const conditionalContexts = [];
|
|
145
|
+
let colonIsExpression = false;
|
|
146
|
+
const expressionFunctionContexts = [];
|
|
147
|
+
let typeAnnotation;
|
|
148
|
+
const parameterContexts = [];
|
|
149
|
+
let closedParameterSignature;
|
|
150
|
+
let bracketDepth = 0;
|
|
89
151
|
// Whether the most recent `}` closed an object literal. It is only consulted while that `}` is
|
|
90
152
|
// the previous token, exactly like `prevPunct`.
|
|
91
153
|
let closedObjectBrace = false;
|
|
@@ -95,10 +157,25 @@ function tokenize(source) {
|
|
|
95
157
|
// The control keyword pending a `(` (if/while/for/with), so the matching `)` can be recognised.
|
|
96
158
|
// Only whitespace and comments may appear between the keyword and the `(`.
|
|
97
159
|
let pendingControlKeyword;
|
|
98
|
-
//
|
|
99
|
-
//
|
|
160
|
+
// Parameter/control parentheses carry the signature they opened, rather than recovering one
|
|
161
|
+
// from an arbitrary `)` when a colon or body brace appears later.
|
|
100
162
|
let parenDepth = 0;
|
|
101
|
-
|
|
163
|
+
const atLexicalDepth = (context) => parenDepth === context.parenDepth &&
|
|
164
|
+
bracketDepth === context.bracketDepth &&
|
|
165
|
+
braceContexts.length === context.braceDepth &&
|
|
166
|
+
frames.length === context.frameDepth;
|
|
167
|
+
const insideObjectLiteral = () => braceContexts[braceContexts.length - 1]?.role === 'object';
|
|
168
|
+
const insideClassBody = () => braceContexts[braceContexts.length - 1]?.role === 'class';
|
|
169
|
+
const atMethodContainer = () => {
|
|
170
|
+
const brace = braceContexts[braceContexts.length - 1];
|
|
171
|
+
return brace?.role === 'object'
|
|
172
|
+
? atLexicalDepth(brace.depth)
|
|
173
|
+
: brace?.role === 'class' && atLexicalDepth(brace.state);
|
|
174
|
+
};
|
|
175
|
+
const currentClass = () => {
|
|
176
|
+
const brace = braceContexts[braceContexts.length - 1];
|
|
177
|
+
return brace?.role === 'class' ? brace.state : undefined;
|
|
178
|
+
};
|
|
102
179
|
// Reset the expression-start state at the beginning of a fresh `${...}` expression.
|
|
103
180
|
const resetExpressionState = () => {
|
|
104
181
|
prevKind = 'start';
|
|
@@ -108,13 +185,54 @@ function tokenize(source) {
|
|
|
108
185
|
afterControlHead = false;
|
|
109
186
|
pendingControlKeyword = undefined;
|
|
110
187
|
closedObjectBrace = false;
|
|
188
|
+
lineTerminatorSinceLastToken = false;
|
|
189
|
+
// Pending signatures retain their own depth across template/JSX expressions in parameters.
|
|
190
|
+
};
|
|
191
|
+
// A finished JSX element leaves an expression value behind: a following `/` divides, and the
|
|
192
|
+
// markup itself never opens a statement context.
|
|
193
|
+
const endJsxElement = () => {
|
|
194
|
+
prevKind = 'template';
|
|
195
|
+
prevPunct = undefined;
|
|
196
|
+
prevIdentifier = undefined;
|
|
197
|
+
prevIsExpressionKeyword = false;
|
|
198
|
+
afterControlHead = false;
|
|
199
|
+
pendingControlKeyword = undefined;
|
|
200
|
+
closedObjectBrace = false;
|
|
201
|
+
};
|
|
202
|
+
// Template interpolations, JSX markup and sibling containers are separate expressions. An
|
|
203
|
+
// explicit boundary keeps the token stream from gluing them into an import/require sequence that
|
|
204
|
+
// does not exist in source (for example `<p>{import}{('pkg')}</p>`).
|
|
205
|
+
const pushBoundary = (offset) => {
|
|
206
|
+
emit({
|
|
207
|
+
kind: 'punct',
|
|
208
|
+
value: ';',
|
|
209
|
+
offset,
|
|
210
|
+
endOffset: offset + 1,
|
|
211
|
+
syntheticBoundary: true,
|
|
212
|
+
});
|
|
111
213
|
};
|
|
112
214
|
while (i < n) {
|
|
215
|
+
memberAtToken = undefined;
|
|
113
216
|
const ch = source[i];
|
|
217
|
+
let typedArrowContext;
|
|
218
|
+
let inType = typeAnnotation !== undefined;
|
|
219
|
+
let closedClassMemberBoundary = false;
|
|
220
|
+
let closedExpressionBrace = false;
|
|
221
|
+
let opensClassMemberBoundary = false;
|
|
222
|
+
let matchingOpen;
|
|
114
223
|
if (ch === undefined) {
|
|
115
224
|
i++;
|
|
116
225
|
continue;
|
|
117
226
|
}
|
|
227
|
+
let conditional = conditionalContexts[conditionalContexts.length - 1];
|
|
228
|
+
while (conditional !== undefined &&
|
|
229
|
+
(conditional.parenDepth > parenDepth ||
|
|
230
|
+
conditional.bracketDepth > bracketDepth ||
|
|
231
|
+
conditional.braceDepth > braceContexts.length ||
|
|
232
|
+
conditional.frameDepth > frames.length)) {
|
|
233
|
+
conditionalContexts.pop();
|
|
234
|
+
conditional = conditionalContexts[conditionalContexts.length - 1];
|
|
235
|
+
}
|
|
118
236
|
// Template literal text: skip everything until `${`, a nested `` ` ``, or the closing `` ` ``.
|
|
119
237
|
// A backtick toggles out of text mode; the matching toggle happens below in code mode.
|
|
120
238
|
if (inText) {
|
|
@@ -125,6 +243,9 @@ function tokenize(source) {
|
|
|
125
243
|
if (ch === '`') {
|
|
126
244
|
// Closing backtick of the current (or nested) template literal.
|
|
127
245
|
inText = false;
|
|
246
|
+
const start = templateStarts.pop() ?? templateStart;
|
|
247
|
+
emit({ kind: 'template', offset: start, endOffset: i + 1 });
|
|
248
|
+
templateStart = templateStarts[templateStarts.length - 1] ?? start;
|
|
128
249
|
prevKind = 'template';
|
|
129
250
|
i++;
|
|
130
251
|
continue;
|
|
@@ -132,7 +253,16 @@ function tokenize(source) {
|
|
|
132
253
|
if (ch === '$' && source[i + 1] === '{') {
|
|
133
254
|
// Start of a `${...}` expression: lex the JavaScript inside like ordinary source. The frame
|
|
134
255
|
// starts at brace depth 0, so the next unmatched `}` closes the interpolation.
|
|
135
|
-
|
|
256
|
+
frames.push({
|
|
257
|
+
kind: 'template-expr',
|
|
258
|
+
depth: 0,
|
|
259
|
+
offset: i,
|
|
260
|
+
...(typeAnnotation !== undefined
|
|
261
|
+
? { typeOwner: { annotation: typeAnnotation, depth: { ...typeAnnotation } } }
|
|
262
|
+
: {}),
|
|
263
|
+
});
|
|
264
|
+
// The preceding tag is not the callee of tokens inside this interpolation.
|
|
265
|
+
pushBoundary(i);
|
|
136
266
|
inText = false;
|
|
137
267
|
resetExpressionState();
|
|
138
268
|
i += 2;
|
|
@@ -141,161 +271,2690 @@ function tokenize(source) {
|
|
|
141
271
|
i++;
|
|
142
272
|
continue;
|
|
143
273
|
}
|
|
144
|
-
|
|
274
|
+
// The innermost frame decides how the next character is read.
|
|
275
|
+
const frame = frames[frames.length - 1];
|
|
276
|
+
// JSX children: inert text up to the matching close tag. Quotes, backticks and comment-looking
|
|
277
|
+
// sequences are text here, so only `<` (markup) and `{` (an expression container) leave it.
|
|
278
|
+
if (frame?.kind === 'jsx-text') {
|
|
279
|
+
if (ch === '{') {
|
|
280
|
+
frames.push({ kind: 'jsx-expr', depth: 0, offset: i });
|
|
281
|
+
pushBoundary(i);
|
|
282
|
+
resetExpressionState();
|
|
283
|
+
i++;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (ch === '<') {
|
|
287
|
+
const after = source[i + 1];
|
|
288
|
+
// Close tag `</name>` or fragment close `</>`: this element ends here.
|
|
289
|
+
if (after === '/') {
|
|
290
|
+
let k = i + 2;
|
|
291
|
+
while (k < n && source[k] !== '>') {
|
|
292
|
+
k++;
|
|
293
|
+
}
|
|
294
|
+
if (k >= n) {
|
|
295
|
+
throw new ModuleSpecifierError('Unterminated JSX element', 'invalid-source', frame.offset);
|
|
296
|
+
}
|
|
297
|
+
frames.pop();
|
|
298
|
+
pushBoundary(i);
|
|
299
|
+
endJsxElement();
|
|
300
|
+
i = k + 1;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
// Nested fragment `<>`.
|
|
304
|
+
if (after === '>') {
|
|
305
|
+
frames.push({ kind: 'jsx-text', name: null, offset: i });
|
|
306
|
+
i += 2;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
// Nested element `<name ...`.
|
|
310
|
+
if (isIdentifierStartAt(source, i + 1)) {
|
|
311
|
+
const tag = readJsxTagName(source, i + 1, n);
|
|
312
|
+
frames.push({ kind: 'jsx-tag', name: tag.name, offset: i, nameEnd: tag.end });
|
|
313
|
+
i = tag.end;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
// A `<` that is not markup (`a < b` written inside JSX text) is ordinary text.
|
|
317
|
+
i++;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
i++;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
// JSX open tag: attribute names, `=` and namespaces are inert; quoted values are inert text and
|
|
324
|
+
// `{...}` returns to real JavaScript.
|
|
325
|
+
if (frame?.kind === 'jsx-tag') {
|
|
326
|
+
if (ch === '<' && skipTrivia(source, frame.nameEnd, n) === i) {
|
|
327
|
+
pushBoundary(i);
|
|
328
|
+
i = readJsxTypeArguments(source, i, n, tokens);
|
|
329
|
+
pushBoundary(i);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (ch === '{') {
|
|
333
|
+
frames.push({ kind: 'jsx-expr', depth: 0, offset: i });
|
|
334
|
+
pushBoundary(i);
|
|
335
|
+
resetExpressionState();
|
|
336
|
+
i++;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (ch === '"' || ch === "'") {
|
|
340
|
+
i = skipJsxAttributeValue(source, i, n);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
// Self-closing tag `<name ... />`: the element ends without children.
|
|
344
|
+
if (ch === '/' && source[i + 1] === '>') {
|
|
345
|
+
frames.pop();
|
|
346
|
+
pushBoundary(i);
|
|
347
|
+
endJsxElement();
|
|
348
|
+
i += 2;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
// End of the open tag: its children follow until the matching close tag.
|
|
352
|
+
if (ch === '>') {
|
|
353
|
+
frames.pop();
|
|
354
|
+
frames.push({ kind: 'jsx-text', name: frame.name, offset: frame.offset });
|
|
355
|
+
i++;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
145
358
|
i++;
|
|
146
359
|
continue;
|
|
147
360
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
i += 2;
|
|
361
|
+
if (!module &&
|
|
362
|
+
(source.startsWith('<!--', i) || (source.startsWith('-->', i) && htmlCloseCommentAllowed))) {
|
|
151
363
|
while (i < n && !isLineTerminator(source[i])) {
|
|
152
364
|
i++;
|
|
153
365
|
}
|
|
154
366
|
continue;
|
|
155
367
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
368
|
+
if (isWhitespace(ch) || (ch === '/' && (source[i + 1] === '/' || source[i + 1] === '*'))) {
|
|
369
|
+
const trivia = readTrivia(source, i, n);
|
|
370
|
+
i = trivia.end;
|
|
371
|
+
lineTerminatorSinceLastToken ||= trivia.lineBreak;
|
|
372
|
+
htmlCloseCommentAllowed ||= trivia.lineBreak;
|
|
160
373
|
continue;
|
|
161
374
|
}
|
|
162
|
-
|
|
375
|
+
tokenLineBreakBefore = lineTerminatorSinceLastToken;
|
|
376
|
+
const previousSignature = closedParameterSignature;
|
|
377
|
+
closedParameterSignature = undefined;
|
|
378
|
+
memberAtToken = undefined;
|
|
379
|
+
const classState = currentClass();
|
|
380
|
+
const identifier = isIdentifierStartAt(source, i) || isUnicodeEscapeStart(source, i)
|
|
381
|
+
? readIdentifier(source, i)
|
|
382
|
+
: undefined;
|
|
383
|
+
if (typeAnnotation?.role === 'assertion' &&
|
|
384
|
+
atLexicalDepth(typeAnnotation.owner) &&
|
|
385
|
+
atTypeRoot(typeAnnotation) &&
|
|
386
|
+
!(ch === '>' && isArrowHead(tokens[tokens.length - 1], i)) &&
|
|
387
|
+
!assertionTypeContinues(typeAnnotation, ch, identifier?.value, tokenLineBreakBefore, source[i + 1])) {
|
|
388
|
+
// Complete the expression before class-field ASI and slash classification consume it.
|
|
389
|
+
// The token keeps its type delimiter identity; no runtime brace is invented or popped.
|
|
390
|
+
const previous = tokens[tokens.length - 1];
|
|
391
|
+
if (previous !== undefined) {
|
|
392
|
+
tokens[tokens.length - 1] = { ...previous, assertionEnd: true };
|
|
393
|
+
}
|
|
394
|
+
prevIsExpressionKeyword = false;
|
|
395
|
+
afterControlHead = false;
|
|
396
|
+
typeAnnotation = undefined;
|
|
397
|
+
inType = false;
|
|
398
|
+
}
|
|
399
|
+
const expressionOwner = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
400
|
+
if (typeAnnotation === undefined &&
|
|
401
|
+
ch === '<' &&
|
|
402
|
+
prevKind === 'identifier' &&
|
|
403
|
+
expressionOwner !== undefined &&
|
|
404
|
+
atLexicalDepth(expressionOwner)) {
|
|
405
|
+
const end = expressionTypeArgumentEnd(source, i, n, typeArgumentBounds);
|
|
406
|
+
if (end !== undefined) {
|
|
407
|
+
// Use the ordinary type overlay: it preserves template imports and lexical scopes.
|
|
408
|
+
typeAnnotation = {
|
|
409
|
+
role: 'assertion',
|
|
410
|
+
conditionalDepth: 0,
|
|
411
|
+
conditionalOperand: false,
|
|
412
|
+
importCallPending: false,
|
|
413
|
+
owner: {
|
|
414
|
+
parenDepth,
|
|
415
|
+
bracketDepth,
|
|
416
|
+
braceDepth: braceContexts.length,
|
|
417
|
+
frameDepth: frames.length,
|
|
418
|
+
},
|
|
419
|
+
parenDepth: 0,
|
|
420
|
+
bracketDepth: 0,
|
|
421
|
+
braceDepth: 0,
|
|
422
|
+
angleDepth: 0,
|
|
423
|
+
typeOperandExpected: true,
|
|
424
|
+
};
|
|
425
|
+
inType = true;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// An assertion must release its type context before the same runtime delimiter can retire
|
|
429
|
+
// an enclosing arrow. Consume the delimiter only after restoring the nearest function grammar.
|
|
430
|
+
let expressionFunctionContext = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
431
|
+
while (expressionFunctionContext !== undefined &&
|
|
432
|
+
typeAnnotation === undefined &&
|
|
433
|
+
frames[frames.length - 1]?.kind !== 'jsx-text' &&
|
|
434
|
+
frames[frames.length - 1]?.kind !== 'jsx-tag' &&
|
|
435
|
+
expressionFunctionBodyEnded(ch, parenDepth, bracketDepth, braceContexts.length, frames.length, functionContextStack.length, conditionalContexts.length, expressionFunctionContext)) {
|
|
436
|
+
closeRequireScope(expressionFunctionContexts.pop()?.requireScope);
|
|
437
|
+
expressionFunctionContext = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
438
|
+
}
|
|
439
|
+
if (typeAnnotation?.role === 'return' &&
|
|
440
|
+
tokenLineBreakBefore &&
|
|
441
|
+
atLexicalDepth(typeAnnotation.signature.owner) &&
|
|
442
|
+
atTypeRoot(typeAnnotation) &&
|
|
443
|
+
!typeAnnotation.typeOperandExpected &&
|
|
444
|
+
((identifier !== undefined &&
|
|
445
|
+
(!TYPE_OPERAND_KEYWORDS.has(identifier.value) || identifier.value === 'const')) ||
|
|
446
|
+
(ch === '[' &&
|
|
447
|
+
typeAnnotation.signature.context.isClassMember === true &&
|
|
448
|
+
computedMethodFollows(source, i, n)))) {
|
|
449
|
+
const signature = typeAnnotation.signature;
|
|
450
|
+
if (pendingFunctionContexts[pendingFunctionContexts.length - 1]?.requireScope ===
|
|
451
|
+
signature.context.requireScope) {
|
|
452
|
+
closeRequireScope(pendingFunctionContexts.pop()?.requireScope);
|
|
453
|
+
}
|
|
454
|
+
if (classState !== undefined && signature.context.isClassMember === true) {
|
|
455
|
+
classState.member = undefined;
|
|
456
|
+
}
|
|
457
|
+
typeAnnotation = undefined;
|
|
458
|
+
inType = false;
|
|
459
|
+
}
|
|
460
|
+
const pendingBodylessSignature = pendingFunctionContexts[pendingFunctionContexts.length - 1];
|
|
461
|
+
if (pendingBodylessSignature?.phase === 'awaiting-body' &&
|
|
462
|
+
atLexicalDepth(pendingBodylessSignature) &&
|
|
463
|
+
((typeAnnotation === undefined && tokenLineBreakBefore && ch !== '{' && ch !== ':') ||
|
|
464
|
+
(ch === '}' &&
|
|
465
|
+
(typeAnnotation === undefined ||
|
|
466
|
+
(typeAnnotation.role === 'return' && atTypeRoot(typeAnnotation)))))) {
|
|
467
|
+
closeRequireScope(pendingFunctionContexts.pop()?.requireScope);
|
|
468
|
+
typeAnnotation = undefined;
|
|
469
|
+
inType = false;
|
|
470
|
+
resetExpressionState();
|
|
471
|
+
}
|
|
472
|
+
if (classState !== undefined &&
|
|
473
|
+
atLexicalDepth(classState) &&
|
|
474
|
+
(typeAnnotation === undefined ||
|
|
475
|
+
(typeAnnotation.role === 'field' && atTypeRoot(typeAnnotation)) ||
|
|
476
|
+
(typeAnnotation.role === 'return' &&
|
|
477
|
+
atTypeRoot(typeAnnotation) &&
|
|
478
|
+
(ch === ';' || ch === '}')))) {
|
|
479
|
+
if (tokenLineBreakBefore &&
|
|
480
|
+
classState.member !== undefined &&
|
|
481
|
+
typeAnnotation?.typeOperandExpected !== true &&
|
|
482
|
+
classFieldCanEnd(classState.member, tokens[tokens.length - 1], ch, identifier?.value)) {
|
|
483
|
+
classState.member = undefined;
|
|
484
|
+
typeAnnotation = undefined;
|
|
485
|
+
inType = false;
|
|
486
|
+
}
|
|
487
|
+
if (ch === ';' || ch === '}') {
|
|
488
|
+
classState.member = undefined;
|
|
489
|
+
if (typeAnnotation?.role === 'field') {
|
|
490
|
+
typeAnnotation = undefined;
|
|
491
|
+
inType = false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
classState.member ??= {
|
|
496
|
+
start: tokens.length,
|
|
497
|
+
functionDepth: functionContextStack.length,
|
|
498
|
+
phase: 'head',
|
|
499
|
+
};
|
|
500
|
+
memberAtToken = classState.member;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (lineTerminatorSinceLastToken && typeAnnotation === undefined) {
|
|
504
|
+
let context = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
505
|
+
while (context !== undefined &&
|
|
506
|
+
frames[frames.length - 1]?.kind !== 'jsx-text' &&
|
|
507
|
+
frames[frames.length - 1]?.kind !== 'jsx-tag' &&
|
|
508
|
+
lineTerminatorEndsExpressionFunctionBody(ch, prevKind, prevPunct, tokens[tokens.length - 1], identifier?.value, (ch === '+' || ch === '-') && source[i + 1] === ch, parenDepth, bracketDepth, braceContexts.length, frames.length, functionContextStack.length, context)) {
|
|
509
|
+
closeRequireScope(expressionFunctionContexts.pop()?.requireScope);
|
|
510
|
+
context = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
511
|
+
}
|
|
512
|
+
lineTerminatorSinceLastToken = false;
|
|
513
|
+
}
|
|
514
|
+
const equalsOperator = ch === '='
|
|
515
|
+
? classifyEquals(source, i, typeAnnotation !== undefined && atTypeRoot(typeAnnotation))
|
|
516
|
+
: undefined;
|
|
517
|
+
if (ch === '<' && typeAnnotation === undefined) {
|
|
518
|
+
const classHeader = pendingClassContexts[pendingClassContexts.length - 1];
|
|
519
|
+
if (classHeader !== undefined &&
|
|
520
|
+
atLexicalDepth(classHeader) &&
|
|
521
|
+
(tokens[tokens.length - 1]?.kind === 'identifier' ||
|
|
522
|
+
isPunct(tokens[tokens.length - 1], ')') ||
|
|
523
|
+
isPunct(tokens[tokens.length - 1], ']') ||
|
|
524
|
+
isPunct(tokens[tokens.length - 1], '>'))) {
|
|
525
|
+
typeAnnotation = {
|
|
526
|
+
role: 'class-header',
|
|
527
|
+
owner: classHeader,
|
|
528
|
+
parenDepth: 0,
|
|
529
|
+
bracketDepth: 0,
|
|
530
|
+
braceDepth: 0,
|
|
531
|
+
angleDepth: 0,
|
|
532
|
+
typeOperandExpected: true,
|
|
533
|
+
};
|
|
534
|
+
inType = true;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// A backtick in code mode opens a (possibly nested) template literal. Inside an annotation
|
|
538
|
+
// that is a template-literal type: its text stays inert and the annotation keeps running until the
|
|
539
|
+
// type itself ends.
|
|
163
540
|
if (ch === '`') {
|
|
164
541
|
templateStart = i;
|
|
542
|
+
templateStarts.push(i);
|
|
165
543
|
inText = true;
|
|
166
544
|
prevKind = 'template';
|
|
545
|
+
if (typeAnnotation !== undefined) {
|
|
546
|
+
typeAnnotation.typeOperandExpected = false;
|
|
547
|
+
}
|
|
548
|
+
i++;
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
// JSX overlay (opt-in via the `jsx` option). A `<` in expression position that is followed by a
|
|
552
|
+
// tag name (or `>` for a fragment) opens JSX markup: its text, tags and attributes are inert,
|
|
553
|
+
// while the JavaScript inside `{...}` containers is lexed normally. Expression position is the
|
|
554
|
+
// same test that decides a regex literal, so a comparison (`a < b`, `f() < x`) is never JSX, and
|
|
555
|
+
// a TypeScript type-argument list (`<T,>`, `<T extends U>`) is excluded explicitly.
|
|
556
|
+
if (jsx &&
|
|
557
|
+
typeAnnotation === undefined &&
|
|
558
|
+
ch === '<' &&
|
|
559
|
+
i + 1 < n &&
|
|
560
|
+
isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword, afterControlHead, closedObjectBrace, tokens[tokens.length - 1])) {
|
|
561
|
+
const after = source[i + 1];
|
|
562
|
+
if (after === '>') {
|
|
563
|
+
frames.push({ kind: 'jsx-text', name: null, offset: i });
|
|
564
|
+
pushBoundary(i);
|
|
565
|
+
i += 2;
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (isIdentifierStartAt(source, i + 1) && !jsxTagIsGeneric(source, i + 1, n)) {
|
|
569
|
+
const tag = readJsxTagName(source, i + 1, n);
|
|
570
|
+
frames.push({ kind: 'jsx-tag', name: tag.name, offset: i, nameEnd: tag.end });
|
|
571
|
+
pushBoundary(i);
|
|
572
|
+
i = tag.end;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// Type delimiters belong to the field, parameter or return signature, never to runtime code.
|
|
577
|
+
// Only that owner's boundary or initializer/body can end the annotation.
|
|
578
|
+
if (typeAnnotation !== undefined) {
|
|
579
|
+
const annotation = typeAnnotation;
|
|
580
|
+
const typeFrame = frame?.kind === 'template-expr' && frame.typeOwner?.annotation === annotation
|
|
581
|
+
? frame.typeOwner
|
|
582
|
+
: undefined;
|
|
583
|
+
const atRoot = typeFrame === undefined && atTypeRoot(annotation);
|
|
584
|
+
const isTypeArrow = ch === '>' && isArrowHead(tokens[tokens.length - 1], i);
|
|
585
|
+
if (annotation.role === 'assertion' && atRoot) {
|
|
586
|
+
if (ch === '?' && annotation.conditionalOperand) {
|
|
587
|
+
annotation.conditionalDepth++;
|
|
588
|
+
annotation.conditionalOperand = false;
|
|
589
|
+
}
|
|
590
|
+
else if (ch === ':' && annotation.conditionalDepth > 0) {
|
|
591
|
+
annotation.conditionalDepth--;
|
|
592
|
+
}
|
|
593
|
+
if (ch === '(') {
|
|
594
|
+
annotation.importCallPending = false;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// Interpolations own depths relative to the surrounding type, which may itself be an object.
|
|
598
|
+
if (ch === '}' &&
|
|
599
|
+
(typeFrame !== undefined
|
|
600
|
+
? sameTypeDepth(annotation, typeFrame.depth)
|
|
601
|
+
: annotation.braceDepth === 0) &&
|
|
602
|
+
(frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr') &&
|
|
603
|
+
frame.depth === 0) {
|
|
604
|
+
frames.pop();
|
|
605
|
+
pushBoundary(i);
|
|
606
|
+
i++;
|
|
607
|
+
if (frame.kind === 'template-expr') {
|
|
608
|
+
annotation.typeOperandExpected = false;
|
|
609
|
+
inText = true;
|
|
610
|
+
resetExpressionState();
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
// Back to the enclosing JSX tag or text frame, which reads the next character itself.
|
|
614
|
+
endJsxElement();
|
|
615
|
+
}
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if ((annotation.role === 'field' || annotation.role === 'parameter') &&
|
|
619
|
+
atRoot &&
|
|
620
|
+
ch === '=' &&
|
|
621
|
+
equalsOperator?.operator === 'assignment') {
|
|
622
|
+
if (annotation.role === 'field') {
|
|
623
|
+
annotation.member.phase = 'initializer';
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
annotation.owner.phase = 'initializer';
|
|
627
|
+
}
|
|
628
|
+
typeAnnotation = undefined;
|
|
629
|
+
inType = false;
|
|
630
|
+
}
|
|
631
|
+
else if (annotation.role === 'return' &&
|
|
632
|
+
ch === '{' &&
|
|
633
|
+
atRoot &&
|
|
634
|
+
!annotation.typeOperandExpected) {
|
|
635
|
+
// A complete type was read, so this brace opens the runtime body.
|
|
636
|
+
typeAnnotation = undefined;
|
|
637
|
+
inType = false;
|
|
638
|
+
lineTerminatorSinceLastToken = false;
|
|
639
|
+
if (!annotation.signature.isFunctionKeyword &&
|
|
640
|
+
pendingFunctionContexts[pendingFunctionContexts.length - 1]?.requireScope !==
|
|
641
|
+
annotation.signature.context.requireScope) {
|
|
642
|
+
pendingFunctionContexts.push({
|
|
643
|
+
...annotation.signature.context,
|
|
644
|
+
...annotation.signature.owner,
|
|
645
|
+
parameterDepth: undefined,
|
|
646
|
+
phase: 'awaiting-body',
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
else if (isTypeArrow &&
|
|
651
|
+
atRoot &&
|
|
652
|
+
annotation.role === 'return' &&
|
|
653
|
+
annotation.signature.isArrowSignature &&
|
|
654
|
+
!functionTypeArrowContinues(source, i, n, conditional !== undefined && atLexicalDepth(conditional))) {
|
|
655
|
+
// The signature's own `=>`: everything after it is the body, not the return type.
|
|
656
|
+
typedArrowContext = annotation.signature.context;
|
|
657
|
+
typeAnnotation = undefined;
|
|
658
|
+
inType = false;
|
|
659
|
+
lineTerminatorSinceLastToken = false;
|
|
660
|
+
}
|
|
661
|
+
else if (atRoot && (ch === ';' || ch === ',' || ch === ')' || ch === ']' || ch === '}')) {
|
|
662
|
+
// A signature with no body of its own. Neither the annotation nor a pending signature may
|
|
663
|
+
// outlive it, otherwise later code (a JSX text `<`, another method body, an async arrow) is
|
|
664
|
+
// misread.
|
|
665
|
+
typeAnnotation = undefined;
|
|
666
|
+
inType = false;
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
if (ch === '{') {
|
|
670
|
+
annotation.braceDepth++;
|
|
671
|
+
annotation.typeOperandExpected = true;
|
|
672
|
+
}
|
|
673
|
+
else if (ch === '}') {
|
|
674
|
+
annotation.braceDepth = Math.max(0, annotation.braceDepth - 1);
|
|
675
|
+
annotation.typeOperandExpected = false;
|
|
676
|
+
}
|
|
677
|
+
else if (ch === '(') {
|
|
678
|
+
annotation.parenDepth++;
|
|
679
|
+
annotation.typeOperandExpected = true;
|
|
680
|
+
}
|
|
681
|
+
else if (ch === ')') {
|
|
682
|
+
annotation.parenDepth = Math.max(0, annotation.parenDepth - 1);
|
|
683
|
+
annotation.typeOperandExpected = false;
|
|
684
|
+
}
|
|
685
|
+
else if (ch === '[') {
|
|
686
|
+
annotation.bracketDepth++;
|
|
687
|
+
annotation.typeOperandExpected = true;
|
|
688
|
+
}
|
|
689
|
+
else if (ch === ']') {
|
|
690
|
+
annotation.bracketDepth = Math.max(0, annotation.bracketDepth - 1);
|
|
691
|
+
annotation.typeOperandExpected = false;
|
|
692
|
+
}
|
|
693
|
+
else if (ch === '<') {
|
|
694
|
+
annotation.angleDepth++;
|
|
695
|
+
annotation.typeOperandExpected = true;
|
|
696
|
+
}
|
|
697
|
+
else if (isTypeArrow) {
|
|
698
|
+
// A function type's own arrow (`(x: A) => B`) still expects a type on its right.
|
|
699
|
+
annotation.typeOperandExpected = true;
|
|
700
|
+
}
|
|
701
|
+
else if (ch === '>' && annotation.angleDepth > 0) {
|
|
702
|
+
annotation.angleDepth--;
|
|
703
|
+
annotation.typeOperandExpected = false;
|
|
704
|
+
}
|
|
705
|
+
else if (ch === ':' || ch === '?' || ch === '.' || ch === '|' || ch === '&') {
|
|
706
|
+
annotation.typeOperandExpected = true;
|
|
707
|
+
}
|
|
708
|
+
if (annotation.role === 'class-header' &&
|
|
709
|
+
ch === '>' &&
|
|
710
|
+
!isTypeArrow &&
|
|
711
|
+
atTypeRoot(annotation)) {
|
|
712
|
+
typeAnnotation = undefined;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
// A `}` in code mode closes the innermost open `${...}` or JSX `{...}` expression only when that
|
|
717
|
+
// expression has no open braces of its own; otherwise it is an ordinary brace inside it.
|
|
718
|
+
if (ch === '}' && typeAnnotation === undefined) {
|
|
719
|
+
if (frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr') {
|
|
720
|
+
if (frame.depth === 0) {
|
|
721
|
+
frames.pop();
|
|
722
|
+
pushBoundary(i);
|
|
723
|
+
i++;
|
|
724
|
+
if (frame.kind === 'template-expr') {
|
|
725
|
+
inText = true;
|
|
726
|
+
resetExpressionState();
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
// Back to the enclosing JSX tag or text frame, which reads the next character itself.
|
|
730
|
+
endJsxElement();
|
|
731
|
+
}
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
frame.depth -= 1;
|
|
735
|
+
}
|
|
736
|
+
// Operand completion for class-field ASI is separate from the regex/division decision.
|
|
737
|
+
// A function/class body can finish an initializer without closing an object literal.
|
|
738
|
+
const closed = braceContexts.pop();
|
|
739
|
+
const closedMethod = closed?.role === 'function' && closed.context.isClassMember === true;
|
|
740
|
+
closedClassMemberBoundary = closedMethod || closed?.role === 'static-block';
|
|
741
|
+
closedObjectBrace =
|
|
742
|
+
closed?.role === 'object' ||
|
|
743
|
+
(closed?.role === 'function' && closed.context.isExpression) ||
|
|
744
|
+
(closed?.role === 'class' && closed.isExpression);
|
|
745
|
+
closedExpressionBrace =
|
|
746
|
+
closedObjectBrace ||
|
|
747
|
+
closedMethod ||
|
|
748
|
+
closed?.role === 'function' ||
|
|
749
|
+
closed?.role === 'class';
|
|
750
|
+
if (closed?.role === 'function') {
|
|
751
|
+
closeRequireScope(closed.context.requireScope);
|
|
752
|
+
functionContextStack.pop();
|
|
753
|
+
}
|
|
754
|
+
if ((closed?.role === 'function' || closed?.role === 'static-block') &&
|
|
755
|
+
closed.owner !== undefined) {
|
|
756
|
+
closed.owner.member = undefined;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// `/` is a regex when it can start an expression, otherwise it is division.
|
|
760
|
+
if (ch === '/') {
|
|
761
|
+
if (isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword ||
|
|
762
|
+
(tokenLineBreakBefore &&
|
|
763
|
+
(isIdentifier(tokens[tokens.length - 1], 'break') ||
|
|
764
|
+
isIdentifier(tokens[tokens.length - 1], 'continue'))), afterControlHead, closedObjectBrace, tokens[tokens.length - 1])) {
|
|
765
|
+
const start = i;
|
|
766
|
+
i = skipRegex(source, i);
|
|
767
|
+
emit({ kind: 'regex', offset: start, endOffset: i });
|
|
768
|
+
prevKind = 'regex';
|
|
769
|
+
afterControlHead = false;
|
|
770
|
+
pendingControlKeyword = undefined;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
emit({ kind: 'punct', value: '/', offset: i, endOffset: i + 1 });
|
|
774
|
+
prevKind = 'punct';
|
|
775
|
+
prevPunct = '/';
|
|
776
|
+
afterControlHead = false;
|
|
777
|
+
pendingControlKeyword = undefined;
|
|
778
|
+
i++;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
// String literals (single or double quoted).
|
|
782
|
+
if (ch === "'" || ch === '"') {
|
|
783
|
+
const { value, next } = readString(source, i);
|
|
784
|
+
emit({ kind: 'string', value, offset: i, endOffset: next });
|
|
785
|
+
if (typeAnnotation !== undefined) {
|
|
786
|
+
typeAnnotation.typeOperandExpected = false;
|
|
787
|
+
}
|
|
788
|
+
prevKind = 'string';
|
|
789
|
+
afterControlHead = false;
|
|
790
|
+
pendingControlKeyword = undefined;
|
|
791
|
+
i = next;
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
// Identifiers.
|
|
795
|
+
if (identifier !== undefined) {
|
|
796
|
+
const start = i;
|
|
797
|
+
i = identifier.end;
|
|
798
|
+
const value = identifier.value;
|
|
799
|
+
if (typeAnnotation !== undefined) {
|
|
800
|
+
if (typeAnnotation.role === 'assertion') {
|
|
801
|
+
if (atLexicalDepth(typeAnnotation.owner) &&
|
|
802
|
+
atTypeRoot(typeAnnotation) &&
|
|
803
|
+
value === 'extends' &&
|
|
804
|
+
!typeAnnotation.typeOperandExpected) {
|
|
805
|
+
typeAnnotation.conditionalOperand = true;
|
|
806
|
+
}
|
|
807
|
+
typeAnnotation.importCallPending = value === 'import';
|
|
808
|
+
}
|
|
809
|
+
// `as const` completes an assertion, while prefix keywords still expect an operand.
|
|
810
|
+
typeAnnotation.typeOperandExpected =
|
|
811
|
+
TYPE_OPERAND_KEYWORDS.has(value) &&
|
|
812
|
+
!(typeAnnotation.role === 'assertion' && value === 'const');
|
|
813
|
+
}
|
|
814
|
+
const previousIdentifier = prevIdentifier;
|
|
815
|
+
const startsAssertion = (value === 'as' || value === 'satisfies') &&
|
|
816
|
+
!inType &&
|
|
817
|
+
// TypeScript permits trivia after these operators, but a preceding newline ends them.
|
|
818
|
+
!tokenLineBreakBefore &&
|
|
819
|
+
!(prevKind === 'punct' && prevPunct === '.') &&
|
|
820
|
+
!(prevKind === 'punct' &&
|
|
821
|
+
prevPunct === '}' &&
|
|
822
|
+
!closedObjectBrace &&
|
|
823
|
+
tokens[tokens.length - 1]?.assertionEnd !== true) &&
|
|
824
|
+
!afterControlHead &&
|
|
825
|
+
!prevIsExpressionKeyword &&
|
|
826
|
+
canStartTypeAssertion(tokens[tokens.length - 1]);
|
|
827
|
+
// Capture the enclosing operand grammar before this identifier replaces it. Contextual
|
|
828
|
+
// await/yield, an arrow head and a fresh embedded expression all expect an expression.
|
|
829
|
+
const expressionPosition = functionIsExpressionPosition(prevKind, prevPunct, colonIsExpression, (prevKind === 'identifier' &&
|
|
830
|
+
prevIsExpressionKeyword &&
|
|
831
|
+
!BLOCK_AFTER_KEYWORD.has(previousIdentifier ?? '') &&
|
|
832
|
+
!((previousIdentifier === 'return' || previousIdentifier === 'yield') &&
|
|
833
|
+
tokenLineBreakBefore)) ||
|
|
834
|
+
(isPunct(tokens[tokens.length - 1], '>') && isArrowHead(tokens[tokens.length - 2])) ||
|
|
835
|
+
(prevKind === 'start' && (frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr')));
|
|
836
|
+
if (value === 'async') {
|
|
837
|
+
asyncFunctionIsExpression = expressionPosition;
|
|
838
|
+
if (isIdentifier(tokens[tokens.length - 1], 'default') &&
|
|
839
|
+
isIdentifier(tokens[tokens.length - 2], 'export')) {
|
|
840
|
+
asyncFunctionIsExpression = false;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
prevIdentifier = value;
|
|
844
|
+
const isMemberName = prevKind === 'punct' && prevPunct === '.';
|
|
845
|
+
const atRoot = braceContexts.length === 0 && parenDepth === 0 && bracketDepth === 0 && frames.length === 0;
|
|
846
|
+
let moduleSignal = detectModule &&
|
|
847
|
+
!isMemberName &&
|
|
848
|
+
((value === 'import' && importSignalsModule(source, i, atRoot)) ||
|
|
849
|
+
(value === 'export' && atRoot && exportSignalsModule(source, i)));
|
|
850
|
+
const classMemberName = value === 'class' &&
|
|
851
|
+
atMethodContainer() &&
|
|
852
|
+
memberDeclarationHead(tokens, tokens.length, insideClassBody(), currentClass()?.member?.start) !== undefined;
|
|
853
|
+
const nextSignificant = skipTrivia(source, i, n);
|
|
854
|
+
const classPropertyName = value === 'class' &&
|
|
855
|
+
(insideObjectLiteral() || insideClassBody()) &&
|
|
856
|
+
[':', '=', '(', ',', '}'].includes(source[nextSignificant] ?? '');
|
|
857
|
+
if (value === 'class' &&
|
|
858
|
+
!isMemberName &&
|
|
859
|
+
!classMemberName &&
|
|
860
|
+
!classPropertyName &&
|
|
861
|
+
typeAnnotation === undefined) {
|
|
862
|
+
const isExportDefault = isIdentifier(tokens[tokens.length - 1], 'default') &&
|
|
863
|
+
isIdentifier(tokens[tokens.length - 2], 'export');
|
|
864
|
+
pendingClassContexts.push({
|
|
865
|
+
parenDepth,
|
|
866
|
+
bracketDepth,
|
|
867
|
+
braceDepth: braceContexts.length,
|
|
868
|
+
frameDepth: frames.length,
|
|
869
|
+
isExpression: !isExportDefault && (previousIdentifier === 'extends' || expressionPosition),
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
// A keyword after `.` is a property name (e.g. `x.return`), not an expression start.
|
|
873
|
+
const forIterationSeparator = (value === 'of' || value === 'in') &&
|
|
874
|
+
!isMemberName &&
|
|
875
|
+
consumeForIterationSeparator(tokens, parameterContexts, {
|
|
876
|
+
parenDepth,
|
|
877
|
+
bracketDepth,
|
|
878
|
+
braceDepth: braceContexts.length,
|
|
879
|
+
frameDepth: frames.length,
|
|
880
|
+
});
|
|
881
|
+
let isExpressionKeyword = (REGEX_PRECEDING_KEYWORDS.has(value) || forIterationSeparator) && !isMemberName;
|
|
882
|
+
// `await`/`yield` are keywords only inside an async / generator function body. A member name
|
|
883
|
+
// (`obj.await`) is always a property access, never a keyword, so a following `/` divides.
|
|
884
|
+
if (!isMemberName) {
|
|
885
|
+
const expressionContext = expressionFunctionContexts[expressionFunctionContexts.length - 1];
|
|
886
|
+
const bodyContext = functionContextStack[functionContextStack.length - 1];
|
|
887
|
+
const parameterOwner = nearestFunctionParameter(parameterContexts);
|
|
888
|
+
const classMemberOwner = nearestClassField(braceContexts);
|
|
889
|
+
const parameterGrammarSuspended = parameterOwner !== undefined &&
|
|
890
|
+
functionContextStack.length <= parameterOwner.functionDepth &&
|
|
891
|
+
expressionContext?.parameterOwner !== parameterOwner;
|
|
892
|
+
const fieldGrammarSuspended = classMemberOwner !== undefined &&
|
|
893
|
+
functionContextStack.length <= classMemberOwner.functionDepth &&
|
|
894
|
+
expressionContext?.classMemberOwner !== classMemberOwner;
|
|
895
|
+
const expressionContextIsCurrent = expressionContext !== undefined &&
|
|
896
|
+
functionContextStack.length <= expressionContext.functionDepth
|
|
897
|
+
? expressionContext
|
|
898
|
+
: undefined;
|
|
899
|
+
const functionContext = parameterGrammarSuspended || fieldGrammarSuspended
|
|
900
|
+
? undefined
|
|
901
|
+
: (expressionContextIsCurrent ?? bodyContext);
|
|
902
|
+
const probe = detectModule &&
|
|
903
|
+
value === 'await' &&
|
|
904
|
+
!(prevKind === 'identifier' &&
|
|
905
|
+
['class', 'function', 'var', 'let', 'const', 'extends'].includes(previousIdentifier ?? '')) &&
|
|
906
|
+
functionContext === undefined &&
|
|
907
|
+
!parameterGrammarSuspended &&
|
|
908
|
+
!fieldGrammarSuspended
|
|
909
|
+
? probeAwaitOperand(source, i, parameterContexts[parameterContexts.length - 1]?.forPhase !== undefined)
|
|
910
|
+
: undefined;
|
|
911
|
+
moduleSignal ||= probe?.module === true;
|
|
912
|
+
if (value === 'await' &&
|
|
913
|
+
(functionContext?.async === true ||
|
|
914
|
+
probe?.keyword === true ||
|
|
915
|
+
(functionContext === undefined &&
|
|
916
|
+
module &&
|
|
917
|
+
!parameterGrammarSuspended &&
|
|
918
|
+
!fieldGrammarSuspended))) {
|
|
919
|
+
isExpressionKeyword = true;
|
|
920
|
+
}
|
|
921
|
+
else if (value === 'yield' && functionContext?.generator === true) {
|
|
922
|
+
isExpressionKeyword = true;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
prevIsExpressionKeyword = isExpressionKeyword;
|
|
926
|
+
// Remember a control-statement keyword until its `(` is seen so only that head's `)` may be
|
|
927
|
+
// followed by a regex literal.
|
|
928
|
+
pendingControlKeyword = pendingControlKeywordAfter(value, isMemberName || insideObjectLiteral() || insideClassBody(), pendingControlKeyword);
|
|
929
|
+
// A `function` keyword (not a property name) starts a function signature. Track whether it is
|
|
930
|
+
// an expression (`const f = function(){}`) or a declaration (`function f(){}`), and whether it
|
|
931
|
+
// is async (`async function`, i.e. preceded by `async`) or a generator (`function*`, resolved
|
|
932
|
+
// when a following `*` is seen). The context is consumed at the body `{`.
|
|
933
|
+
const methodName = value === 'function' &&
|
|
934
|
+
atMethodContainer() &&
|
|
935
|
+
memberDeclarationHead(tokens, tokens.length, insideClassBody(), currentClass()?.member?.start) !== undefined;
|
|
936
|
+
if (value === 'function' && !isMemberName && !methodName && typeAnnotation === undefined) {
|
|
937
|
+
// `async function` is only async without a line terminator between the two words: a line
|
|
938
|
+
// break ends the `async` statement, so the declaration that follows is an ordinary one.
|
|
939
|
+
const isAsync = previousIdentifier === 'async' && prevKind === 'identifier' && !tokenLineBreakBefore;
|
|
940
|
+
pendingFunctionContexts.push({
|
|
941
|
+
requireScope: {
|
|
942
|
+
parent: requireScopes[requireScopes.length - 1],
|
|
943
|
+
bindings: [],
|
|
944
|
+
inParameters: true,
|
|
945
|
+
},
|
|
946
|
+
async: isAsync,
|
|
947
|
+
generator: false,
|
|
948
|
+
isExpression: isAsync
|
|
949
|
+
? asyncFunctionIsExpression
|
|
950
|
+
: !(isIdentifier(tokens[tokens.length - 1], 'default') &&
|
|
951
|
+
isIdentifier(tokens[tokens.length - 2], 'export')) && expressionPosition,
|
|
952
|
+
parenDepth,
|
|
953
|
+
bracketDepth,
|
|
954
|
+
braceDepth: braceContexts.length,
|
|
955
|
+
frameDepth: frames.length,
|
|
956
|
+
parameterDepth: undefined,
|
|
957
|
+
phase: 'awaiting-parameters',
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
// A bare arrow parameter has no opening parenthesis to own its binding range.
|
|
961
|
+
const next = !inType && !isMemberName ? skipTrivia(source, i, n) : n;
|
|
962
|
+
const singleArrowContext = source[next] === '=' && source[next + 1] === '>'
|
|
963
|
+
? {
|
|
964
|
+
async: prevKind === 'identifier' &&
|
|
965
|
+
previousIdentifier === 'async' &&
|
|
966
|
+
!tokenLineBreakBefore,
|
|
967
|
+
generator: false,
|
|
968
|
+
isExpression: false,
|
|
969
|
+
requireScope: {
|
|
970
|
+
parent: requireScopes[requireScopes.length - 1],
|
|
971
|
+
bindings: [{ start: tokens.length, end: tokens.length + 1 }],
|
|
972
|
+
inParameters: false,
|
|
973
|
+
},
|
|
974
|
+
}
|
|
975
|
+
: undefined;
|
|
976
|
+
if (singleArrowContext?.requireScope !== undefined) {
|
|
977
|
+
requireScopes.push(singleArrowContext.requireScope);
|
|
978
|
+
}
|
|
979
|
+
emit({
|
|
980
|
+
kind: 'identifier',
|
|
981
|
+
value,
|
|
982
|
+
offset: start,
|
|
983
|
+
...(singleArrowContext === undefined ? {} : { signatureContext: singleArrowContext }),
|
|
984
|
+
endOffset: i,
|
|
985
|
+
...(value === 'require' && requireScopes.length > 0
|
|
986
|
+
? { requireScope: requireScopes[requireScopes.length - 1] }
|
|
987
|
+
: {}),
|
|
988
|
+
...(isMemberName ? { propertyName: true } : {}),
|
|
989
|
+
...(moduleSignal ? { moduleSignal: true } : {}),
|
|
990
|
+
});
|
|
991
|
+
// Once the mode is known, only the definitive module pass should validate the remainder.
|
|
992
|
+
// Continuing this probe with script grammar could reject valid newline await operands.
|
|
993
|
+
if (detectModule && moduleSignal) {
|
|
994
|
+
return tokens;
|
|
995
|
+
}
|
|
996
|
+
if (startsAssertion) {
|
|
997
|
+
typeAnnotation = {
|
|
998
|
+
role: 'assertion',
|
|
999
|
+
conditionalDepth: 0,
|
|
1000
|
+
conditionalOperand: false,
|
|
1001
|
+
importCallPending: false,
|
|
1002
|
+
owner: {
|
|
1003
|
+
parenDepth,
|
|
1004
|
+
bracketDepth,
|
|
1005
|
+
braceDepth: braceContexts.length,
|
|
1006
|
+
frameDepth: frames.length,
|
|
1007
|
+
},
|
|
1008
|
+
parenDepth: 0,
|
|
1009
|
+
bracketDepth: 0,
|
|
1010
|
+
braceDepth: 0,
|
|
1011
|
+
angleDepth: 0,
|
|
1012
|
+
typeOperandExpected: true,
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
prevKind = 'identifier';
|
|
1016
|
+
afterControlHead = false;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
// Numbers end an expression, so they affect the regex/division decision.
|
|
1020
|
+
if (isNumberStart(ch)) {
|
|
1021
|
+
const start = i;
|
|
167
1022
|
i++;
|
|
1023
|
+
while (i < n) {
|
|
1024
|
+
const part = source[i];
|
|
1025
|
+
if (part === undefined || !(isIdentifierPartAt(source, i) || part === '.')) {
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
i++;
|
|
1029
|
+
}
|
|
1030
|
+
emit({ kind: 'number', offset: start, endOffset: i });
|
|
1031
|
+
if (typeAnnotation !== undefined) {
|
|
1032
|
+
typeAnnotation.typeOperandExpected = false;
|
|
1033
|
+
}
|
|
1034
|
+
prevKind = 'number';
|
|
1035
|
+
afterControlHead = false;
|
|
1036
|
+
pendingControlKeyword = undefined;
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
if (ch === ':' && typeAnnotation === undefined) {
|
|
1040
|
+
const parameter = parameterContexts[parameterContexts.length - 1];
|
|
1041
|
+
if (previousSignature !== undefined && atLexicalDepth(previousSignature.owner)) {
|
|
1042
|
+
typeAnnotation = {
|
|
1043
|
+
role: 'return',
|
|
1044
|
+
signature: previousSignature,
|
|
1045
|
+
parenDepth: 0,
|
|
1046
|
+
bracketDepth: 0,
|
|
1047
|
+
braceDepth: 0,
|
|
1048
|
+
angleDepth: 0,
|
|
1049
|
+
typeOperandExpected: true,
|
|
1050
|
+
};
|
|
1051
|
+
inType = true;
|
|
1052
|
+
}
|
|
1053
|
+
else if (classState !== undefined &&
|
|
1054
|
+
memberAtToken !== undefined &&
|
|
1055
|
+
(memberAtToken.phase === 'field' || memberAtToken.phase === 'head')) {
|
|
1056
|
+
typeAnnotation = {
|
|
1057
|
+
role: 'field',
|
|
1058
|
+
owner: classState,
|
|
1059
|
+
member: memberAtToken,
|
|
1060
|
+
parenDepth: 0,
|
|
1061
|
+
bracketDepth: 0,
|
|
1062
|
+
braceDepth: 0,
|
|
1063
|
+
angleDepth: 0,
|
|
1064
|
+
typeOperandExpected: true,
|
|
1065
|
+
};
|
|
1066
|
+
memberAtToken.phase = 'type';
|
|
1067
|
+
inType = true;
|
|
1068
|
+
}
|
|
1069
|
+
else if (parameter?.signature !== undefined &&
|
|
1070
|
+
parameter.phase === 'binding' &&
|
|
1071
|
+
atLexicalDepth(parameter.depth)) {
|
|
1072
|
+
typeAnnotation = {
|
|
1073
|
+
role: 'parameter',
|
|
1074
|
+
owner: parameter,
|
|
1075
|
+
parenDepth: 0,
|
|
1076
|
+
bracketDepth: 0,
|
|
1077
|
+
braceDepth: 0,
|
|
1078
|
+
angleDepth: 0,
|
|
1079
|
+
typeOperandExpected: true,
|
|
1080
|
+
};
|
|
1081
|
+
parameter.bindingEnd ??= tokens.length;
|
|
1082
|
+
parameter.phase = 'type';
|
|
1083
|
+
inType = true;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
// Any other single character is punctuation. This (and the `:`/`;`/`}` handlers below) is the
|
|
1087
|
+
// only place that pushes a conditionalContext, and it is reached solely while scanning real
|
|
1088
|
+
// JavaScript: template text, JSX markup and `${...}`/`{...}` expression bodies are consumed by
|
|
1089
|
+
// the text-mode gates above, so inert punctuation inside them never contributes a conditional.
|
|
1090
|
+
// A `?` in template text is a plain character, not a ternary, so it must not leak here.
|
|
1091
|
+
if (!inType &&
|
|
1092
|
+
ch === '?' &&
|
|
1093
|
+
(memberAtToken === undefined || memberAtToken.phase === 'initializer') &&
|
|
1094
|
+
source[i + 1] !== '?' &&
|
|
1095
|
+
source[i - 1] !== '?' &&
|
|
1096
|
+
source[i + 1] !== '.') {
|
|
1097
|
+
conditionalContexts.push({
|
|
1098
|
+
parenDepth,
|
|
1099
|
+
bracketDepth,
|
|
1100
|
+
braceDepth: braceContexts.length,
|
|
1101
|
+
frameDepth: frames.length,
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
else if (!inType && ch === ':') {
|
|
1105
|
+
const isConditional = conditional !== undefined && atLexicalDepth(conditional);
|
|
1106
|
+
if (isConditional) {
|
|
1107
|
+
conditionalContexts.pop();
|
|
1108
|
+
}
|
|
1109
|
+
colonIsExpression = isConditional || insideObjectLiteral();
|
|
1110
|
+
}
|
|
1111
|
+
else if (!inType && ch === ';' && conditional !== undefined && atLexicalDepth(conditional)) {
|
|
1112
|
+
conditionalContexts.pop();
|
|
1113
|
+
}
|
|
1114
|
+
if (!inType && ch === '{') {
|
|
1115
|
+
// Record what this brace opens, so its `}` knows whether an expression or a statement ended.
|
|
1116
|
+
let isObject = opensObjectLiteral(prevKind, prevPunct, prevIdentifier, prevIsExpressionKeyword, frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr', colonIsExpression, tokenLineBreakBefore);
|
|
1117
|
+
const pending = pendingFunctionContexts[pendingFunctionContexts.length - 1];
|
|
1118
|
+
let context;
|
|
1119
|
+
if (pending?.phase === 'awaiting-body' && atLexicalDepth(pending)) {
|
|
1120
|
+
context = pendingFunctionContexts.pop();
|
|
1121
|
+
}
|
|
1122
|
+
if (context !== undefined) {
|
|
1123
|
+
isObject = false;
|
|
1124
|
+
}
|
|
1125
|
+
const classExpression = !isObject && context === undefined
|
|
1126
|
+
? takeClassBodyContext(pendingClassContexts, {
|
|
1127
|
+
parenDepth,
|
|
1128
|
+
bracketDepth,
|
|
1129
|
+
braceDepth: braceContexts.length,
|
|
1130
|
+
frameDepth: frames.length,
|
|
1131
|
+
})
|
|
1132
|
+
: undefined;
|
|
1133
|
+
const isClassBody = classExpression !== undefined;
|
|
1134
|
+
opensClassMemberBoundary = isClassBody;
|
|
1135
|
+
const isStaticBlock = !isObject &&
|
|
1136
|
+
!isClassBody &&
|
|
1137
|
+
insideClassBody() &&
|
|
1138
|
+
isIdentifier(tokens[tokens.length - 1], 'static');
|
|
1139
|
+
// A statement-block brace that consumes a pending function signature is a function body. A
|
|
1140
|
+
// function expression used as a value ends an expression (so a following `/` divides), while a
|
|
1141
|
+
// declaration in statement position starts a block (so a following `/` may be a regex).
|
|
1142
|
+
//
|
|
1143
|
+
// Methods consume the signature captured at their parameter opener. Arrows use their
|
|
1144
|
+
// contiguous operator; neither a control head nor an unrelated call can claim this brace.
|
|
1145
|
+
if (context === undefined && !isObject && !isClassBody) {
|
|
1146
|
+
const last = tokens[tokens.length - 1];
|
|
1147
|
+
context =
|
|
1148
|
+
isPunct(last, '>') && isArrowHead(tokens[tokens.length - 2])
|
|
1149
|
+
? inferArrowContext(tokens, tokens.length - 2)
|
|
1150
|
+
: previousSignature !== undefined &&
|
|
1151
|
+
!previousSignature.isArrowSignature &&
|
|
1152
|
+
atLexicalDepth(previousSignature.owner)
|
|
1153
|
+
? previousSignature.context
|
|
1154
|
+
: undefined;
|
|
1155
|
+
}
|
|
1156
|
+
if (!isObject && context !== undefined) {
|
|
1157
|
+
functionContextStack.push(context);
|
|
1158
|
+
}
|
|
1159
|
+
const owner = currentClass();
|
|
1160
|
+
if (context !== undefined) {
|
|
1161
|
+
braceContexts.push({
|
|
1162
|
+
role: 'function',
|
|
1163
|
+
context,
|
|
1164
|
+
...(context.isClassMember === true && owner !== undefined ? { owner } : {}),
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
else if (isClassBody) {
|
|
1168
|
+
braceContexts.push({
|
|
1169
|
+
role: 'class',
|
|
1170
|
+
isExpression: classExpression.isExpression,
|
|
1171
|
+
state: {
|
|
1172
|
+
parenDepth,
|
|
1173
|
+
bracketDepth,
|
|
1174
|
+
braceDepth: braceContexts.length + 1,
|
|
1175
|
+
frameDepth: frames.length,
|
|
1176
|
+
member: undefined,
|
|
1177
|
+
},
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
else if (isStaticBlock && owner !== undefined) {
|
|
1181
|
+
braceContexts.push({ role: 'static-block', owner });
|
|
1182
|
+
}
|
|
1183
|
+
else {
|
|
1184
|
+
braceContexts.push(isObject
|
|
1185
|
+
? {
|
|
1186
|
+
role: 'object',
|
|
1187
|
+
depth: {
|
|
1188
|
+
parenDepth,
|
|
1189
|
+
bracketDepth,
|
|
1190
|
+
braceDepth: braceContexts.length + 1,
|
|
1191
|
+
frameDepth: frames.length,
|
|
1192
|
+
},
|
|
1193
|
+
}
|
|
1194
|
+
: { role: 'block' });
|
|
1195
|
+
}
|
|
1196
|
+
// Count the brace inside the innermost open `${...}` or JSX `{...}` expression so its closing
|
|
1197
|
+
// `}` is found.
|
|
1198
|
+
if (frame?.kind === 'template-expr' || frame?.kind === 'jsx-expr') {
|
|
1199
|
+
frame.depth += 1;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
else if (!inType && ch === '(') {
|
|
1203
|
+
// A `(` after a control keyword opens a control head; only its matching `)` may then be
|
|
1204
|
+
// followed by a regex literal, so the nesting depth of every `(` has to be tracked.
|
|
1205
|
+
const pending = pendingFunctionContexts[pendingFunctionContexts.length - 1];
|
|
1206
|
+
const signatureCandidate = parameterSignatureContext(tokens, pending !== undefined && atLexicalDepth(pending) ? pending : undefined, atMethodContainer(), insideClassBody(), tokenLineBreakBefore, currentClass()?.member?.start);
|
|
1207
|
+
// A ternary's alternate may itself be an arrow. Its => is not proof that
|
|
1208
|
+
// the consequent's parentheses were parameters: a typed arrow must still
|
|
1209
|
+
// leave the enclosing conditional's colon after its own body.
|
|
1210
|
+
const needsConditionalEnd = conditional !== undefined && atLexicalDepth(conditional);
|
|
1211
|
+
const selectedSignature = signatureCandidate?.isArrowSignature === true &&
|
|
1212
|
+
!arrowFollowsParametersSafely(source, i, n, jsx, needsConditionalEnd)
|
|
1213
|
+
? undefined
|
|
1214
|
+
: signatureCandidate;
|
|
1215
|
+
const signature = selectedSignature === undefined
|
|
1216
|
+
? undefined
|
|
1217
|
+
: {
|
|
1218
|
+
...selectedSignature,
|
|
1219
|
+
context: selectedSignature.context.requireScope !== undefined
|
|
1220
|
+
? selectedSignature.context
|
|
1221
|
+
: {
|
|
1222
|
+
...selectedSignature.context,
|
|
1223
|
+
requireScope: {
|
|
1224
|
+
parent: requireScopes[requireScopes.length - 1],
|
|
1225
|
+
bindings: [],
|
|
1226
|
+
inParameters: true,
|
|
1227
|
+
},
|
|
1228
|
+
},
|
|
1229
|
+
};
|
|
1230
|
+
if (signature?.context.requireScope !== undefined) {
|
|
1231
|
+
requireScopes.push(signature.context.requireScope);
|
|
1232
|
+
}
|
|
1233
|
+
parameterContexts.push({
|
|
1234
|
+
openIndex: tokens.length,
|
|
1235
|
+
bindingStart: tokens.length + 1,
|
|
1236
|
+
bindingEnd: undefined,
|
|
1237
|
+
bindingCommas: [],
|
|
1238
|
+
signature: signature === undefined
|
|
1239
|
+
? undefined
|
|
1240
|
+
: {
|
|
1241
|
+
...signature,
|
|
1242
|
+
owner: {
|
|
1243
|
+
parenDepth,
|
|
1244
|
+
bracketDepth,
|
|
1245
|
+
braceDepth: braceContexts.length,
|
|
1246
|
+
frameDepth: frames.length,
|
|
1247
|
+
},
|
|
1248
|
+
},
|
|
1249
|
+
control: pendingControlKeyword !== undefined,
|
|
1250
|
+
controlKeyword: pendingControlKeyword,
|
|
1251
|
+
functionDepth: functionContextStack.length,
|
|
1252
|
+
depth: {
|
|
1253
|
+
parenDepth: parenDepth + 1,
|
|
1254
|
+
bracketDepth,
|
|
1255
|
+
braceDepth: braceContexts.length,
|
|
1256
|
+
frameDepth: frames.length,
|
|
1257
|
+
},
|
|
1258
|
+
phase: 'binding',
|
|
1259
|
+
...(pendingControlKeyword === 'for' ? { forPhase: 'binding' } : {}),
|
|
1260
|
+
});
|
|
1261
|
+
if (pending?.phase === 'awaiting-parameters' &&
|
|
1262
|
+
atLexicalDepth(pending) &&
|
|
1263
|
+
signature?.isFunctionKeyword === true) {
|
|
1264
|
+
pending.parameterDepth = parenDepth + 1;
|
|
1265
|
+
pending.phase = 'parameters';
|
|
1266
|
+
}
|
|
1267
|
+
parenDepth++;
|
|
1268
|
+
pendingControlKeyword = undefined;
|
|
1269
|
+
afterControlHead = false;
|
|
1270
|
+
}
|
|
1271
|
+
else if (!inType && ch === '[') {
|
|
1272
|
+
bracketDepth++;
|
|
1273
|
+
}
|
|
1274
|
+
else if (!inType && ch === ')') {
|
|
1275
|
+
const pending = pendingFunctionContexts[pendingFunctionContexts.length - 1];
|
|
1276
|
+
if (pending?.parameterDepth === parenDepth &&
|
|
1277
|
+
bracketDepth === pending.bracketDepth &&
|
|
1278
|
+
braceContexts.length === pending.braceDepth &&
|
|
1279
|
+
frames.length === pending.frameDepth) {
|
|
1280
|
+
pending.phase = 'awaiting-body';
|
|
1281
|
+
}
|
|
1282
|
+
const parameters = parameterContexts.pop();
|
|
1283
|
+
if (parameters?.signature !== undefined) {
|
|
1284
|
+
recordRequireBinding(parameters, tokens.length);
|
|
1285
|
+
const scope = parameters.signature.context.requireScope;
|
|
1286
|
+
if (scope !== undefined) {
|
|
1287
|
+
scope.inParameters = false;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
matchingOpen = parameters?.openIndex;
|
|
1291
|
+
closedParameterSignature = parameters?.signature;
|
|
1292
|
+
if (closedParameterSignature !== undefined &&
|
|
1293
|
+
!closedParameterSignature.isFunctionKeyword &&
|
|
1294
|
+
!closedParameterSignature.isArrowSignature) {
|
|
1295
|
+
pendingFunctionContexts.push({
|
|
1296
|
+
...closedParameterSignature.context,
|
|
1297
|
+
...closedParameterSignature.owner,
|
|
1298
|
+
parameterDepth: undefined,
|
|
1299
|
+
phase: 'awaiting-body',
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
afterControlHead = parameters?.control === true;
|
|
1303
|
+
if (parenDepth > 0) {
|
|
1304
|
+
parenDepth--;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
else if (!inType && ch === ']') {
|
|
1308
|
+
if (bracketDepth > 0) {
|
|
1309
|
+
bracketDepth--;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
if (!inType && (ch === '+' || ch === '-') && source[i + 1] === ch) {
|
|
1313
|
+
const previous = tokens[tokens.length - 1];
|
|
1314
|
+
const postfix = !tokenLineBreakBefore &&
|
|
1315
|
+
previous !== undefined &&
|
|
1316
|
+
isMemberExpressionEnd(previous) &&
|
|
1317
|
+
!prevIsExpressionKeyword;
|
|
1318
|
+
emit({
|
|
1319
|
+
kind: 'punct',
|
|
1320
|
+
value: ch + ch,
|
|
1321
|
+
offset: i,
|
|
1322
|
+
endOffset: i + 2,
|
|
1323
|
+
expressionEnd: postfix,
|
|
1324
|
+
});
|
|
1325
|
+
prevKind = 'punct';
|
|
1326
|
+
prevPunct = ch + ch;
|
|
1327
|
+
i += 2;
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
// An arrow's expression body has no brace where its function context can be consumed. Record
|
|
1331
|
+
// its bounded context after `=>`; a following delimiter removes it again.
|
|
1332
|
+
if (!inType && ch === '>' && isArrowHead(tokens[tokens.length - 1], i)) {
|
|
1333
|
+
const context = typedArrowContext ?? inferArrowContext(tokens, tokens.length - 1);
|
|
1334
|
+
const next = skipTrivia(source, i + 1, n);
|
|
1335
|
+
if (context !== undefined) {
|
|
1336
|
+
if (source[next] === '{') {
|
|
1337
|
+
pendingFunctionContexts.push({
|
|
1338
|
+
...context,
|
|
1339
|
+
parenDepth,
|
|
1340
|
+
bracketDepth,
|
|
1341
|
+
braceDepth: braceContexts.length,
|
|
1342
|
+
frameDepth: frames.length,
|
|
1343
|
+
parameterDepth: undefined,
|
|
1344
|
+
phase: 'awaiting-body',
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
else {
|
|
1348
|
+
const parameterOwner = nearestFunctionParameter(parameterContexts);
|
|
1349
|
+
const classMemberOwner = nearestClassField(braceContexts);
|
|
1350
|
+
expressionFunctionContexts.push({
|
|
1351
|
+
...context,
|
|
1352
|
+
isExpression: true,
|
|
1353
|
+
parenDepth,
|
|
1354
|
+
bracketDepth,
|
|
1355
|
+
braceDepth: braceContexts.length,
|
|
1356
|
+
frameDepth: frames.length,
|
|
1357
|
+
functionDepth: functionContextStack.length,
|
|
1358
|
+
conditionalDepth: conditionalContexts.length,
|
|
1359
|
+
...(parameterOwner === undefined ? {} : { parameterOwner }),
|
|
1360
|
+
...(classMemberOwner === undefined ? {} : { classMemberOwner }),
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
// A `*` right after `function` (whitespace/comments aside) marks a generator: `function*` and
|
|
1366
|
+
// `function *`. It is recorded on the pending signature, consumed at the body `{`.
|
|
1367
|
+
const pending = pendingFunctionContexts[pendingFunctionContexts.length - 1];
|
|
1368
|
+
const parameter = parameterContexts[parameterContexts.length - 1];
|
|
1369
|
+
if (ch === ';' && parameter?.forPhase !== undefined && atLexicalDepth(parameter.depth)) {
|
|
1370
|
+
parameter.forPhase = 'regular';
|
|
1371
|
+
}
|
|
1372
|
+
if (!inType && parameter !== undefined && atLexicalDepth(parameter.depth)) {
|
|
1373
|
+
if (ch === ',') {
|
|
1374
|
+
const scope = parameter.signature?.context.requireScope;
|
|
1375
|
+
if (scope !== undefined) {
|
|
1376
|
+
parameter.bindingCommas.push({
|
|
1377
|
+
index: tokens.length,
|
|
1378
|
+
bindingStart: parameter.bindingStart,
|
|
1379
|
+
bindingEnd: parameter.bindingEnd,
|
|
1380
|
+
phase: parameter.phase,
|
|
1381
|
+
bindingCount: scope.bindings.length,
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
recordRequireBinding(parameter, tokens.length);
|
|
1385
|
+
parameter.bindingStart = tokens.length + 1;
|
|
1386
|
+
parameter.bindingEnd = undefined;
|
|
1387
|
+
parameter.phase = 'binding';
|
|
1388
|
+
}
|
|
1389
|
+
else if (equalsOperator?.operator === 'assignment') {
|
|
1390
|
+
parameter.bindingEnd ??= tokens.length;
|
|
1391
|
+
parameter.phase = 'initializer';
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
if (!inType &&
|
|
1395
|
+
ch === '*' &&
|
|
1396
|
+
prevKind === 'identifier' &&
|
|
1397
|
+
prevIdentifier === 'function' &&
|
|
1398
|
+
pending !== undefined) {
|
|
1399
|
+
pendingFunctionContexts[pendingFunctionContexts.length - 1] = {
|
|
1400
|
+
...pending,
|
|
1401
|
+
generator: true,
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
// A callback's statements cannot invalidate a signature outside its body or parameter list.
|
|
1405
|
+
if (!inType && ch === ';' && pending !== undefined && atLexicalDepth(pending)) {
|
|
1406
|
+
closeRequireScope(pendingFunctionContexts.pop()?.requireScope);
|
|
1407
|
+
}
|
|
1408
|
+
const previousToken = tokens[tokens.length - 1];
|
|
1409
|
+
const openingSignature = ch === '(' ? parameterContexts[parameterContexts.length - 1]?.signature?.context : undefined;
|
|
1410
|
+
emit({
|
|
1411
|
+
kind: 'punct',
|
|
1412
|
+
value: ch,
|
|
1413
|
+
offset: i,
|
|
1414
|
+
endOffset: i + 1,
|
|
1415
|
+
...(ch === '!' &&
|
|
1416
|
+
source[i + 1] !== '=' &&
|
|
1417
|
+
previousToken !== undefined &&
|
|
1418
|
+
isMemberExpressionEnd(previousToken) &&
|
|
1419
|
+
!prevIsExpressionKeyword
|
|
1420
|
+
? { expressionEnd: true }
|
|
1421
|
+
: {}),
|
|
1422
|
+
...equalsOperator,
|
|
1423
|
+
...(openingSignature === undefined ? {} : { signatureContext: openingSignature }),
|
|
1424
|
+
...(matchingOpen !== undefined ? { matchingOpen } : {}),
|
|
1425
|
+
...(closedClassMemberBoundary || opensClassMemberBoundary || (ch === ';' && insideClassBody())
|
|
1426
|
+
? { memberSeparator: true }
|
|
1427
|
+
: {}),
|
|
1428
|
+
...(closedExpressionBrace ? { expressionEnd: true } : {}),
|
|
1429
|
+
});
|
|
1430
|
+
if (!inType &&
|
|
1431
|
+
ch === '>' &&
|
|
1432
|
+
requireScopes[requireScopes.length - 1]?.inParameters === true &&
|
|
1433
|
+
!isArrowHead(tokens[tokens.length - 2], i)) {
|
|
1434
|
+
const start = markParameterTypeArguments(tokens, tokens.length);
|
|
1435
|
+
const owner = parameterContexts[parameterContexts.length - 1];
|
|
1436
|
+
const scope = owner?.signature?.context.requireScope;
|
|
1437
|
+
if (start !== undefined && owner !== undefined && scope !== undefined) {
|
|
1438
|
+
// Commas were provisional until the closing > established a type-argument list.
|
|
1439
|
+
// Restore the initializer state as well as its binding ranges; do not re-lex source.
|
|
1440
|
+
let first = owner.bindingCommas.length;
|
|
1441
|
+
while (first > 0 && (owner.bindingCommas[first - 1]?.index ?? -1) >= start) {
|
|
1442
|
+
first--;
|
|
1443
|
+
}
|
|
1444
|
+
const checkpoint = owner.bindingCommas[first];
|
|
1445
|
+
if (checkpoint !== undefined) {
|
|
1446
|
+
owner.bindingStart = checkpoint.bindingStart;
|
|
1447
|
+
owner.bindingEnd = checkpoint.bindingEnd;
|
|
1448
|
+
owner.phase = checkpoint.phase;
|
|
1449
|
+
scope.bindings.length = checkpoint.bindingCount;
|
|
1450
|
+
owner.bindingCommas.length = first;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
prevKind = 'punct';
|
|
1455
|
+
prevPunct = ch;
|
|
1456
|
+
i++;
|
|
1457
|
+
}
|
|
1458
|
+
// Reaching the end of the source with an open frame means the template literal, the JSX element or
|
|
1459
|
+
// the JSX expression container was never closed. The innermost open frame is the one that failed.
|
|
1460
|
+
const openFrame = frames[frames.length - 1];
|
|
1461
|
+
if (inText || openFrame?.kind === 'template-expr') {
|
|
1462
|
+
throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', inText ? templateStart : (openFrame?.offset ?? templateStart));
|
|
1463
|
+
}
|
|
1464
|
+
if (openFrame?.kind === 'jsx-expr') {
|
|
1465
|
+
throw new ModuleSpecifierError('Unterminated JSX expression', 'invalid-source', openFrame.offset);
|
|
1466
|
+
}
|
|
1467
|
+
if (openFrame !== undefined) {
|
|
1468
|
+
throw new ModuleSpecifierError('Unterminated JSX element', 'invalid-source', openFrame.offset);
|
|
1469
|
+
}
|
|
1470
|
+
return tokens;
|
|
1471
|
+
}
|
|
1472
|
+
// A `<` followed by an identifier that begins a generic type-argument list (e.g. `<T,>`, `<T = X>`,
|
|
1473
|
+
// `<T extends U>`, `<const T,>`) or a generic function signature (`<T>(x: T) => T`) is not a JSX
|
|
1474
|
+
// element. `<T>` alone in expression position remains ambiguous and is treated as JSX.
|
|
1475
|
+
function jsxTagIsGeneric(source, i, n) {
|
|
1476
|
+
try {
|
|
1477
|
+
if (!isIdentifierStartAt(source, i)) {
|
|
1478
|
+
return false;
|
|
1479
|
+
}
|
|
1480
|
+
const identifier = readIdentifier(source, i);
|
|
1481
|
+
let k = identifier.end;
|
|
1482
|
+
const name = identifier.value;
|
|
1483
|
+
k = skipTrivia(source, k, n);
|
|
1484
|
+
const next = source[k];
|
|
1485
|
+
if (next === ',' || next === '=') {
|
|
1486
|
+
return true;
|
|
1487
|
+
}
|
|
1488
|
+
if (next === '>') {
|
|
1489
|
+
const j = skipTrivia(source, k + 1, n);
|
|
1490
|
+
if (source[j] === '(') {
|
|
1491
|
+
return arrowFollowsParameters(source, j, n);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
let end = k;
|
|
1495
|
+
while (end < n && isIdentifierPartAt(source, end)) {
|
|
1496
|
+
end++;
|
|
1497
|
+
}
|
|
1498
|
+
if (source.slice(k, end) === 'extends') {
|
|
1499
|
+
// `extends` is a generic constraint only when it introduces a constraint type that closes
|
|
1500
|
+
// the type-parameter list. `implements` is not a TypeScript type-parameter constraint and
|
|
1501
|
+
// remains a JSX boolean attribute.
|
|
1502
|
+
const j = skipTrivia(source, end, n);
|
|
1503
|
+
const after = source[j];
|
|
1504
|
+
if (after !== '=' &&
|
|
1505
|
+
after !== '"' &&
|
|
1506
|
+
after !== "'" &&
|
|
1507
|
+
after !== '`' &&
|
|
1508
|
+
after !== '>' &&
|
|
1509
|
+
after !== '/') {
|
|
1510
|
+
return true;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
// `const T, ...`: a TypeScript const type parameter, whose real name follows the modifier.
|
|
1514
|
+
if (name === 'const' && k < n && isIdentifierStartAt(source, k)) {
|
|
1515
|
+
return jsxTagIsGeneric(source, k, n);
|
|
1516
|
+
}
|
|
1517
|
+
return false;
|
|
1518
|
+
}
|
|
1519
|
+
catch (error) {
|
|
1520
|
+
if (error instanceof ModuleSpecifierError) {
|
|
1521
|
+
// This is speculative: an apostrophe or `/*` may be inert JSX children, not broken JS.
|
|
1522
|
+
// Let the normal JSX path scan it; genuinely incomplete markup will still fail there.
|
|
1523
|
+
return false;
|
|
1524
|
+
}
|
|
1525
|
+
throw error;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
// Scan a generic function signature's parameter list starting at its opening `(` (offset `open`).
|
|
1529
|
+
// Returns true only when the list is closed and `=>` follows, so callers can treat `<T>(...) =>` as
|
|
1530
|
+
// a generic signature rather than a JSX element.
|
|
1531
|
+
//
|
|
1532
|
+
// Brackets inside strings, template literals and regex literals are inert: a `)` inside any of them
|
|
1533
|
+
// must not be counted as the parameter-list close. Template interpolations are skipped opaquely
|
|
1534
|
+
// because a template literal is a single argument value, so its internal parentheses never shape
|
|
1535
|
+
// the parameter list. Regex-vs-division tracking mirrors the main tokenizer so a regex default
|
|
1536
|
+
// (for example `= /a/)`) is skipped instead of read as division.
|
|
1537
|
+
function arrowFollowsParameters(source, open, n, jsx = false, needsConditionalEnd = false, mode = 'parameters') {
|
|
1538
|
+
let depth = 0;
|
|
1539
|
+
let bracketDepth = 0;
|
|
1540
|
+
let braceDepth = 0;
|
|
1541
|
+
let conditionalDepth = 0;
|
|
1542
|
+
let assertionOwner;
|
|
1543
|
+
let prevKind = 'start';
|
|
1544
|
+
let prevPunct;
|
|
1545
|
+
let k = open;
|
|
1546
|
+
const jsxFrames = [];
|
|
1547
|
+
const typeArgumentBounds = new Map();
|
|
1548
|
+
while (k < n) {
|
|
1549
|
+
let ch = source[k];
|
|
1550
|
+
const frame = jsxFrames[jsxFrames.length - 1];
|
|
1551
|
+
if (frame?.kind === 'jsx-text') {
|
|
1552
|
+
if (ch === '{') {
|
|
1553
|
+
jsxFrames.push({ kind: 'jsx-expr', depth: 0, offset: k });
|
|
1554
|
+
prevKind = 'start';
|
|
1555
|
+
prevPunct = undefined;
|
|
1556
|
+
k++;
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
if (ch === '<' && source[k + 1] === '/') {
|
|
1560
|
+
const end = source.indexOf('>', k + 2);
|
|
1561
|
+
if (end < 0) {
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
jsxFrames.pop();
|
|
1565
|
+
prevKind = 'template';
|
|
1566
|
+
prevPunct = undefined;
|
|
1567
|
+
k = end + 1;
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
// Quotes, parentheses, backticks and comment markers in children are inert.
|
|
1571
|
+
if (ch !== '<') {
|
|
1572
|
+
k++;
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
else if (frame?.kind === 'jsx-tag') {
|
|
1577
|
+
if (ch === '<' && skipTrivia(source, frame.nameEnd, n) === k) {
|
|
1578
|
+
k = readJsxTypeArguments(source, k, n, []);
|
|
1579
|
+
}
|
|
1580
|
+
else if (ch === '{') {
|
|
1581
|
+
jsxFrames.push({ kind: 'jsx-expr', depth: 0, offset: k });
|
|
1582
|
+
prevKind = 'start';
|
|
1583
|
+
prevPunct = undefined;
|
|
1584
|
+
k++;
|
|
1585
|
+
}
|
|
1586
|
+
else if (ch === '"' || ch === "'") {
|
|
1587
|
+
k = skipJsxAttributeValue(source, k, n);
|
|
1588
|
+
}
|
|
1589
|
+
else if (ch === '/' && source[k + 1] === '>') {
|
|
1590
|
+
jsxFrames.pop();
|
|
1591
|
+
prevKind = 'template';
|
|
1592
|
+
prevPunct = undefined;
|
|
1593
|
+
k += 2;
|
|
1594
|
+
}
|
|
1595
|
+
else if (ch === '>') {
|
|
1596
|
+
jsxFrames.pop();
|
|
1597
|
+
jsxFrames.push({ kind: 'jsx-text', name: frame.name, offset: frame.offset });
|
|
1598
|
+
k++;
|
|
1599
|
+
}
|
|
1600
|
+
else {
|
|
1601
|
+
k++;
|
|
1602
|
+
}
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
k = skipTrivia(source, k, n);
|
|
1607
|
+
ch = source[k];
|
|
1608
|
+
}
|
|
1609
|
+
if (ch === undefined) {
|
|
1610
|
+
break;
|
|
1611
|
+
}
|
|
1612
|
+
if (jsx &&
|
|
1613
|
+
ch === '<' &&
|
|
1614
|
+
(frame?.kind === 'jsx-text' || isRegexStart(prevKind, prevPunct, false, false, false))) {
|
|
1615
|
+
if (source[k + 1] === '>') {
|
|
1616
|
+
jsxFrames.push({ kind: 'jsx-text', name: null, offset: k });
|
|
1617
|
+
k += 2;
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
if (isIdentifierStartAt(source, k + 1) &&
|
|
1621
|
+
(frame?.kind === 'jsx-text' || !jsxTagIsGeneric(source, k + 1, n))) {
|
|
1622
|
+
const tag = readJsxTagName(source, k + 1, n);
|
|
1623
|
+
jsxFrames.push({ kind: 'jsx-tag', name: tag.name, offset: k, nameEnd: tag.end });
|
|
1624
|
+
k = tag.end;
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
if (frame?.kind === 'jsx-text') {
|
|
1629
|
+
k++;
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
if (frame?.kind === 'jsx-expr') {
|
|
1633
|
+
if (ch === '}') {
|
|
1634
|
+
if (frame.depth === 0) {
|
|
1635
|
+
jsxFrames.pop();
|
|
1636
|
+
prevKind = 'template';
|
|
1637
|
+
prevPunct = undefined;
|
|
1638
|
+
k++;
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
frame.depth--;
|
|
1642
|
+
}
|
|
1643
|
+
else if (ch === '{') {
|
|
1644
|
+
frame.depth++;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
if (mode === 'conditional-body' && ch === '<' && prevKind === 'identifier') {
|
|
1648
|
+
const inAssertion = assertionOwner?.paren === depth &&
|
|
1649
|
+
assertionOwner.bracket === bracketDepth &&
|
|
1650
|
+
assertionOwner.brace === braceDepth &&
|
|
1651
|
+
assertionOwner.frame === jsxFrames.length;
|
|
1652
|
+
const end = expressionTypeArgumentEnd(source, k, n, typeArgumentBounds, inAssertion);
|
|
1653
|
+
if (end !== undefined) {
|
|
1654
|
+
k = end;
|
|
1655
|
+
prevKind = 'identifier';
|
|
1656
|
+
prevPunct = undefined;
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (assertionOwner?.paren === depth &&
|
|
1661
|
+
assertionOwner.bracket === bracketDepth &&
|
|
1662
|
+
assertionOwner.brace === braceDepth &&
|
|
1663
|
+
assertionOwner.frame === jsxFrames.length &&
|
|
1664
|
+
['+', '-', '*', '/', '%', '=', '?', '!'].includes(ch)) {
|
|
1665
|
+
assertionOwner = undefined;
|
|
1666
|
+
}
|
|
1667
|
+
// Reuse the same opaque regions to confirm a typed nested arrow in a ternary.
|
|
1668
|
+
// Colons in JSX, object literals, nested delimiters or inner conditionals do not
|
|
1669
|
+
// close the enclosing conditional. Stop at this expression's own boundary.
|
|
1670
|
+
if (mode === 'conditional-body') {
|
|
1671
|
+
if (jsxFrames.length === 0 && depth === 0 && bracketDepth === 0 && braceDepth === 0) {
|
|
1672
|
+
if (ch === ':') {
|
|
1673
|
+
if (conditionalDepth === 0) {
|
|
1674
|
+
return true;
|
|
1675
|
+
}
|
|
1676
|
+
conditionalDepth--;
|
|
1677
|
+
}
|
|
1678
|
+
else if (ch === '?' &&
|
|
1679
|
+
source[k + 1] !== '?' &&
|
|
1680
|
+
source[k - 1] !== '?' &&
|
|
1681
|
+
source[k + 1] !== '.') {
|
|
1682
|
+
conditionalDepth++;
|
|
1683
|
+
}
|
|
1684
|
+
else if ([';', ',', ')', ']', '}'].includes(ch)) {
|
|
1685
|
+
return false;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
if (ch === '[')
|
|
1689
|
+
bracketDepth++;
|
|
1690
|
+
else if (ch === ']')
|
|
1691
|
+
bracketDepth--;
|
|
1692
|
+
else if (ch === '{')
|
|
1693
|
+
braceDepth++;
|
|
1694
|
+
else if (ch === '}')
|
|
1695
|
+
braceDepth--;
|
|
1696
|
+
}
|
|
1697
|
+
// String literals: their brackets and slashes are inert text, so skip the whole value.
|
|
1698
|
+
if (ch === "'" || ch === '"') {
|
|
1699
|
+
k = readString(source, k).next;
|
|
1700
|
+
prevKind = 'string';
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
// Template literals: skip their text and interpolation so a `)` inside a template is not
|
|
1704
|
+
// counted as the parameter-list close.
|
|
1705
|
+
if (ch === '`') {
|
|
1706
|
+
k = skipTemplateLiteral(source, k);
|
|
1707
|
+
prevKind = 'template';
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
// Regex literals: a `/` that begins a regex must be skipped so its `)` stays inside the pattern
|
|
1711
|
+
// rather than closing the parameter list.
|
|
1712
|
+
if (ch === '/' && isRegexStart(prevKind, prevPunct, false, false, false)) {
|
|
1713
|
+
k = skipRegex(source, k);
|
|
1714
|
+
prevKind = 'regex';
|
|
1715
|
+
continue;
|
|
1716
|
+
}
|
|
1717
|
+
if (ch === '(') {
|
|
1718
|
+
depth++;
|
|
1719
|
+
prevKind = 'punct';
|
|
1720
|
+
prevPunct = '(';
|
|
1721
|
+
}
|
|
1722
|
+
else if (ch === ')') {
|
|
1723
|
+
depth--;
|
|
1724
|
+
prevKind = 'punct';
|
|
1725
|
+
prevPunct = ')';
|
|
1726
|
+
if (depth === 0 && mode === 'parameters') {
|
|
1727
|
+
const j = skipTrivia(source, k + 1, n);
|
|
1728
|
+
if (source[j] === '=' && source[j + 1] === '>') {
|
|
1729
|
+
return true;
|
|
1730
|
+
}
|
|
1731
|
+
const arrow = source[j] === ':' ? arrowAfterReturnType(source, j + 1, n) : undefined;
|
|
1732
|
+
return (arrow !== undefined &&
|
|
1733
|
+
(!needsConditionalEnd ||
|
|
1734
|
+
arrowFollowsParameters(source, arrow + 2, n, jsx, false, 'conditional-body')));
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
else if (isIdentifierStartAt(source, k)) {
|
|
1738
|
+
const identifier = readIdentifier(source, k);
|
|
1739
|
+
if (mode === 'conditional-body' &&
|
|
1740
|
+
['as', 'satisfies'].includes(identifier.value) &&
|
|
1741
|
+
prevKind !== 'start' &&
|
|
1742
|
+
!(prevKind === 'punct' && prevPunct === '.')) {
|
|
1743
|
+
assertionOwner = {
|
|
1744
|
+
paren: depth,
|
|
1745
|
+
bracket: bracketDepth,
|
|
1746
|
+
brace: braceDepth,
|
|
1747
|
+
frame: jsxFrames.length,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
k = identifier.end - 1;
|
|
1751
|
+
prevKind = 'identifier';
|
|
1752
|
+
prevPunct = undefined;
|
|
1753
|
+
}
|
|
1754
|
+
else {
|
|
1755
|
+
prevKind = 'punct';
|
|
1756
|
+
prevPunct = ch;
|
|
1757
|
+
}
|
|
1758
|
+
k++;
|
|
1759
|
+
}
|
|
1760
|
+
return false;
|
|
1761
|
+
}
|
|
1762
|
+
/** Skip a balanced type-argument list only when its suffix can continue an expression. */
|
|
1763
|
+
function expressionTypeArgumentEnd(source, open, n, bounds, typeAssertion = false) {
|
|
1764
|
+
let end = bounds.get(open);
|
|
1765
|
+
if (end === undefined) {
|
|
1766
|
+
try {
|
|
1767
|
+
end = readJsxTypeArguments(source, open, n, [], true, bounds);
|
|
1768
|
+
}
|
|
1769
|
+
catch (error) {
|
|
1770
|
+
if (error instanceof ModuleSpecifierError) {
|
|
1771
|
+
bounds.set(open, null);
|
|
1772
|
+
return undefined;
|
|
1773
|
+
}
|
|
1774
|
+
throw error;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
if (end === null)
|
|
1778
|
+
return undefined;
|
|
1779
|
+
const next = skipTrivia(source, end, n);
|
|
1780
|
+
const ch = source[next];
|
|
1781
|
+
if (!typeAssertion)
|
|
1782
|
+
return ch === '(' ? end : undefined;
|
|
1783
|
+
if (isIdentifierStartAt(source, next) || isUnicodeEscapeStart(source, next)) {
|
|
1784
|
+
return ['as', 'satisfies', 'in', 'instanceof'].includes(readIdentifier(source, next).value)
|
|
1785
|
+
? end
|
|
1786
|
+
: undefined;
|
|
1787
|
+
}
|
|
1788
|
+
return ch !== undefined && (isNumberStart(ch) || ch === '"' || ch === "'") ? undefined : end;
|
|
1789
|
+
}
|
|
1790
|
+
/** A newline computed method head ends a complete bodyless member return annotation. */
|
|
1791
|
+
function computedMethodFollows(source, start, n) {
|
|
1792
|
+
let depth = 0;
|
|
1793
|
+
let index = start;
|
|
1794
|
+
let previous = 'start';
|
|
1795
|
+
let punct;
|
|
1796
|
+
while (index < n) {
|
|
1797
|
+
index = skipTrivia(source, index, n);
|
|
1798
|
+
const ch = source[index];
|
|
1799
|
+
if (ch === undefined) {
|
|
1800
|
+
break;
|
|
1801
|
+
}
|
|
1802
|
+
if (ch === "'" || ch === '"') {
|
|
1803
|
+
index = readString(source, index).next;
|
|
1804
|
+
previous = 'string';
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
if (ch === '`') {
|
|
1808
|
+
index = skipTemplateLiteral(source, index);
|
|
1809
|
+
previous = 'template';
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
if (ch === '/' && isRegexStart(previous, punct, false, false, false)) {
|
|
1813
|
+
index = skipRegex(source, index);
|
|
1814
|
+
previous = 'regex';
|
|
1815
|
+
continue;
|
|
1816
|
+
}
|
|
1817
|
+
if (ch === '[') {
|
|
1818
|
+
depth++;
|
|
1819
|
+
}
|
|
1820
|
+
else if (ch === ']') {
|
|
1821
|
+
depth--;
|
|
1822
|
+
if (depth === 0) {
|
|
1823
|
+
return source[skipTrivia(source, index + 1, n)] === '(';
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
if (isIdentifierStartAt(source, index) || isUnicodeEscapeStart(source, index)) {
|
|
1827
|
+
index = readIdentifier(source, index).end;
|
|
1828
|
+
previous = 'identifier';
|
|
1829
|
+
}
|
|
1830
|
+
else {
|
|
1831
|
+
previous = isNumberStart(ch) ? 'number' : 'punct';
|
|
1832
|
+
punct = ch;
|
|
1833
|
+
index++;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return false;
|
|
1837
|
+
}
|
|
1838
|
+
function arrowAfterReturnType(source, start, n) {
|
|
1839
|
+
let parenDepth = 0;
|
|
1840
|
+
let bracketDepth = 0;
|
|
1841
|
+
let braceDepth = 0;
|
|
1842
|
+
let angleDepth = 0;
|
|
1843
|
+
let index = start;
|
|
1844
|
+
while (index < n) {
|
|
1845
|
+
index = skipTrivia(source, index, n);
|
|
1846
|
+
const ch = source[index];
|
|
1847
|
+
if (ch === undefined) {
|
|
1848
|
+
return undefined;
|
|
1849
|
+
}
|
|
1850
|
+
if (ch === "'" || ch === '"') {
|
|
1851
|
+
index = readString(source, index).next;
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
if (ch === '`') {
|
|
1855
|
+
index = skipTemplateLiteral(source, index);
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
if (ch === '=' && source[index + 1] === '>') {
|
|
1859
|
+
if (parenDepth === 0 && bracketDepth === 0 && braceDepth === 0 && angleDepth === 0) {
|
|
1860
|
+
return index;
|
|
1861
|
+
}
|
|
1862
|
+
index += 2;
|
|
1863
|
+
continue;
|
|
1864
|
+
}
|
|
1865
|
+
if (parenDepth === 0 &&
|
|
1866
|
+
bracketDepth === 0 &&
|
|
1867
|
+
braceDepth === 0 &&
|
|
1868
|
+
angleDepth === 0 &&
|
|
1869
|
+
(isIdentifierStartAt(source, index) || isUnicodeEscapeStart(source, index))) {
|
|
1870
|
+
const identifier = readIdentifier(source, index);
|
|
1871
|
+
const next = skipTrivia(source, identifier.end, n);
|
|
1872
|
+
// An operand followed by a call is runtime code (not a return type). In particular,
|
|
1873
|
+
// a conditional's `: async () =>` must not claim the preceding sequence expression.
|
|
1874
|
+
if (source[next] === '(' &&
|
|
1875
|
+
identifier.value !== 'import' &&
|
|
1876
|
+
!TYPE_OPERAND_KEYWORDS.has(identifier.value)) {
|
|
1877
|
+
return undefined;
|
|
1878
|
+
}
|
|
1879
|
+
index = identifier.end;
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1882
|
+
if (ch === '(') {
|
|
1883
|
+
parenDepth++;
|
|
1884
|
+
}
|
|
1885
|
+
else if (ch === ')') {
|
|
1886
|
+
if (parenDepth === 0) {
|
|
1887
|
+
return undefined;
|
|
1888
|
+
}
|
|
1889
|
+
parenDepth--;
|
|
1890
|
+
}
|
|
1891
|
+
else if (ch === '[') {
|
|
1892
|
+
bracketDepth++;
|
|
1893
|
+
}
|
|
1894
|
+
else if (ch === ']') {
|
|
1895
|
+
if (bracketDepth === 0) {
|
|
1896
|
+
return undefined;
|
|
1897
|
+
}
|
|
1898
|
+
bracketDepth--;
|
|
1899
|
+
}
|
|
1900
|
+
else if (ch === '{') {
|
|
1901
|
+
braceDepth++;
|
|
1902
|
+
}
|
|
1903
|
+
else if (ch === '}') {
|
|
1904
|
+
if (braceDepth === 0) {
|
|
1905
|
+
return undefined;
|
|
1906
|
+
}
|
|
1907
|
+
braceDepth--;
|
|
1908
|
+
}
|
|
1909
|
+
else if (ch === '<') {
|
|
1910
|
+
angleDepth++;
|
|
1911
|
+
}
|
|
1912
|
+
else if (ch === '>') {
|
|
1913
|
+
if (angleDepth === 0) {
|
|
1914
|
+
return undefined;
|
|
1915
|
+
}
|
|
1916
|
+
angleDepth--;
|
|
1917
|
+
}
|
|
1918
|
+
else if (parenDepth === 0 &&
|
|
1919
|
+
bracketDepth === 0 &&
|
|
1920
|
+
braceDepth === 0 &&
|
|
1921
|
+
angleDepth === 0 &&
|
|
1922
|
+
(ch === ';' || ch === ',')) {
|
|
1923
|
+
return undefined;
|
|
1924
|
+
}
|
|
1925
|
+
index++;
|
|
1926
|
+
}
|
|
1927
|
+
return undefined;
|
|
1928
|
+
}
|
|
1929
|
+
function arrowFollowsParametersSafely(source, open, n, jsx = false, needsConditionalEnd = false) {
|
|
1930
|
+
try {
|
|
1931
|
+
return arrowFollowsParameters(source, open, n, jsx, needsConditionalEnd);
|
|
1932
|
+
}
|
|
1933
|
+
catch (error) {
|
|
1934
|
+
if (error instanceof ModuleSpecifierError) {
|
|
1935
|
+
return false;
|
|
1936
|
+
}
|
|
1937
|
+
throw error;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
// Skip a template literal starting at its opening backtick (offset `start`). Returns the index just
|
|
1941
|
+
// past the closing backtick.
|
|
1942
|
+
//
|
|
1943
|
+
// `${...}` interpolations are skipped opaquely by brace depth: the caller only needs this region to
|
|
1944
|
+
// be invisible so its brackets never reach the parameter-list accounting, which is why the body is
|
|
1945
|
+
// not re-lexed here.
|
|
1946
|
+
//
|
|
1947
|
+
// @throws {ModuleSpecifierError} on an unterminated template literal.
|
|
1948
|
+
function skipTemplateLiteral(source, start) {
|
|
1949
|
+
const n = source.length;
|
|
1950
|
+
let i = start + 1; // past the opening backtick
|
|
1951
|
+
while (i < n) {
|
|
1952
|
+
const ch = source[i];
|
|
1953
|
+
// A backslash escapes the next character (including a nested backtick or `${`).
|
|
1954
|
+
if (ch === '\\') {
|
|
1955
|
+
i += 2;
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
if (ch === '`') {
|
|
1959
|
+
return i + 1;
|
|
1960
|
+
}
|
|
1961
|
+
if (ch === '$' && source[i + 1] === '{') {
|
|
1962
|
+
// Skip the interpolation until its braces balance. Nested templates/braces are handled by the
|
|
1963
|
+
// depth counter; a raw line terminator is invalid inside a template and stops the scan.
|
|
1964
|
+
let depth = 1;
|
|
1965
|
+
i += 2;
|
|
1966
|
+
while (i < n && depth > 0) {
|
|
1967
|
+
const inner = source[i];
|
|
1968
|
+
if (inner === '\n' || inner === '\r') {
|
|
1969
|
+
throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', start);
|
|
1970
|
+
}
|
|
1971
|
+
if (inner === '\\') {
|
|
1972
|
+
i += 2;
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
if (inner === '{') {
|
|
1976
|
+
depth++;
|
|
1977
|
+
}
|
|
1978
|
+
else if (inner === '}') {
|
|
1979
|
+
depth--;
|
|
1980
|
+
}
|
|
1981
|
+
i++;
|
|
1982
|
+
}
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
i++;
|
|
1986
|
+
}
|
|
1987
|
+
throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', start);
|
|
1988
|
+
}
|
|
1989
|
+
/** Probe template types with the same type boundaries as their interpolated operands. */
|
|
1990
|
+
function skipTemplateType(source, start, n, bounds) {
|
|
1991
|
+
let i = start + 1;
|
|
1992
|
+
while (i < n) {
|
|
1993
|
+
if (source[i] === '\\')
|
|
1994
|
+
i += 2;
|
|
1995
|
+
else if (source[i] === '`')
|
|
1996
|
+
return i + 1;
|
|
1997
|
+
else if (source[i] === '$' && source[i + 1] === '{') {
|
|
1998
|
+
i = readJsxTypeArguments(source, i + 1, n, [], true, bounds, '}');
|
|
1999
|
+
}
|
|
2000
|
+
else
|
|
2001
|
+
i++;
|
|
2002
|
+
}
|
|
2003
|
+
throw new ModuleSpecifierError('Unterminated template literal', 'invalid-source', start);
|
|
2004
|
+
}
|
|
2005
|
+
/** Only a complete type followed by a line break can begin another bodyless type member. */
|
|
2006
|
+
function typeMemberFollowsLineBreak(tokens, lineBreak) {
|
|
2007
|
+
const complete = (token) => {
|
|
2008
|
+
if (token === undefined)
|
|
2009
|
+
return false;
|
|
2010
|
+
if (token.kind === 'identifier')
|
|
2011
|
+
return !TYPE_OPERAND_KEYWORDS.has(token.value);
|
|
2012
|
+
return (token.kind === 'string' ||
|
|
2013
|
+
token.kind === 'number' ||
|
|
2014
|
+
token.kind === 'template' ||
|
|
2015
|
+
(token.kind === 'punct' &&
|
|
2016
|
+
([')', ']', '}', '>'].includes(token.value) || /^[0-9]$/.test(token.value))));
|
|
2017
|
+
};
|
|
2018
|
+
let index = tokens.length - 1;
|
|
2019
|
+
if (lineBreak && complete(tokens[index]))
|
|
2020
|
+
return true;
|
|
2021
|
+
// The line break may precede a modifier rather than the method name (e.g. get b()).
|
|
2022
|
+
while (index >= 0) {
|
|
2023
|
+
const token = tokens[index];
|
|
2024
|
+
if (token?.kind !== 'identifier' || !MEMBER_MODIFIERS.has(token.value))
|
|
2025
|
+
break;
|
|
2026
|
+
if (token.lineBreakBefore === true && complete(tokens[index - 1]))
|
|
2027
|
+
return true;
|
|
2028
|
+
index--;
|
|
2029
|
+
}
|
|
2030
|
+
return false;
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Lex only the boundaries of a component's type arguments. These tokens preserve literal imports
|
|
2034
|
+
* without changing the surrounding JavaScript function/brace state or treating type braces as JSX.
|
|
2035
|
+
*/
|
|
2036
|
+
function readJsxTypeArguments(source, open, n, tokens, expressionProbe = false, bounds, rootClose = '>') {
|
|
2037
|
+
const closers = [rootClose];
|
|
2038
|
+
const opens = [open];
|
|
2039
|
+
const conditions = [0];
|
|
2040
|
+
let i = open + 1;
|
|
2041
|
+
try {
|
|
2042
|
+
while (i < n) {
|
|
2043
|
+
const trivia = readTrivia(source, i, n);
|
|
2044
|
+
i = trivia.end;
|
|
2045
|
+
const ch = source[i];
|
|
2046
|
+
if (ch === undefined) {
|
|
2047
|
+
break;
|
|
2048
|
+
}
|
|
2049
|
+
// A speculative expression probe must not borrow a > from a later statement
|
|
2050
|
+
// or conditional branch. Colons in conditional types still belong to the list.
|
|
2051
|
+
if (expressionProbe &&
|
|
2052
|
+
(closers[closers.length - 1] === '>' || (rootClose === '}' && closers.length === 1))) {
|
|
2053
|
+
const level = conditions.length - 1;
|
|
2054
|
+
if (ch === '?')
|
|
2055
|
+
conditions[level] = (conditions[level] ?? 0) + 1;
|
|
2056
|
+
else if (ch === ':' && (conditions[level] ?? 0) > 0)
|
|
2057
|
+
conditions[level] = (conditions[level] ?? 0) - 1;
|
|
2058
|
+
else if (ch === ';' ||
|
|
2059
|
+
ch === ':' ||
|
|
2060
|
+
(ch === '=' && source[i + 1] !== '>') ||
|
|
2061
|
+
([')', ']', '}'].includes(ch) && ch !== closers[closers.length - 1])) {
|
|
2062
|
+
throw new ModuleSpecifierError('Invalid type argument boundary', 'invalid-source', i);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
if (expressionProbe && ch === '/') {
|
|
2066
|
+
throw new ModuleSpecifierError('Runtime slash in type argument probe', 'invalid-source', i);
|
|
2067
|
+
}
|
|
2068
|
+
if (ch === "'" || ch === '"') {
|
|
2069
|
+
const callee = tokens[tokens.length - 2];
|
|
2070
|
+
if (expressionProbe &&
|
|
2071
|
+
isPunct(tokens[tokens.length - 1], '(') &&
|
|
2072
|
+
callee?.kind === 'identifier' &&
|
|
2073
|
+
callee.value !== 'import' &&
|
|
2074
|
+
!TYPE_OPERAND_KEYWORDS.has(callee.value)) {
|
|
2075
|
+
throw new ModuleSpecifierError('Literal method parameter in type argument probe', 'invalid-source', i);
|
|
2076
|
+
}
|
|
2077
|
+
const { value, next } = readString(source, i);
|
|
2078
|
+
tokens.push({
|
|
2079
|
+
kind: 'string',
|
|
2080
|
+
value,
|
|
2081
|
+
offset: i,
|
|
2082
|
+
endOffset: next,
|
|
2083
|
+
lineBreakBefore: trivia.lineBreak,
|
|
2084
|
+
});
|
|
2085
|
+
i = next;
|
|
2086
|
+
continue;
|
|
2087
|
+
}
|
|
2088
|
+
if (ch === '`') {
|
|
2089
|
+
if (expressionProbe) {
|
|
2090
|
+
const end = skipTemplateType(source, i, n, bounds);
|
|
2091
|
+
tokens.push({
|
|
2092
|
+
kind: 'template',
|
|
2093
|
+
offset: i,
|
|
2094
|
+
endOffset: end,
|
|
2095
|
+
lineBreakBefore: trivia.lineBreak,
|
|
2096
|
+
});
|
|
2097
|
+
i = end;
|
|
2098
|
+
continue;
|
|
2099
|
+
}
|
|
2100
|
+
throw new ModuleSpecifierError('Template literal types in JSX type arguments are not supported', 'invalid-source', i);
|
|
2101
|
+
}
|
|
2102
|
+
if (isIdentifierStartAt(source, i)) {
|
|
2103
|
+
const start = i;
|
|
2104
|
+
const identifier = readIdentifier(source, i);
|
|
2105
|
+
const next = skipTrivia(source, identifier.end, n);
|
|
2106
|
+
if (expressionProbe &&
|
|
2107
|
+
source[next] === '(' &&
|
|
2108
|
+
identifier.value !== 'import' &&
|
|
2109
|
+
!TYPE_OPERAND_KEYWORDS.has(identifier.value) &&
|
|
2110
|
+
!(closers[closers.length - 1] === '}' &&
|
|
2111
|
+
closers.length > 1 &&
|
|
2112
|
+
(memberDeclarationHead(tokens, tokens.length, false) !== undefined ||
|
|
2113
|
+
typeMemberFollowsLineBreak(tokens, trivia.lineBreak)))) {
|
|
2114
|
+
throw new ModuleSpecifierError('Runtime call in type argument probe', 'invalid-source', i);
|
|
2115
|
+
}
|
|
2116
|
+
i = identifier.end;
|
|
2117
|
+
tokens.push({
|
|
2118
|
+
kind: 'identifier',
|
|
2119
|
+
value: identifier.value,
|
|
2120
|
+
offset: start,
|
|
2121
|
+
endOffset: i,
|
|
2122
|
+
lineBreakBefore: trivia.lineBreak,
|
|
2123
|
+
});
|
|
2124
|
+
continue;
|
|
2125
|
+
}
|
|
2126
|
+
if (expressionProbe && ch === '{' && isPunct(tokens[tokens.length - 1], ')')) {
|
|
2127
|
+
throw new ModuleSpecifierError('Runtime body in type argument probe', 'invalid-source', i);
|
|
2128
|
+
}
|
|
2129
|
+
if (ch === '<' || ch === '(' || ch === '{' || ch === '[') {
|
|
2130
|
+
closers.push(ch === '<' ? '>' : ch === '(' ? ')' : ch === '{' ? '}' : ']');
|
|
2131
|
+
opens.push(i);
|
|
2132
|
+
conditions.push(0);
|
|
2133
|
+
}
|
|
2134
|
+
else if (ch === closers[closers.length - 1] && !(ch === '>' && source[i - 1] === '=')) {
|
|
2135
|
+
closers.pop();
|
|
2136
|
+
const opened = opens.pop();
|
|
2137
|
+
conditions.pop();
|
|
2138
|
+
if (expressionProbe && ch === '>' && opened !== undefined)
|
|
2139
|
+
bounds?.set(opened, i + 1);
|
|
2140
|
+
if (closers.length === 0) {
|
|
2141
|
+
return i + 1;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
tokens.push({
|
|
2145
|
+
kind: 'punct',
|
|
2146
|
+
value: ch,
|
|
2147
|
+
offset: i,
|
|
2148
|
+
endOffset: i + 1,
|
|
2149
|
+
lineBreakBefore: trivia.lineBreak,
|
|
2150
|
+
...(ch === '=' ? classifyEquals(source, i, true) : {}),
|
|
2151
|
+
});
|
|
2152
|
+
i++;
|
|
2153
|
+
}
|
|
2154
|
+
throw new ModuleSpecifierError('Unterminated JSX type arguments', 'invalid-source', open);
|
|
2155
|
+
}
|
|
2156
|
+
catch (error) {
|
|
2157
|
+
if (expressionProbe && bounds !== undefined) {
|
|
2158
|
+
// One failed walk classifies every still-open angle encountered along the way.
|
|
2159
|
+
for (let level = 0; level < closers.length; level++) {
|
|
2160
|
+
const opened = opens[level];
|
|
2161
|
+
if (closers[level] === '>' && opened !== undefined)
|
|
2162
|
+
bounds.set(opened, null);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
throw error;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Whether the source between two offsets contains a line terminator.
|
|
2170
|
+
*/
|
|
2171
|
+
function hasLineTerminator(source, from, to) {
|
|
2172
|
+
for (let k = from; k < to; k++) {
|
|
2173
|
+
if (isLineTerminator(source[k])) {
|
|
2174
|
+
return true;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
return false;
|
|
2178
|
+
}
|
|
2179
|
+
/** Skip whitespace and comments without interpreting any target-project code. */
|
|
2180
|
+
function skipTrivia(source, start, n) {
|
|
2181
|
+
return readTrivia(source, start, n).end;
|
|
2182
|
+
}
|
|
2183
|
+
function readTrivia(source, start, n) {
|
|
2184
|
+
let i = start;
|
|
2185
|
+
let lineBreak = false;
|
|
2186
|
+
while (i < n) {
|
|
2187
|
+
if (isWhitespace(source[i])) {
|
|
2188
|
+
lineBreak ||= isLineTerminator(source[i]);
|
|
2189
|
+
i++;
|
|
2190
|
+
continue;
|
|
2191
|
+
}
|
|
2192
|
+
if (source[i] === '/' && source[i + 1] === '/') {
|
|
2193
|
+
i += 2;
|
|
2194
|
+
while (i < n && !isLineTerminator(source[i])) {
|
|
2195
|
+
i++;
|
|
2196
|
+
}
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
if (source[i] === '/' && source[i + 1] === '*') {
|
|
2200
|
+
const start = i;
|
|
2201
|
+
i = skipBlockComment(source, i);
|
|
2202
|
+
lineBreak ||= hasLineTerminator(source, start, i);
|
|
2203
|
+
continue;
|
|
2204
|
+
}
|
|
2205
|
+
break;
|
|
2206
|
+
}
|
|
2207
|
+
return { end: i, lineBreak };
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Type keywords that still expect a type after them, so a `{` following one opens an object type and
|
|
2211
|
+
* not a function body (`T extends { ok: boolean } ? A : B`).
|
|
2212
|
+
*/
|
|
2213
|
+
const TYPE_OPERAND_KEYWORDS = new Set([
|
|
2214
|
+
'abstract',
|
|
2215
|
+
'as',
|
|
2216
|
+
'asserts',
|
|
2217
|
+
'await',
|
|
2218
|
+
'const',
|
|
2219
|
+
'extends',
|
|
2220
|
+
'in',
|
|
2221
|
+
'infer',
|
|
2222
|
+
'is',
|
|
2223
|
+
'keyof',
|
|
2224
|
+
'new',
|
|
2225
|
+
'readonly',
|
|
2226
|
+
'satisfies',
|
|
2227
|
+
'typeof',
|
|
2228
|
+
]);
|
|
2229
|
+
/**
|
|
2230
|
+
* Whether a root-level `=>` inside a return annotation is an arrow of the return type itself rather
|
|
2231
|
+
* than the signature's runtime arrow. A function type keeps a second arrow at the same nesting level
|
|
2232
|
+
* (`(): (x: number) => void => {}`); once the return type is over, the next significant token is the
|
|
2233
|
+
* body (`{`), a statement terminator or a delimiter that closes the surrounding code.
|
|
2234
|
+
*/
|
|
2235
|
+
function functionTypeArrowContinues(source, arrowEnd, n, conditionalEnd = false) {
|
|
2236
|
+
let depth = 0;
|
|
2237
|
+
for (let i = arrowEnd + 1; i < n; i++) {
|
|
2238
|
+
const ch = source[i];
|
|
2239
|
+
if (ch === '(' || ch === '[' || ch === '<') {
|
|
2240
|
+
depth++;
|
|
2241
|
+
}
|
|
2242
|
+
else if (ch === '>' && depth > 0) {
|
|
2243
|
+
depth--;
|
|
2244
|
+
}
|
|
2245
|
+
else if (ch === ')' || ch === ']') {
|
|
2246
|
+
if (depth === 0) {
|
|
2247
|
+
return false;
|
|
2248
|
+
}
|
|
2249
|
+
depth--;
|
|
2250
|
+
}
|
|
2251
|
+
else if (depth === 0) {
|
|
2252
|
+
if (ch === '{' || ch === ';' || ch === ',' || (conditionalEnd && ch === ':')) {
|
|
2253
|
+
return false;
|
|
2254
|
+
}
|
|
2255
|
+
if (ch === '=' && source[i + 1] === '>') {
|
|
2256
|
+
return true;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
return false;
|
|
2261
|
+
}
|
|
2262
|
+
/** Preserve type-argument identity while inspecting a parameter binding pattern. */
|
|
2263
|
+
function markParameterTypeArguments(tokens, openIndex) {
|
|
2264
|
+
if (!isPunct(tokens[openIndex - 1], '>')) {
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
const start = signatureNameIndex(tokens, openIndex) + 1;
|
|
2268
|
+
if (!isPunct(tokens[start], '<')) {
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
// A real JS destructuring entry between comparison operators needs a default or property
|
|
2272
|
+
// separator. Do not mistake that binding for a generic argument. Nested type members may
|
|
2273
|
+
// contain their own separators inside balanced (), [] and {}.
|
|
2274
|
+
let depth = 0;
|
|
2275
|
+
for (let index = start + 1; index < openIndex - 1; index++) {
|
|
2276
|
+
const token = tokens[index];
|
|
2277
|
+
if (token?.kind !== 'punct') {
|
|
2278
|
+
continue;
|
|
2279
|
+
}
|
|
2280
|
+
if (['(', '[', '{'].includes(token.value)) {
|
|
2281
|
+
depth++;
|
|
2282
|
+
}
|
|
2283
|
+
else if ([')', ']', '}'].includes(token.value)) {
|
|
2284
|
+
depth--;
|
|
2285
|
+
}
|
|
2286
|
+
else if (depth === 0 &&
|
|
2287
|
+
(token.operator === 'assignment' || token.value === ':' || token.value === ';')) {
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
for (let index = start; index < openIndex; index++) {
|
|
2292
|
+
const token = tokens[index];
|
|
2293
|
+
if (token !== undefined) {
|
|
2294
|
+
tokens[index] = { ...token, parameterType: true };
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return start;
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Index of the token that names a `(` parameter list. A generic signature keeps its type-parameter
|
|
2301
|
+
* list between the name and the `(` (`async function f<T>(): T {}`, `async <T,>(value: T) => {}`,
|
|
2302
|
+
* `async check<T>(): T {}`), so a balanced `<...>` directly before the `(` is skipped. The index of
|
|
2303
|
+
* the token directly before the `(` is returned unchanged when no such list precedes it.
|
|
2304
|
+
*/
|
|
2305
|
+
function signatureNameIndex(tokens, openIndex) {
|
|
2306
|
+
let index = openIndex - 1;
|
|
2307
|
+
const angle = tokens[index];
|
|
2308
|
+
if (angle?.kind !== 'punct' || angle.value !== '>') {
|
|
2309
|
+
return index;
|
|
2310
|
+
}
|
|
2311
|
+
const closers = [];
|
|
2312
|
+
while (index >= 0) {
|
|
2313
|
+
const token = tokens[index];
|
|
2314
|
+
if (token === undefined) {
|
|
2315
|
+
break;
|
|
2316
|
+
}
|
|
2317
|
+
if (token.kind !== 'punct') {
|
|
2318
|
+
index--;
|
|
2319
|
+
continue;
|
|
2320
|
+
}
|
|
2321
|
+
if (token.value === '>' && isArrowHead(tokens[index - 1], token.offset)) {
|
|
2322
|
+
index -= 2;
|
|
2323
|
+
continue;
|
|
2324
|
+
}
|
|
2325
|
+
if (['>', ')', ']', '}'].includes(token.value)) {
|
|
2326
|
+
closers.push(token.value);
|
|
2327
|
+
}
|
|
2328
|
+
else if (['<', '(', '[', '{'].includes(token.value)) {
|
|
2329
|
+
const expected = token.value === '<' ? '>' : token.value === '(' ? ')' : token.value === '[' ? ']' : '}';
|
|
2330
|
+
if (closers.pop() !== expected) {
|
|
2331
|
+
break;
|
|
2332
|
+
}
|
|
2333
|
+
if (closers.length === 0) {
|
|
2334
|
+
return index - 1;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
else if (token.value === ';' &&
|
|
2338
|
+
token.syntheticBoundary !== true &&
|
|
2339
|
+
closers[closers.length - 1] === '>') {
|
|
2340
|
+
break;
|
|
2341
|
+
}
|
|
2342
|
+
index--;
|
|
2343
|
+
}
|
|
2344
|
+
// The `>` does not close a balanced type-parameter list, so the token before `(` names the list.
|
|
2345
|
+
return openIndex - 1;
|
|
2346
|
+
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Modifiers that may sit between a member separator and a method name.
|
|
2349
|
+
*
|
|
2350
|
+
* `new`, `typeof`, `void` and `await` are absent on purpose: they are operators, so an identifier
|
|
2351
|
+
* after them belongs to an expression and never starts a member declaration.
|
|
2352
|
+
*/
|
|
2353
|
+
const MEMBER_MODIFIERS = new Set([
|
|
2354
|
+
'abstract',
|
|
2355
|
+
'async',
|
|
2356
|
+
'get',
|
|
2357
|
+
'override',
|
|
2358
|
+
'private',
|
|
2359
|
+
'protected',
|
|
2360
|
+
'public',
|
|
2361
|
+
'set',
|
|
2362
|
+
'static',
|
|
2363
|
+
]);
|
|
2364
|
+
/** Punctuation that can start a member declaration inside an object literal, class body or interface. */
|
|
2365
|
+
const MEMBER_SEPARATORS = new Set(['{', ',', ';']);
|
|
2366
|
+
/**
|
|
2367
|
+
* Index of the `@` that starts the decorator ending at `index`, or -1 when the tokens ending there
|
|
2368
|
+
* are not a decorator. Only the tail of a decorator is known from the member side, so the
|
|
2369
|
+
* LeftHandSideExpression of `@dec`, `@dec(1)`, `@a.b` and `@(dec)` is scanned backwards.
|
|
2370
|
+
*/
|
|
2371
|
+
function decoratorStartIndex(tokens, index) {
|
|
2372
|
+
let depth = 0;
|
|
2373
|
+
for (let k = index; k >= 0; k--) {
|
|
2374
|
+
const token = tokens[k];
|
|
2375
|
+
if (token === undefined) {
|
|
2376
|
+
return -1;
|
|
2377
|
+
}
|
|
2378
|
+
if (token.kind !== 'punct') {
|
|
2379
|
+
continue;
|
|
2380
|
+
}
|
|
2381
|
+
if (token.value === ')' || token.value === ']' || token.value === '}') {
|
|
2382
|
+
depth++;
|
|
2383
|
+
}
|
|
2384
|
+
else if (token.value === '(' || token.value === '[' || token.value === '{') {
|
|
2385
|
+
// An unmatched opener means the scan left the decorator expression.
|
|
2386
|
+
if (depth === 0) {
|
|
2387
|
+
return -1;
|
|
2388
|
+
}
|
|
2389
|
+
depth--;
|
|
2390
|
+
}
|
|
2391
|
+
else if (depth === 0) {
|
|
2392
|
+
if (token.value === '@') {
|
|
2393
|
+
return k;
|
|
2394
|
+
}
|
|
2395
|
+
// Only property access (`@a.b`, `@a?.b`) continues a decorator name.
|
|
2396
|
+
if (token.value !== '.' && token.value !== '?') {
|
|
2397
|
+
return -1;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
return -1;
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* The async/generator flags of the member declaration whose name is the token at `nameIndex`, or
|
|
2405
|
+
* `undefined` when no member declaration starts there.
|
|
2406
|
+
*
|
|
2407
|
+
* Class headers are bounded by the current member's forward-tracked start. Objects use explicit
|
|
2408
|
+
* separators, never class-field ASI. Only modifiers and decorators may precede the name.
|
|
2409
|
+
*/
|
|
2410
|
+
function memberDeclarationHead(tokens, nameIndex, insideClassBody, memberStart) {
|
|
2411
|
+
// A computed member (`[key]() {}`) is named by the `]` that closes its key.
|
|
2412
|
+
let index = nameIndex - 1;
|
|
2413
|
+
if (isPunct(tokens[nameIndex], ']')) {
|
|
2414
|
+
const open = matchingOpenBracket(tokens, nameIndex);
|
|
2415
|
+
if (open < 0) {
|
|
2416
|
+
return undefined;
|
|
2417
|
+
}
|
|
2418
|
+
index = open - 1;
|
|
2419
|
+
}
|
|
2420
|
+
let async = false;
|
|
2421
|
+
let generator = false;
|
|
2422
|
+
for (;;) {
|
|
2423
|
+
while (index >= (memberStart ?? 0)) {
|
|
2424
|
+
const token = tokens[index];
|
|
2425
|
+
if (isPunct(token, '*') || isPunct(token, '#')) {
|
|
2426
|
+
generator ||= isPunct(token, '*');
|
|
2427
|
+
index--;
|
|
2428
|
+
continue;
|
|
2429
|
+
}
|
|
2430
|
+
if (token?.kind === 'identifier') {
|
|
2431
|
+
const nextToken = tokens[index + 1];
|
|
2432
|
+
const startsMethodModifier = nextToken !== undefined && nextToken.lineBreakBefore !== true;
|
|
2433
|
+
if (token.value === 'async' && startsMethodModifier) {
|
|
2434
|
+
async = true;
|
|
2435
|
+
index--;
|
|
2436
|
+
continue;
|
|
2437
|
+
}
|
|
2438
|
+
if (MEMBER_MODIFIERS.has(token.value) && startsMethodModifier) {
|
|
2439
|
+
index--;
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
break;
|
|
2444
|
+
}
|
|
2445
|
+
if (insideClassBody && memberStart !== undefined && index < memberStart) {
|
|
2446
|
+
return { async, generator };
|
|
2447
|
+
}
|
|
2448
|
+
const start = tokens[index];
|
|
2449
|
+
if (start?.kind === 'punct' &&
|
|
2450
|
+
(MEMBER_SEPARATORS.has(start.value) ||
|
|
2451
|
+
(start.value === '}' && start.memberSeparator === true))) {
|
|
2452
|
+
return { async, generator };
|
|
2453
|
+
}
|
|
2454
|
+
// Decorators precede the modifiers they apply to, and each one ends just before them.
|
|
2455
|
+
const decorator = decoratorStartIndex(tokens, index);
|
|
2456
|
+
if (decorator < 0) {
|
|
2457
|
+
return undefined;
|
|
2458
|
+
}
|
|
2459
|
+
index = decorator - 1;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
function isPunct(token, value) {
|
|
2463
|
+
return token?.kind === 'punct' && token.value === value;
|
|
2464
|
+
}
|
|
2465
|
+
function classifyEquals(source, offset, atTypeBoundary = false) {
|
|
2466
|
+
const next = source[offset + 1];
|
|
2467
|
+
const previous = source[offset - 1];
|
|
2468
|
+
if (next === '>') {
|
|
2469
|
+
return { operator: 'arrow', operatorEndOffset: offset + 2 };
|
|
2470
|
+
}
|
|
2471
|
+
if (next === '=' ||
|
|
2472
|
+
(previous !== undefined &&
|
|
2473
|
+
'=!<>+-*/%&|^?'.includes(previous) &&
|
|
2474
|
+
!(atTypeBoundary && previous === '>'))) {
|
|
2475
|
+
let end = offset + 1;
|
|
2476
|
+
while (source[end] === '=') {
|
|
2477
|
+
end++;
|
|
2478
|
+
}
|
|
2479
|
+
return { operator: 'other', operatorEndOffset: end };
|
|
2480
|
+
}
|
|
2481
|
+
return { operator: 'assignment', operatorEndOffset: offset + 1 };
|
|
2482
|
+
}
|
|
2483
|
+
function readIdentifier(source, start) {
|
|
2484
|
+
let end = start;
|
|
2485
|
+
let value = '';
|
|
2486
|
+
let first = true;
|
|
2487
|
+
while (end < source.length) {
|
|
2488
|
+
const escaped = isUnicodeEscapeStart(source, end);
|
|
2489
|
+
const segment = escaped ? readUnicodeEscape(source, end) : readCodePoint(source, end);
|
|
2490
|
+
if (segment === undefined) {
|
|
2491
|
+
break;
|
|
2492
|
+
}
|
|
2493
|
+
const allowed = first
|
|
2494
|
+
? isIdentifierStartCharacter(segment.character)
|
|
2495
|
+
: isIdentifierPartCharacter(segment.character);
|
|
2496
|
+
if (!allowed) {
|
|
2497
|
+
if (escaped) {
|
|
2498
|
+
throw new ModuleSpecifierError('Invalid Unicode escape in identifier', 'invalid-source', end);
|
|
2499
|
+
}
|
|
2500
|
+
break;
|
|
2501
|
+
}
|
|
2502
|
+
value += segment.character;
|
|
2503
|
+
end = segment.end;
|
|
2504
|
+
first = false;
|
|
2505
|
+
}
|
|
2506
|
+
if (first) {
|
|
2507
|
+
throw new ModuleSpecifierError('Invalid identifier', 'invalid-source', start);
|
|
2508
|
+
}
|
|
2509
|
+
return { value, end };
|
|
2510
|
+
}
|
|
2511
|
+
function isArrowHead(token, greaterOffset) {
|
|
2512
|
+
return (token?.kind === 'punct' &&
|
|
2513
|
+
token.operator === 'arrow' &&
|
|
2514
|
+
(greaterOffset === undefined || token.operatorEndOffset === greaterOffset + 1));
|
|
2515
|
+
}
|
|
2516
|
+
function lineBreakBetween(tokens, start, end) {
|
|
2517
|
+
for (let index = start + 1; index <= end; index++) {
|
|
2518
|
+
if (tokens[index]?.lineBreakBefore === true) {
|
|
2519
|
+
return true;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return false;
|
|
2523
|
+
}
|
|
2524
|
+
function isMemberExpressionEnd(token) {
|
|
2525
|
+
if (token.assertionEnd === true) {
|
|
2526
|
+
return true;
|
|
2527
|
+
}
|
|
2528
|
+
if (token.kind === 'identifier') {
|
|
2529
|
+
return (token.propertyName === true ||
|
|
2530
|
+
(!REGEX_PRECEDING_KEYWORDS.has(token.value) && !['await', 'yield'].includes(token.value)));
|
|
2531
|
+
}
|
|
2532
|
+
return (EXPRESSION_ENDING.has(token.kind) ||
|
|
2533
|
+
isPunct(token, ')') ||
|
|
2534
|
+
isPunct(token, ']') ||
|
|
2535
|
+
(token.kind === 'punct' && token.expressionEnd === true));
|
|
2536
|
+
}
|
|
2537
|
+
function advanceMember(member, token, previous) {
|
|
2538
|
+
if (member.phase === 'initializer' || member.phase === 'parameters') {
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
if (isPunct(token, '@')) {
|
|
2542
|
+
member.phase = 'decorator';
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
if (member.phase === 'decorator') {
|
|
2546
|
+
if (token.lineBreakBefore !== true &&
|
|
2547
|
+
!isPunct(previous, ')') &&
|
|
2548
|
+
!(previous?.kind === 'identifier' && token.kind === 'identifier')) {
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2551
|
+
member.phase = 'head';
|
|
2552
|
+
}
|
|
2553
|
+
if (token.kind === 'punct' && token.operator === 'assignment') {
|
|
2554
|
+
member.phase = 'initializer';
|
|
2555
|
+
}
|
|
2556
|
+
else if (isPunct(token, ':')) {
|
|
2557
|
+
member.phase = 'type';
|
|
2558
|
+
}
|
|
2559
|
+
else if (isPunct(token, '(') && member.phase !== 'type') {
|
|
2560
|
+
member.phase = 'parameters';
|
|
2561
|
+
}
|
|
2562
|
+
else if (member.phase === 'head' &&
|
|
2563
|
+
((token.kind === 'identifier' &&
|
|
2564
|
+
!MEMBER_MODIFIERS.has(token.value) &&
|
|
2565
|
+
!['declare', 'readonly'].includes(token.value)) ||
|
|
2566
|
+
token.kind === 'string' ||
|
|
2567
|
+
token.kind === 'number' ||
|
|
2568
|
+
isPunct(token, '['))) {
|
|
2569
|
+
member.phase = 'field';
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
function classFieldCanEnd(member, previous, next, nextIdentifier) {
|
|
2573
|
+
if (previous === undefined || member.phase === 'parameters' || member.phase === 'decorator') {
|
|
2574
|
+
return false;
|
|
2575
|
+
}
|
|
2576
|
+
const canEnd = isMemberExpressionEnd(previous) ||
|
|
2577
|
+
((member.phase === 'field' || member.phase === 'head') &&
|
|
2578
|
+
(previous.kind === 'identifier' || isPunct(previous, '?') || isPunct(previous, '!'))) ||
|
|
2579
|
+
(member.phase === 'type' &&
|
|
2580
|
+
((previous.kind === 'identifier' && !TYPE_OPERAND_KEYWORDS.has(previous.value)) ||
|
|
2581
|
+
isPunct(previous, '>') ||
|
|
2582
|
+
isPunct(previous, '}')));
|
|
2583
|
+
if (!canEnd) {
|
|
2584
|
+
return false;
|
|
2585
|
+
}
|
|
2586
|
+
if (member.phase === 'initializer') {
|
|
2587
|
+
return !expressionContinues(next, nextIdentifier);
|
|
2588
|
+
}
|
|
2589
|
+
// An uninitialized field cannot continue by multiplication. Type operators and suffixes can.
|
|
2590
|
+
return !UNINITIALIZED_FIELD_CONTINUATIONS.has(next);
|
|
2591
|
+
}
|
|
2592
|
+
const UNINITIALIZED_FIELD_CONTINUATIONS = new Set([
|
|
2593
|
+
'(',
|
|
2594
|
+
'[',
|
|
2595
|
+
'.',
|
|
2596
|
+
'?',
|
|
2597
|
+
'!',
|
|
2598
|
+
'=',
|
|
2599
|
+
':',
|
|
2600
|
+
'<',
|
|
2601
|
+
'>',
|
|
2602
|
+
'|',
|
|
2603
|
+
'&',
|
|
2604
|
+
',',
|
|
2605
|
+
]);
|
|
2606
|
+
function atTypeRoot(type) {
|
|
2607
|
+
return (type.parenDepth === 0 &&
|
|
2608
|
+
type.bracketDepth === 0 &&
|
|
2609
|
+
type.braceDepth === 0 &&
|
|
2610
|
+
type.angleDepth === 0);
|
|
2611
|
+
}
|
|
2612
|
+
function sameTypeDepth(left, right) {
|
|
2613
|
+
return (left.parenDepth === right.parenDepth &&
|
|
2614
|
+
left.bracketDepth === right.bracketDepth &&
|
|
2615
|
+
left.braceDepth === right.braceDepth &&
|
|
2616
|
+
left.angleDepth === right.angleDepth);
|
|
2617
|
+
}
|
|
2618
|
+
/**
|
|
2619
|
+
* Infer an arrow signature using the index of `=` in `=>`, regardless of whether `>` is emitted.
|
|
2620
|
+
*/
|
|
2621
|
+
function inferArrowContext(tokens, arrowIndex) {
|
|
2622
|
+
const arrow = tokens[arrowIndex];
|
|
2623
|
+
if (!isArrowHead(arrow)) {
|
|
2624
|
+
return undefined;
|
|
2625
|
+
}
|
|
2626
|
+
const beforeArrow = arrowIndex - 1;
|
|
2627
|
+
const parameters = tokens[beforeArrow];
|
|
2628
|
+
let parametersIndex;
|
|
2629
|
+
if (parameters?.kind === 'punct' && parameters.value === ')') {
|
|
2630
|
+
parametersIndex = matchingOpenParen(tokens, beforeArrow);
|
|
2631
|
+
}
|
|
2632
|
+
else if (parameters?.kind === 'identifier') {
|
|
2633
|
+
parametersIndex = beforeArrow;
|
|
2634
|
+
}
|
|
2635
|
+
else {
|
|
2636
|
+
return undefined;
|
|
2637
|
+
}
|
|
2638
|
+
const parametersStart = parametersIndex >= 0 ? tokens[parametersIndex] : undefined;
|
|
2639
|
+
if (parametersStart?.kind !== 'identifier' && parametersStart?.kind !== 'punct') {
|
|
2640
|
+
return undefined;
|
|
2641
|
+
}
|
|
2642
|
+
// Only trivia before the parameters can break the async modifier; newlines inside `(...)` cannot.
|
|
2643
|
+
// A generic arrow keeps its type-parameter list between `async` and the parameter list.
|
|
2644
|
+
const markerIndex = parametersStart.kind === 'punct' && parametersStart.value === '('
|
|
2645
|
+
? signatureNameIndex(tokens, parametersIndex)
|
|
2646
|
+
: parametersIndex - 1;
|
|
2647
|
+
const markerToken = tokens[markerIndex];
|
|
2648
|
+
const async = markerToken?.kind === 'identifier' &&
|
|
2649
|
+
markerToken.value === 'async' &&
|
|
2650
|
+
asyncModifierIsContinuous(tokens, markerIndex, parametersIndex);
|
|
2651
|
+
return (parametersStart.signatureContext ?? {
|
|
2652
|
+
async,
|
|
2653
|
+
generator: false,
|
|
2654
|
+
isExpression: false,
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
/** Parameter lists can start at expression/statement boundaries, never after a call's name. */
|
|
2658
|
+
function arrowParameterPrefix(token) {
|
|
2659
|
+
if (token === undefined) {
|
|
2660
|
+
return true;
|
|
2661
|
+
}
|
|
2662
|
+
if (token.kind === 'punct') {
|
|
2663
|
+
return (EXPRESSION_BEFORE_BRACE.has(token.value) || [':', '>', ';', '{', '/'].includes(token.value));
|
|
2664
|
+
}
|
|
2665
|
+
return (token.kind === 'identifier' &&
|
|
2666
|
+
['return', 'throw', 'yield', 'await', 'void', 'typeof', 'delete', 'default'].includes(token.value));
|
|
2667
|
+
}
|
|
2668
|
+
function parameterSignatureContext(tokens, pending, insideMethodContainer, insideClassBody, lineBreakBefore, memberStart) {
|
|
2669
|
+
const openIndex = tokens.length;
|
|
2670
|
+
const nameIndex = signatureNameIndex(tokens, openIndex);
|
|
2671
|
+
const name = tokens[nameIndex];
|
|
2672
|
+
const head = insideMethodContainer && isMethodName(name)
|
|
2673
|
+
? memberDeclarationHead(tokens, nameIndex, insideClassBody, memberStart)
|
|
2674
|
+
: undefined;
|
|
2675
|
+
if (head !== undefined) {
|
|
2676
|
+
return {
|
|
2677
|
+
context: {
|
|
2678
|
+
...head,
|
|
2679
|
+
isExpression: false,
|
|
2680
|
+
isClassMember: insideClassBody,
|
|
2681
|
+
},
|
|
2682
|
+
isFunctionKeyword: false,
|
|
2683
|
+
isArrowSignature: false,
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
if (pending?.phase === 'awaiting-parameters' &&
|
|
2687
|
+
(name?.kind === 'identifier' || isPunct(name, '*'))) {
|
|
2688
|
+
return { context: pending, isFunctionKeyword: true, isArrowSignature: false };
|
|
2689
|
+
}
|
|
2690
|
+
const marker = tokens[nameIndex - 1];
|
|
2691
|
+
// A generic signature has no name of its own before `(` when it is anonymous (`async <T,>(v: T) => {}`).
|
|
2692
|
+
const startsArrowParameters = arrowParameterPrefix(name) || (isIdentifier(name, 'async') && arrowParameterPrefix(marker));
|
|
2693
|
+
if (startsArrowParameters) {
|
|
2694
|
+
const asyncToken = name?.kind === 'identifier' && name.value === 'async' ? name : undefined;
|
|
2695
|
+
const async = asyncToken !== undefined &&
|
|
2696
|
+
!lineBreakBefore &&
|
|
2697
|
+
asyncModifierIsContinuous(tokens, nameIndex, openIndex - 1);
|
|
2698
|
+
return {
|
|
2699
|
+
context: { async, generator: false, isExpression: false },
|
|
2700
|
+
isFunctionKeyword: false,
|
|
2701
|
+
isArrowSignature: true,
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
// Without a `function` keyword the parameter list must start a real member declaration, so the
|
|
2705
|
+
// `:` of a conditional expression or of an unrelated expression never claims a return annotation.
|
|
2706
|
+
return undefined;
|
|
2707
|
+
}
|
|
2708
|
+
function asyncModifierIsContinuous(tokens, modifierIndex, parameterIndex) {
|
|
2709
|
+
const genericOpen = isPunct(tokens[modifierIndex + 1], '<') ? modifierIndex + 1 : undefined;
|
|
2710
|
+
return !lineBreakBetween(tokens, modifierIndex, genericOpen ?? parameterIndex);
|
|
2711
|
+
}
|
|
2712
|
+
function nearestFunctionParameter(parameters) {
|
|
2713
|
+
for (let index = parameters.length - 1; index >= 0; index--) {
|
|
2714
|
+
const parameter = parameters[index];
|
|
2715
|
+
if (parameter?.signature !== undefined) {
|
|
2716
|
+
return parameter;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
return undefined;
|
|
2720
|
+
}
|
|
2721
|
+
function nearestClassField(braces) {
|
|
2722
|
+
for (let index = braces.length - 1; index >= 0; index--) {
|
|
2723
|
+
const brace = braces[index];
|
|
2724
|
+
if (brace?.role === 'class' && brace.state.member?.phase === 'initializer') {
|
|
2725
|
+
return brace.state.member;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
return undefined;
|
|
2729
|
+
}
|
|
2730
|
+
function consumeForIterationSeparator(tokens, parameters, depth) {
|
|
2731
|
+
const control = parameters[parameters.length - 1];
|
|
2732
|
+
if (control?.forPhase !== 'binding' ||
|
|
2733
|
+
control.depth.parenDepth !== depth.parenDepth ||
|
|
2734
|
+
control.depth.bracketDepth !== depth.bracketDepth ||
|
|
2735
|
+
control.depth.braceDepth !== depth.braceDepth ||
|
|
2736
|
+
control.depth.frameDepth !== depth.frameDepth ||
|
|
2737
|
+
tokens.length <= control.openIndex) {
|
|
2738
|
+
return false;
|
|
2739
|
+
}
|
|
2740
|
+
const previous = tokens[tokens.length - 1];
|
|
2741
|
+
const isSeparator = previous !== undefined &&
|
|
2742
|
+
isMemberExpressionEnd(previous) &&
|
|
2743
|
+
!isIdentifier(previous, 'const') &&
|
|
2744
|
+
!isIdentifier(previous, 'let') &&
|
|
2745
|
+
!isIdentifier(previous, 'var');
|
|
2746
|
+
if (isSeparator) {
|
|
2747
|
+
control.forPhase = 'iteration';
|
|
2748
|
+
}
|
|
2749
|
+
return isSeparator;
|
|
2750
|
+
}
|
|
2751
|
+
// At the owner's root, a finished operand can still have a suffix or compose with another
|
|
2752
|
+
// type. Only a runtime boundary retires the assertion; internal closers never do so themselves.
|
|
2753
|
+
function assertionTypeContinues(annotation, next, identifier, lineBreak, after) {
|
|
2754
|
+
if (next === '-' && annotation.typeOperandExpected && after !== '-') {
|
|
2755
|
+
return true;
|
|
2756
|
+
}
|
|
2757
|
+
if (identifier !== undefined) {
|
|
2758
|
+
return annotation.typeOperandExpected || (identifier === 'extends' && !lineBreak);
|
|
2759
|
+
}
|
|
2760
|
+
if (next === '?') {
|
|
2761
|
+
return annotation.conditionalOperand;
|
|
2762
|
+
}
|
|
2763
|
+
if (next === ':') {
|
|
2764
|
+
return annotation.conditionalDepth > 0;
|
|
2765
|
+
}
|
|
2766
|
+
if (next === '(') {
|
|
2767
|
+
return annotation.typeOperandExpected || annotation.importCallPending;
|
|
2768
|
+
}
|
|
2769
|
+
if (next === '{' || next === '`' || next === "'" || next === '"') {
|
|
2770
|
+
return annotation.typeOperandExpected;
|
|
2771
|
+
}
|
|
2772
|
+
if (next === '<' || next === '[') {
|
|
2773
|
+
return after !== next && after !== '=' && (annotation.typeOperandExpected || !lineBreak);
|
|
2774
|
+
}
|
|
2775
|
+
if (next === '|' || next === '&') {
|
|
2776
|
+
return after !== next && after !== '=';
|
|
2777
|
+
}
|
|
2778
|
+
if (next === '.') {
|
|
2779
|
+
return true;
|
|
2780
|
+
}
|
|
2781
|
+
if (next === '=' && after === '>') {
|
|
2782
|
+
return true;
|
|
2783
|
+
}
|
|
2784
|
+
return (annotation.typeOperandExpected &&
|
|
2785
|
+
!['/', '+', '-', '*', '%', '!', ';', ',', ')', ']', '}', '=', '>'].includes(next));
|
|
2786
|
+
}
|
|
2787
|
+
function canStartTypeAssertion(previous) {
|
|
2788
|
+
if (previous?.assertionEnd === true) {
|
|
2789
|
+
return true;
|
|
2790
|
+
}
|
|
2791
|
+
return (previous !== undefined &&
|
|
2792
|
+
isMemberExpressionEnd(previous) &&
|
|
2793
|
+
!(previous.kind === 'identifier' &&
|
|
2794
|
+
previous.propertyName !== true &&
|
|
2795
|
+
[
|
|
2796
|
+
'const',
|
|
2797
|
+
'let',
|
|
2798
|
+
'var',
|
|
2799
|
+
'function',
|
|
2800
|
+
'class',
|
|
2801
|
+
'type',
|
|
2802
|
+
'interface',
|
|
2803
|
+
'namespace',
|
|
2804
|
+
'import',
|
|
2805
|
+
'export',
|
|
2806
|
+
'extends',
|
|
2807
|
+
'implements',
|
|
2808
|
+
].includes(previous.value)));
|
|
2809
|
+
}
|
|
2810
|
+
function isMethodName(token) {
|
|
2811
|
+
return (token?.kind === 'identifier' ||
|
|
2812
|
+
token?.kind === 'string' ||
|
|
2813
|
+
token?.kind === 'number' ||
|
|
2814
|
+
isPunct(token, ']'));
|
|
2815
|
+
}
|
|
2816
|
+
function expressionFunctionBodyEnded(ch, parenDepth, bracketDepth, braceDepth, frameDepth, functionDepth, conditionalDepth, context) {
|
|
2817
|
+
if (functionDepth > context.functionDepth ||
|
|
2818
|
+
braceDepth > context.braceDepth ||
|
|
2819
|
+
frameDepth !== context.frameDepth) {
|
|
2820
|
+
return false;
|
|
2821
|
+
}
|
|
2822
|
+
if (ch === ':' &&
|
|
2823
|
+
context.conditionalDepth > 0 &&
|
|
2824
|
+
conditionalDepth === context.conditionalDepth &&
|
|
2825
|
+
parenDepth === context.parenDepth &&
|
|
2826
|
+
bracketDepth === context.bracketDepth &&
|
|
2827
|
+
braceDepth === context.braceDepth) {
|
|
2828
|
+
return true;
|
|
2829
|
+
}
|
|
2830
|
+
if (ch === ';' || ch === ',') {
|
|
2831
|
+
return (parenDepth === context.parenDepth &&
|
|
2832
|
+
bracketDepth === context.bracketDepth &&
|
|
2833
|
+
braceDepth === context.braceDepth);
|
|
2834
|
+
}
|
|
2835
|
+
if (ch === ')' || ch === ']' || ch === '}') {
|
|
2836
|
+
return (parenDepth === context.parenDepth &&
|
|
2837
|
+
bracketDepth === context.bracketDepth &&
|
|
2838
|
+
braceDepth === context.braceDepth);
|
|
2839
|
+
}
|
|
2840
|
+
return false;
|
|
2841
|
+
}
|
|
2842
|
+
function lineTerminatorEndsExpressionFunctionBody(next, prevKind, prevPunct, previous, nextIdentifier, nextIsUpdate, parenDepth, bracketDepth, braceDepth, frameDepth, functionDepth, context) {
|
|
2843
|
+
if (functionDepth > context.functionDepth ||
|
|
2844
|
+
parenDepth !== context.parenDepth ||
|
|
2845
|
+
bracketDepth !== context.bracketDepth ||
|
|
2846
|
+
braceDepth !== context.braceDepth ||
|
|
2847
|
+
frameDepth !== context.frameDepth) {
|
|
2848
|
+
return false;
|
|
2849
|
+
}
|
|
2850
|
+
const previousEndsExpression = previous?.assertionEnd === true ||
|
|
2851
|
+
EXPRESSION_ENDING.has(prevKind) ||
|
|
2852
|
+
(prevKind === 'punct' &&
|
|
2853
|
+
([')', ']', '}'].includes(prevPunct ?? '') ||
|
|
2854
|
+
(previous?.kind === 'punct' && previous.expressionEnd === true)));
|
|
2855
|
+
return previousEndsExpression && (nextIsUpdate || !expressionContinues(next, nextIdentifier));
|
|
2856
|
+
}
|
|
2857
|
+
function expressionContinues(next, identifier) {
|
|
2858
|
+
return (EXPRESSION_CONTINUATION_CHARACTERS.has(next) ||
|
|
2859
|
+
identifier === 'in' ||
|
|
2860
|
+
identifier === 'instanceof');
|
|
2861
|
+
}
|
|
2862
|
+
/** Whether a token is the given identifier. */
|
|
2863
|
+
function isIdentifier(token, value) {
|
|
2864
|
+
return token?.kind === 'identifier' && token.value === value;
|
|
2865
|
+
}
|
|
2866
|
+
/**
|
|
2867
|
+
* The index of the `[` matching the `]` at `closeIndex`, or `-1` when the source has no match.
|
|
2868
|
+
*/
|
|
2869
|
+
function matchingOpenBracket(tokens, closeIndex) {
|
|
2870
|
+
let depth = 0;
|
|
2871
|
+
for (let k = closeIndex; k >= 0; k--) {
|
|
2872
|
+
const token = tokens[k];
|
|
2873
|
+
if (token?.kind !== 'punct') {
|
|
168
2874
|
continue;
|
|
169
2875
|
}
|
|
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;
|
|
2876
|
+
if (token.value === ']') {
|
|
2877
|
+
depth++;
|
|
187
2878
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
prevKind = 'regex';
|
|
193
|
-
afterControlHead = false;
|
|
194
|
-
pendingControlKeyword = undefined;
|
|
195
|
-
continue;
|
|
2879
|
+
else if (token.value === '[') {
|
|
2880
|
+
depth--;
|
|
2881
|
+
if (depth === 0) {
|
|
2882
|
+
return k;
|
|
196
2883
|
}
|
|
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
2884
|
}
|
|
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;
|
|
2885
|
+
}
|
|
2886
|
+
return -1;
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* The index of the `(` matching the `)` at `closeIndex`, or `-1` when the source has no match.
|
|
2890
|
+
*/
|
|
2891
|
+
function matchingOpenParen(tokens, closeIndex) {
|
|
2892
|
+
const close = tokens[closeIndex];
|
|
2893
|
+
if (close?.kind === 'punct' && close.matchingOpen !== undefined) {
|
|
2894
|
+
return close.matchingOpen;
|
|
2895
|
+
}
|
|
2896
|
+
let depth = 0;
|
|
2897
|
+
for (let k = closeIndex; k >= 0; k--) {
|
|
2898
|
+
const token = tokens[k];
|
|
2899
|
+
if (token?.kind !== 'punct') {
|
|
239
2900
|
continue;
|
|
240
2901
|
}
|
|
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;
|
|
2902
|
+
if (token.value === ')') {
|
|
2903
|
+
depth++;
|
|
257
2904
|
}
|
|
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;
|
|
2905
|
+
else if (token.value === '(') {
|
|
2906
|
+
depth--;
|
|
2907
|
+
if (depth === 0) {
|
|
2908
|
+
return k;
|
|
267
2909
|
}
|
|
268
2910
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
2911
|
+
}
|
|
2912
|
+
return -1;
|
|
2913
|
+
}
|
|
2914
|
+
/** Claim the most recent class header at this exact depth; nested heritage classes claim first. */
|
|
2915
|
+
function takeClassBodyContext(contexts, depth) {
|
|
2916
|
+
for (let index = contexts.length - 1; index >= 0; index--) {
|
|
2917
|
+
const context = contexts[index];
|
|
2918
|
+
if (context?.parenDepth === depth.parenDepth &&
|
|
2919
|
+
context.bracketDepth === depth.bracketDepth &&
|
|
2920
|
+
context.braceDepth === depth.braceDepth &&
|
|
2921
|
+
context.frameDepth === depth.frameDepth) {
|
|
2922
|
+
contexts.splice(index, 1);
|
|
2923
|
+
return context;
|
|
278
2924
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
2925
|
+
}
|
|
2926
|
+
return undefined;
|
|
2927
|
+
}
|
|
2928
|
+
/**
|
|
2929
|
+
* Read a JSX tag name starting at `i` (the first character after `<`). Member expressions
|
|
2930
|
+
* (`<UI.Panel>`), namespaced names (`<svg:rect>`) and dashed names (`<my-tag>`) are part of the name.
|
|
2931
|
+
*/
|
|
2932
|
+
function readJsxTagName(source, i, n) {
|
|
2933
|
+
const start = i;
|
|
2934
|
+
while (i < n) {
|
|
2935
|
+
const ch = source[i];
|
|
2936
|
+
if (ch === undefined ||
|
|
2937
|
+
!(isIdentifierPartAt(source, i) || ch === '.' || ch === '-' || ch === ':')) {
|
|
2938
|
+
break;
|
|
287
2939
|
}
|
|
288
|
-
|
|
289
|
-
prevKind = 'punct';
|
|
290
|
-
prevPunct = ch;
|
|
291
|
-
i++;
|
|
2940
|
+
i += codePointLength(source, i);
|
|
292
2941
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
2942
|
+
return { name: source.slice(start, i), end: i };
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2945
|
+
* Skip a quoted JSX attribute value starting at its opening quote. The value is inert text: it ends
|
|
2946
|
+
* at the matching quote and backslashes are not escapes, so it can never contribute a specifier.
|
|
2947
|
+
*/
|
|
2948
|
+
function skipJsxAttributeValue(source, i, n) {
|
|
2949
|
+
const quote = source[i];
|
|
2950
|
+
let k = i + 1;
|
|
2951
|
+
while (k < n && source[k] !== quote) {
|
|
2952
|
+
k++;
|
|
297
2953
|
}
|
|
298
|
-
|
|
2954
|
+
if (k >= n) {
|
|
2955
|
+
throw new ModuleSpecifierError('Unterminated JSX attribute value', 'invalid-source', i);
|
|
2956
|
+
}
|
|
2957
|
+
return k + 1;
|
|
299
2958
|
}
|
|
300
2959
|
/**
|
|
301
2960
|
* Significant token kinds that clearly terminate an expression. After one of these, `/` is
|
|
@@ -308,6 +2967,29 @@ const EXPRESSION_ENDING = new Set([
|
|
|
308
2967
|
'template',
|
|
309
2968
|
'regex',
|
|
310
2969
|
]);
|
|
2970
|
+
// A token beginning with one of these characters may continue an expression across a line
|
|
2971
|
+
// terminator, so ASI must not retire an expression-bodied arrow before it.
|
|
2972
|
+
const EXPRESSION_CONTINUATION_CHARACTERS = new Set([
|
|
2973
|
+
'.',
|
|
2974
|
+
'(',
|
|
2975
|
+
'[',
|
|
2976
|
+
'?',
|
|
2977
|
+
':',
|
|
2978
|
+
',',
|
|
2979
|
+
'+',
|
|
2980
|
+
'-',
|
|
2981
|
+
'*',
|
|
2982
|
+
'/',
|
|
2983
|
+
'%',
|
|
2984
|
+
'<',
|
|
2985
|
+
'>',
|
|
2986
|
+
'=',
|
|
2987
|
+
'!',
|
|
2988
|
+
'&',
|
|
2989
|
+
'|',
|
|
2990
|
+
'^',
|
|
2991
|
+
'`',
|
|
2992
|
+
]);
|
|
311
2993
|
/**
|
|
312
2994
|
* Expression-starting keywords. After one of these, a `/` begins a regex literal even though the
|
|
313
2995
|
* keyword itself is tokenized as an identifier. `of`, `yield` and `await` are intentionally
|
|
@@ -355,9 +3037,8 @@ function pendingControlKeywordAfter(value, isMemberName, pending) {
|
|
|
355
3037
|
* expression on its right: assignment (`= {`), an argument or element list (`f({`, `[{`) and the
|
|
356
3038
|
* separators and binary/logical operators. `>` is absent because it usually ends an arrow function
|
|
357
3039
|
* (`() => {}`), whose `{` opens a function body, and `)`/`}` are absent because they close
|
|
358
|
-
* statement heads and blocks. `:` is
|
|
359
|
-
*
|
|
360
|
-
* otherwise.
|
|
3040
|
+
* statement heads and blocks. `:` is classified separately using property/conditional depth,
|
|
3041
|
+
* because labels and `case`/`default` clauses instead introduce statements.
|
|
361
3042
|
*/
|
|
362
3043
|
const EXPRESSION_BEFORE_BRACE = new Set([
|
|
363
3044
|
'=',
|
|
@@ -377,26 +3058,41 @@ const EXPRESSION_BEFORE_BRACE = new Set([
|
|
|
377
3058
|
]);
|
|
378
3059
|
// Keywords that start an expression but are still followed by a statement block.
|
|
379
3060
|
const BLOCK_AFTER_KEYWORD = new Set(['else', 'do']);
|
|
3061
|
+
// Whether a `function` keyword sits in expression position (a function expression used as a value,
|
|
3062
|
+
// so a slash after its body `{}` divides) rather than statement position (a declaration, so a slash
|
|
3063
|
+
// after its body may start a regex). The previous significant token must expect an expression on
|
|
3064
|
+
// its right: an expression-starting punctuation, an expression keyword, or a regex.
|
|
3065
|
+
function functionIsExpressionPosition(prevKind, prevPunct, colonIsExpression, expressionExpected) {
|
|
3066
|
+
if (expressionExpected) {
|
|
3067
|
+
return true;
|
|
3068
|
+
}
|
|
3069
|
+
if (prevKind === 'punct') {
|
|
3070
|
+
return prevPunct === ':' ? colonIsExpression : EXPRESSION_BEFORE_BRACE.has(prevPunct ?? '');
|
|
3071
|
+
}
|
|
3072
|
+
return prevKind === 'regex';
|
|
3073
|
+
}
|
|
380
3074
|
/**
|
|
381
3075
|
* Whether a `{` opens an expression (an object literal or a destructuring pattern) instead of a
|
|
382
|
-
* statement block or function body. `
|
|
383
|
-
*
|
|
3076
|
+
* statement block or function body. `colonIsExpression` distinguishes property/conditional colons
|
|
3077
|
+
* from statement separators. Statement position is the default, so an unknown context keeps the existing
|
|
384
3078
|
* behaviour of allowing a regex literal after the matching `}`.
|
|
385
3079
|
*/
|
|
386
|
-
function opensObjectLiteral(prevKind, prevPunct, prevIdentifier, prevIsExpressionKeyword, inTemplateExpression,
|
|
3080
|
+
function opensObjectLiteral(prevKind, prevPunct, prevIdentifier, prevIsExpressionKeyword, inTemplateExpression, colonIsExpression, lineBreakBefore) {
|
|
387
3081
|
if (prevKind === 'punct') {
|
|
388
3082
|
// Inside an object literal a `:` separates a property from its value (`{ scale: {} }`, where a
|
|
389
3083
|
// following `/` divides), while at statement level the same `:` follows a label
|
|
390
3084
|
// (`label: {}`) or a `case`/`default` clause, whose `{` opens a block.
|
|
391
3085
|
if (prevPunct === ':') {
|
|
392
|
-
return
|
|
3086
|
+
return colonIsExpression;
|
|
393
3087
|
}
|
|
394
3088
|
return EXPRESSION_BEFORE_BRACE.has(prevPunct ?? '');
|
|
395
3089
|
}
|
|
396
3090
|
if (prevKind === 'identifier') {
|
|
397
3091
|
// `return {` is an expression, `else {`/`do {` are blocks and every other identifier
|
|
398
3092
|
// (`function f`, `class C`) sits in statement position.
|
|
399
|
-
return prevIsExpressionKeyword &&
|
|
3093
|
+
return (prevIsExpressionKeyword &&
|
|
3094
|
+
!BLOCK_AFTER_KEYWORD.has(prevIdentifier ?? '') &&
|
|
3095
|
+
!(prevIdentifier === 'return' && lineBreakBefore));
|
|
400
3096
|
}
|
|
401
3097
|
// Right after `${` an expression is expected, so a `{` opens an object literal.
|
|
402
3098
|
return inTemplateExpression && prevKind === 'start';
|
|
@@ -409,7 +3105,16 @@ function opensObjectLiteral(prevKind, prevPunct, prevIdentifier, prevIsExpressio
|
|
|
409
3105
|
* what it closed: an object literal ends an expression, so the `/` divides, while a statement block
|
|
410
3106
|
* leaves the expression-start position, where a regex literal may begin.
|
|
411
3107
|
*/
|
|
412
|
-
function isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword, afterControlHead, closedObjectBrace) {
|
|
3108
|
+
function isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword, afterControlHead, closedObjectBrace, previous) {
|
|
3109
|
+
if (previous?.assertionEnd === true) {
|
|
3110
|
+
return false;
|
|
3111
|
+
}
|
|
3112
|
+
if (previous?.kind === 'punct' && previous.value !== '}' && previous.expressionEnd === true) {
|
|
3113
|
+
return false;
|
|
3114
|
+
}
|
|
3115
|
+
if (previous?.kind === 'punct' && (previous.value === '++' || previous.value === '--')) {
|
|
3116
|
+
return previous.expressionEnd !== true;
|
|
3117
|
+
}
|
|
413
3118
|
if (prevKind === 'identifier' && prevIsExpressionKeyword) {
|
|
414
3119
|
return true;
|
|
415
3120
|
}
|
|
@@ -436,6 +3141,29 @@ function isRegexStart(prevKind, prevPunct, prevIsExpressionKeyword, afterControl
|
|
|
436
3141
|
function interpret(tokens) {
|
|
437
3142
|
const occurrences = [];
|
|
438
3143
|
const n = tokens.length;
|
|
3144
|
+
let closingDelimiters;
|
|
3145
|
+
const closingDelimiter = (openIndex) => {
|
|
3146
|
+
closingDelimiters ??= indexClosingDelimiters(tokens);
|
|
3147
|
+
return closingDelimiters.get(openIndex);
|
|
3148
|
+
};
|
|
3149
|
+
const resolvedRequireScopes = new Map();
|
|
3150
|
+
const requireIsShadowed = (scope) => {
|
|
3151
|
+
const path = [];
|
|
3152
|
+
let current = scope;
|
|
3153
|
+
while (current !== undefined && !resolvedRequireScopes.has(current)) {
|
|
3154
|
+
path.push(current);
|
|
3155
|
+
current = current.parent;
|
|
3156
|
+
}
|
|
3157
|
+
let shadowed = current === undefined ? false : (resolvedRequireScopes.get(current) ?? false);
|
|
3158
|
+
for (let index = path.length - 1; index >= 0; index--) {
|
|
3159
|
+
const owner = path[index];
|
|
3160
|
+
if (owner !== undefined) {
|
|
3161
|
+
shadowed ||= owner.bindings.some((range) => bindingHasRequire(tokens, range.start, range.end, closingDelimiter));
|
|
3162
|
+
resolvedRequireScopes.set(owner, shadowed);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
return shadowed;
|
|
3166
|
+
};
|
|
439
3167
|
let i = 0;
|
|
440
3168
|
while (i < n) {
|
|
441
3169
|
const token = tokens[i];
|
|
@@ -444,7 +3172,7 @@ function interpret(tokens) {
|
|
|
444
3172
|
continue;
|
|
445
3173
|
}
|
|
446
3174
|
if (token.value === 'import') {
|
|
447
|
-
i = handleImport(tokens, i, occurrences);
|
|
3175
|
+
i = handleImport(tokens, i, occurrences, closingDelimiter);
|
|
448
3176
|
continue;
|
|
449
3177
|
}
|
|
450
3178
|
if (token.value === 'export') {
|
|
@@ -452,6 +3180,10 @@ function interpret(tokens) {
|
|
|
452
3180
|
continue;
|
|
453
3181
|
}
|
|
454
3182
|
if (token.value === 'require') {
|
|
3183
|
+
if (token.requireScope !== undefined && requireIsShadowed(token.requireScope)) {
|
|
3184
|
+
i++;
|
|
3185
|
+
continue;
|
|
3186
|
+
}
|
|
455
3187
|
i = handleRequire(tokens, i, occurrences);
|
|
456
3188
|
continue;
|
|
457
3189
|
}
|
|
@@ -465,13 +3197,32 @@ function interpret(tokens) {
|
|
|
465
3197
|
* - `obj.import(...)` is ignored (a member call, preceded by `.`).
|
|
466
3198
|
* - `import.meta` is ignored (treated as `import` followed by `.`).
|
|
467
3199
|
* - `import 'pkg'` and `import ... from 'pkg'` are `import`.
|
|
468
|
-
* -
|
|
3200
|
+
* - Dynamic import accepts a literal source with optional bounded object-literal options.
|
|
3201
|
+
*/
|
|
3202
|
+
/**
|
|
3203
|
+
* Whether the identifier at `i` is a property name (`obj.require`), which is never the global
|
|
3204
|
+
* `require`/`import`. A `.` that belongs to a spread (`...require('pkg')`) is not member access.
|
|
469
3205
|
*/
|
|
470
|
-
function
|
|
3206
|
+
function isMemberName(tokens, i) {
|
|
471
3207
|
const prev = tokens[i - 1];
|
|
3208
|
+
if (prev?.kind === 'punct' && prev.value === '#') {
|
|
3209
|
+
return true;
|
|
3210
|
+
}
|
|
3211
|
+
if (prev?.kind !== 'punct' || prev.value !== '.') {
|
|
3212
|
+
return false;
|
|
3213
|
+
}
|
|
3214
|
+
const second = tokens[i - 2];
|
|
3215
|
+
const third = tokens[i - 3];
|
|
3216
|
+
const isSpread = second?.kind === 'punct' &&
|
|
3217
|
+
second.value === '.' &&
|
|
3218
|
+
third?.kind === 'punct' &&
|
|
3219
|
+
third.value === '.';
|
|
3220
|
+
return !isSpread;
|
|
3221
|
+
}
|
|
3222
|
+
function handleImport(tokens, i, occurrences, closingDelimiter) {
|
|
472
3223
|
const next = tokens[i + 1];
|
|
473
3224
|
// `obj.import(...)`: a member call, not the import keyword.
|
|
474
|
-
if (
|
|
3225
|
+
if (isMemberName(tokens, i)) {
|
|
475
3226
|
return i + 1;
|
|
476
3227
|
}
|
|
477
3228
|
// `import.meta` / `import .foo`: not a module import.
|
|
@@ -483,80 +3234,164 @@ function handleImport(tokens, i, occurrences) {
|
|
|
483
3234
|
occurrences.push({ specifier: next.value, kind: 'import' });
|
|
484
3235
|
return i + 2;
|
|
485
3236
|
}
|
|
486
|
-
//
|
|
3237
|
+
// Keep literal specifiers with sole-argument or bounded object-literal options forms.
|
|
487
3238
|
if (next?.kind === 'punct' && next.value === '(') {
|
|
488
|
-
const specifier =
|
|
3239
|
+
const specifier = dynamicImportSpecifier(tokens, i + 2, closingDelimiter);
|
|
489
3240
|
if (specifier !== undefined) {
|
|
490
3241
|
occurrences.push({ specifier, kind: 'dynamic-import' });
|
|
491
3242
|
return i + 3;
|
|
492
3243
|
}
|
|
493
3244
|
return i + 1;
|
|
494
3245
|
}
|
|
495
|
-
// Static `import ... from 'pkg'`:
|
|
3246
|
+
// Static `import ... from 'pkg'`: consume only the supported clause.
|
|
496
3247
|
return scanImportSource(tokens, i, occurrences, 'import');
|
|
497
3248
|
}
|
|
498
3249
|
/**
|
|
499
3250
|
* Handle an `export` identifier. `export ... from 'pkg'` is `export-from`.
|
|
500
3251
|
*/
|
|
501
3252
|
function handleExport(tokens, i, occurrences) {
|
|
3253
|
+
// Member names share the same boundary rule as import and require.
|
|
3254
|
+
if (isMemberName(tokens, i)) {
|
|
3255
|
+
return i + 1;
|
|
3256
|
+
}
|
|
502
3257
|
return scanImportSource(tokens, i, occurrences, 'export-from');
|
|
503
3258
|
}
|
|
3259
|
+
/** Read only the supported clause; unrelated statements cannot supply its source. */
|
|
3260
|
+
function scanImportSource(tokens, i, occurrences, kind) {
|
|
3261
|
+
const sourceIndex = kind === 'export-from' ? exportSourceIndex(tokens, i) : importSourceIndex(tokens, i);
|
|
3262
|
+
const source = sourceIndex === undefined ? undefined : tokens[sourceIndex];
|
|
3263
|
+
if (source?.kind === 'string' && sourceIndex !== undefined) {
|
|
3264
|
+
occurrences.push({ specifier: source.value, kind });
|
|
3265
|
+
return sourceIndex + 1;
|
|
3266
|
+
}
|
|
3267
|
+
return i + 1;
|
|
3268
|
+
}
|
|
504
3269
|
/**
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* current statement (e.g. `import { export as v } from 'pkg'`).
|
|
3270
|
+
* Consume a default binding and/or a namespace/named clause, then require `from` + literal.
|
|
3271
|
+
* Each property lookalike fails locally. Named clauses accept only specifiers, never arbitrary
|
|
3272
|
+
* brace contents, so failed candidates cannot repeatedly search an unrelated suffix.
|
|
509
3273
|
*/
|
|
510
|
-
function
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
3274
|
+
function importSourceIndex(tokens, importIndex) {
|
|
3275
|
+
let index = importIndex + 1;
|
|
3276
|
+
if (isIdentifier(tokens[index], 'type') &&
|
|
3277
|
+
(isPunct(tokens[index + 1], '{') ||
|
|
3278
|
+
isPunct(tokens[index + 1], '*') ||
|
|
3279
|
+
(tokens[index + 1]?.kind === 'identifier' &&
|
|
3280
|
+
(!isIdentifier(tokens[index + 1], 'from') || tokens[index + 2]?.kind !== 'string') &&
|
|
3281
|
+
(!isIdentifier(tokens[index + 1], 'as') || isIdentifier(tokens[index + 2], 'from'))))) {
|
|
3282
|
+
index++;
|
|
3283
|
+
}
|
|
3284
|
+
if (tokens[index]?.kind === 'identifier') {
|
|
3285
|
+
index++;
|
|
3286
|
+
// Preserve the historical synthetic default-alias form supported by this scanner.
|
|
3287
|
+
if (isIdentifier(tokens[index], 'as') && tokens[index + 1]?.kind === 'identifier') {
|
|
3288
|
+
index += 2;
|
|
3289
|
+
}
|
|
3290
|
+
if (isPunct(tokens[index], ',')) {
|
|
3291
|
+
index++;
|
|
3292
|
+
}
|
|
3293
|
+
else {
|
|
3294
|
+
return importFromSourceIndex(tokens, index);
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
if (isPunct(tokens[index], '*')) {
|
|
3298
|
+
if (!isIdentifier(tokens[index + 1], 'as') || tokens[index + 2]?.kind !== 'identifier') {
|
|
3299
|
+
return undefined;
|
|
3300
|
+
}
|
|
3301
|
+
index += 3;
|
|
3302
|
+
}
|
|
3303
|
+
else if (isPunct(tokens[index], '{')) {
|
|
3304
|
+
index++;
|
|
3305
|
+
while (!isPunct(tokens[index], '}')) {
|
|
3306
|
+
// `type as T` imports the value `type`, but `type as` imports the type `as`.
|
|
3307
|
+
// A second `as` plus an alias distinguishes `type as as Alias` from `type as as`.
|
|
3308
|
+
if (isIdentifier(tokens[index], 'type') &&
|
|
3309
|
+
tokens[index + 1]?.kind === 'identifier' &&
|
|
3310
|
+
(!isIdentifier(tokens[index + 1], 'as') ||
|
|
3311
|
+
isPunct(tokens[index + 2], ',') ||
|
|
3312
|
+
isPunct(tokens[index + 2], '}') ||
|
|
3313
|
+
(isIdentifier(tokens[index + 2], 'as') && tokens[index + 3]?.kind === 'identifier'))) {
|
|
3314
|
+
index++;
|
|
522
3315
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
3316
|
+
const imported = tokens[index];
|
|
3317
|
+
if (imported?.kind !== 'identifier' && imported?.kind !== 'string') {
|
|
3318
|
+
return undefined;
|
|
526
3319
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (
|
|
535
|
-
|
|
3320
|
+
index++;
|
|
3321
|
+
if (isIdentifier(tokens[index], 'as')) {
|
|
3322
|
+
if (tokens[index + 1]?.kind !== 'identifier') {
|
|
3323
|
+
return undefined;
|
|
3324
|
+
}
|
|
3325
|
+
index += 2;
|
|
3326
|
+
}
|
|
3327
|
+
else if (imported.kind === 'string') {
|
|
3328
|
+
return undefined;
|
|
3329
|
+
}
|
|
3330
|
+
if (!isPunct(tokens[index], ',')) {
|
|
536
3331
|
break;
|
|
537
3332
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
3333
|
+
index++;
|
|
3334
|
+
}
|
|
3335
|
+
if (!isPunct(tokens[index], '}')) {
|
|
3336
|
+
return undefined;
|
|
3337
|
+
}
|
|
3338
|
+
index++;
|
|
3339
|
+
}
|
|
3340
|
+
else {
|
|
3341
|
+
return undefined;
|
|
3342
|
+
}
|
|
3343
|
+
return importFromSourceIndex(tokens, index);
|
|
3344
|
+
}
|
|
3345
|
+
function importFromSourceIndex(tokens, index) {
|
|
3346
|
+
return isIdentifier(tokens[index], 'from') && tokens[index + 1]?.kind === 'string'
|
|
3347
|
+
? index + 1
|
|
3348
|
+
: undefined;
|
|
3349
|
+
}
|
|
3350
|
+
function exportSourceIndex(tokens, exportIndex) {
|
|
3351
|
+
let index = exportIndex + 1;
|
|
3352
|
+
if (isIdentifier(tokens[index], 'type')) {
|
|
3353
|
+
index++;
|
|
3354
|
+
}
|
|
3355
|
+
if (isPunct(tokens[index], '*')) {
|
|
3356
|
+
index++;
|
|
3357
|
+
if (isIdentifier(tokens[index], 'as')) {
|
|
3358
|
+
index += 2;
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
else if (isPunct(tokens[index], '{')) {
|
|
3362
|
+
let depth = 1;
|
|
3363
|
+
index++;
|
|
3364
|
+
while (index < tokens.length && depth > 0) {
|
|
3365
|
+
if (isPunct(tokens[index], '{')) {
|
|
3366
|
+
depth++;
|
|
3367
|
+
}
|
|
3368
|
+
else if (isPunct(tokens[index], '}')) {
|
|
3369
|
+
depth--;
|
|
541
3370
|
}
|
|
3371
|
+
index++;
|
|
3372
|
+
}
|
|
3373
|
+
if (depth !== 0) {
|
|
3374
|
+
return undefined;
|
|
542
3375
|
}
|
|
543
|
-
j++;
|
|
544
3376
|
}
|
|
545
|
-
|
|
546
|
-
|
|
3377
|
+
else {
|
|
3378
|
+
return undefined;
|
|
3379
|
+
}
|
|
3380
|
+
return isIdentifier(tokens[index], 'from') && tokens[index + 1]?.kind === 'string'
|
|
3381
|
+
? index + 1
|
|
3382
|
+
: undefined;
|
|
547
3383
|
}
|
|
548
3384
|
/**
|
|
549
3385
|
* Handle a `require` identifier. `obj.require(...)` is ignored (preceded by `.`).
|
|
550
3386
|
* `require('pkg')` is `require` only when the sole argument is a static string.
|
|
551
3387
|
*/
|
|
552
3388
|
function handleRequire(tokens, i, occurrences) {
|
|
553
|
-
const prev = tokens[i - 1];
|
|
554
3389
|
// `obj.require(...)`: not the global CommonJS require.
|
|
555
|
-
if (
|
|
3390
|
+
if (isMemberName(tokens, i)) {
|
|
556
3391
|
return i + 1;
|
|
557
3392
|
}
|
|
558
3393
|
const next = tokens[i + 1];
|
|
559
|
-
//
|
|
3394
|
+
// A sole string argument, with an optional trailing comma, is static; options are not supported.
|
|
560
3395
|
if (next?.kind === 'punct' && next.value === '(') {
|
|
561
3396
|
const specifier = soleStringSpecifier(tokens, i + 2);
|
|
562
3397
|
if (specifier !== undefined) {
|
|
@@ -566,15 +3401,160 @@ function handleRequire(tokens, i, occurrences) {
|
|
|
566
3401
|
}
|
|
567
3402
|
return i + 1;
|
|
568
3403
|
}
|
|
3404
|
+
/** Inspect binding targets only, skipping property keys, types and initializer expressions. */
|
|
3405
|
+
function bindingHasRequire(tokens, start, end, closingDelimiter) {
|
|
3406
|
+
const targets = [{ start, end }];
|
|
3407
|
+
while (targets.length > 0) {
|
|
3408
|
+
const target = targets.pop();
|
|
3409
|
+
if (target === undefined) {
|
|
3410
|
+
break;
|
|
3411
|
+
}
|
|
3412
|
+
let index = bindingAfterRest(tokens, target.start);
|
|
3413
|
+
// Constructor parameter properties prefix the binding, rather than naming it.
|
|
3414
|
+
let modifier = tokens[index];
|
|
3415
|
+
while (index + 1 < target.end &&
|
|
3416
|
+
modifier?.kind === 'identifier' &&
|
|
3417
|
+
['public', 'private', 'protected', 'readonly', 'override'].includes(modifier.value)) {
|
|
3418
|
+
index++;
|
|
3419
|
+
modifier = tokens[index];
|
|
3420
|
+
}
|
|
3421
|
+
const token = tokens[index];
|
|
3422
|
+
if (token?.kind === 'identifier') {
|
|
3423
|
+
const next = tokens[index + 1];
|
|
3424
|
+
if (token.value === 'require' &&
|
|
3425
|
+
(index + 1 >= target.end ||
|
|
3426
|
+
isPunct(next, ',') ||
|
|
3427
|
+
isPunct(next, '?') ||
|
|
3428
|
+
(next?.kind === 'punct' && next.operator === 'assignment'))) {
|
|
3429
|
+
return true;
|
|
3430
|
+
}
|
|
3431
|
+
continue;
|
|
3432
|
+
}
|
|
3433
|
+
const object = isPunct(token, '{');
|
|
3434
|
+
if (!object && !isPunct(token, '[')) {
|
|
3435
|
+
continue;
|
|
3436
|
+
}
|
|
3437
|
+
const close = closingDelimiter(index);
|
|
3438
|
+
if (close === undefined || close >= target.end) {
|
|
3439
|
+
continue;
|
|
3440
|
+
}
|
|
3441
|
+
const frame = token?.parameterFrame;
|
|
3442
|
+
index++;
|
|
3443
|
+
while (index < close) {
|
|
3444
|
+
if (isPunct(tokens[index], ',')) {
|
|
3445
|
+
index++;
|
|
3446
|
+
continue;
|
|
3447
|
+
}
|
|
3448
|
+
index = bindingAfterRest(tokens, index);
|
|
3449
|
+
let binding = index;
|
|
3450
|
+
if (object) {
|
|
3451
|
+
const keyClose = isPunct(tokens[index], '[') ? closingDelimiter(index) : index;
|
|
3452
|
+
if (keyClose === undefined) {
|
|
3453
|
+
break;
|
|
3454
|
+
}
|
|
3455
|
+
if (isPunct(tokens[keyClose + 1], ':')) {
|
|
3456
|
+
binding = keyClose + 2;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
targets.push({ start: binding, end: close });
|
|
3460
|
+
index = bindingElementEnd(tokens, binding, close, frame, closingDelimiter);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
return false;
|
|
3464
|
+
}
|
|
3465
|
+
function bindingAfterRest(tokens, index) {
|
|
3466
|
+
return isPunct(tokens[index], '.') &&
|
|
3467
|
+
isPunct(tokens[index + 1], '.') &&
|
|
3468
|
+
isPunct(tokens[index + 2], '.')
|
|
3469
|
+
? index + 3
|
|
3470
|
+
: index;
|
|
3471
|
+
}
|
|
3472
|
+
function bindingElementEnd(tokens, start, end, frame, closingDelimiter) {
|
|
3473
|
+
let index = start;
|
|
3474
|
+
while (index < end) {
|
|
3475
|
+
const token = tokens[index];
|
|
3476
|
+
if (isPunct(token, ',') && token?.parameterFrame === frame && token?.parameterType !== true) {
|
|
3477
|
+
return index + 1;
|
|
3478
|
+
}
|
|
3479
|
+
if (isPunct(token, '(') || isPunct(token, '[') || isPunct(token, '{')) {
|
|
3480
|
+
const close = closingDelimiter(index);
|
|
3481
|
+
if (close !== undefined && close < end) {
|
|
3482
|
+
index = close + 1;
|
|
3483
|
+
continue;
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
index++;
|
|
3487
|
+
}
|
|
3488
|
+
return end;
|
|
3489
|
+
}
|
|
3490
|
+
/** Index balanced delimiters once, lazily, so nested option calls do not rescan suffixes. */
|
|
3491
|
+
function indexClosingDelimiters(tokens) {
|
|
3492
|
+
const closing = new Map();
|
|
3493
|
+
const stack = [];
|
|
3494
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
3495
|
+
const token = tokens[index];
|
|
3496
|
+
if (token?.kind !== 'punct') {
|
|
3497
|
+
continue;
|
|
3498
|
+
}
|
|
3499
|
+
if (token.value === '(' || token.value === '[' || token.value === '{') {
|
|
3500
|
+
stack.push(index);
|
|
3501
|
+
continue;
|
|
3502
|
+
}
|
|
3503
|
+
const opener = closerFor(token.value);
|
|
3504
|
+
if (opener === undefined) {
|
|
3505
|
+
continue;
|
|
3506
|
+
}
|
|
3507
|
+
const open = stack.pop();
|
|
3508
|
+
if (open === undefined || !isPunct(tokens[open], opener)) {
|
|
3509
|
+
// Do not pair an enclosing option object across a malformed nested delimiter.
|
|
3510
|
+
stack.length = 0;
|
|
3511
|
+
}
|
|
3512
|
+
else {
|
|
3513
|
+
closing.set(open, index);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
return closing;
|
|
3517
|
+
}
|
|
3518
|
+
/** The opener a closer matches, or undefined when the token is not a closer. */
|
|
3519
|
+
function closerFor(value) {
|
|
3520
|
+
if (value === ')') {
|
|
3521
|
+
return '(';
|
|
3522
|
+
}
|
|
3523
|
+
return value === ']' ? '[' : value === '}' ? '{' : undefined;
|
|
3524
|
+
}
|
|
3525
|
+
/**
|
|
3526
|
+
* Recognize the supported dynamic import arguments: a literal source plus either no further
|
|
3527
|
+
* argument or one balanced object-literal options expression. Option contents are never evaluated
|
|
3528
|
+
* and never contribute evidence. `require` keeps the stricter sole-literal rule.
|
|
3529
|
+
*/
|
|
3530
|
+
function dynamicImportSpecifier(tokens, argIndex, closingDelimiter) {
|
|
3531
|
+
const sole = soleStringSpecifier(tokens, argIndex);
|
|
3532
|
+
if (sole !== undefined) {
|
|
3533
|
+
return sole;
|
|
3534
|
+
}
|
|
3535
|
+
const specifier = tokens[argIndex];
|
|
3536
|
+
if (specifier?.kind !== 'string' ||
|
|
3537
|
+
!isPunct(tokens[argIndex + 1], ',') ||
|
|
3538
|
+
!isPunct(tokens[argIndex + 2], '{')) {
|
|
3539
|
+
return undefined;
|
|
3540
|
+
}
|
|
3541
|
+
const optionsClose = closingDelimiter(argIndex + 2);
|
|
3542
|
+
if (optionsClose === undefined) {
|
|
3543
|
+
return undefined;
|
|
3544
|
+
}
|
|
3545
|
+
const next = isPunct(tokens[optionsClose + 1], ',') ? optionsClose + 2 : optionsClose + 1;
|
|
3546
|
+
return isPunct(tokens[next], ')') ? specifier.value : undefined;
|
|
3547
|
+
}
|
|
569
3548
|
/**
|
|
570
3549
|
* The decoded string at `argIndex` when it is the only argument of the surrounding call, that is
|
|
571
|
-
* when it is a string
|
|
3550
|
+
* when it is a string followed by an optional trailing comma and `)`. Returns undefined otherwise,
|
|
572
3551
|
* so a dynamic argument (e.g. `'pkg-' + name`) or a call with extra arguments (e.g.
|
|
573
3552
|
* `require('pkg', ctx)`) never yields a static specifier.
|
|
574
3553
|
*/
|
|
575
3554
|
function soleStringSpecifier(tokens, argIndex) {
|
|
576
3555
|
const arg = tokens[argIndex];
|
|
577
|
-
const
|
|
3556
|
+
const closingIndex = isPunct(tokens[argIndex + 1], ',') ? argIndex + 2 : argIndex + 1;
|
|
3557
|
+
const closing = tokens[closingIndex];
|
|
578
3558
|
if (arg?.kind !== 'string') {
|
|
579
3559
|
return undefined;
|
|
580
3560
|
}
|
|
@@ -597,7 +3577,88 @@ function skipBlockComment(source, start) {
|
|
|
597
3577
|
}
|
|
598
3578
|
throw new ModuleSpecifierError('Unterminated block comment', 'invalid-source', start);
|
|
599
3579
|
}
|
|
3580
|
+
/** A dynamic import remains valid script syntax; only static forms and meta signal a module. */
|
|
3581
|
+
function importSignalsModule(source, end, atRoot) {
|
|
3582
|
+
const next = skipTrivia(source, end, source.length);
|
|
3583
|
+
if (source[next] === '.') {
|
|
3584
|
+
const meta = skipTrivia(source, next + 1, source.length);
|
|
3585
|
+
return ((isIdentifierStartAt(source, meta) || isUnicodeEscapeStart(source, meta)) &&
|
|
3586
|
+
readIdentifier(source, meta).value === 'meta');
|
|
3587
|
+
}
|
|
3588
|
+
return (atRoot &&
|
|
3589
|
+
(source[next] === "'" ||
|
|
3590
|
+
source[next] === '"' ||
|
|
3591
|
+
source[next] === '{' ||
|
|
3592
|
+
source[next] === '*' ||
|
|
3593
|
+
isIdentifierStartAt(source, next) ||
|
|
3594
|
+
isUnicodeEscapeStart(source, next)));
|
|
3595
|
+
}
|
|
3596
|
+
function exportSignalsModule(source, end) {
|
|
3597
|
+
const next = skipTrivia(source, end, source.length);
|
|
3598
|
+
return (source[next] === '{' ||
|
|
3599
|
+
source[next] === '*' ||
|
|
3600
|
+
(isIdentifierStartAt(source, next) &&
|
|
3601
|
+
['default', 'const', 'let', 'var', 'function', 'async', 'class'].includes(readIdentifier(source, next).value)));
|
|
3602
|
+
}
|
|
3603
|
+
/**
|
|
3604
|
+
* Probe top-level await without treating every identifier named await as a keyword. A complete
|
|
3605
|
+
* regex may be read opaquely during this pass even when its following operand makes it script
|
|
3606
|
+
* division. That protects apostrophes until a later static declaration settles the whole file.
|
|
3607
|
+
* Calls, indexing, tags, unary +/- and flag-like identifier divisors remain ambiguous script
|
|
3608
|
+
* expressions absent another signal.
|
|
3609
|
+
*/
|
|
3610
|
+
function probeAwaitOperand(source, end, inForHead) {
|
|
3611
|
+
const trivia = readTrivia(source, end, source.length);
|
|
3612
|
+
const next = trivia.end;
|
|
3613
|
+
const ch = source[next];
|
|
3614
|
+
if (ch === '/') {
|
|
3615
|
+
const finish = regexEnd(source, next);
|
|
3616
|
+
if (finish === undefined) {
|
|
3617
|
+
return { keyword: false, module: false };
|
|
3618
|
+
}
|
|
3619
|
+
const following = skipTrivia(source, finish, source.length);
|
|
3620
|
+
const tail = source[following];
|
|
3621
|
+
// Characters consumed as flags can instead be a complete identifier divisor (including a
|
|
3622
|
+
// valid flag such as g). Its following operator/terminator cannot establish module grammar.
|
|
3623
|
+
const hasIdentifierDivisor = source[finish - 1] !== '/';
|
|
3624
|
+
const comparisonKeyword = (isIdentifierStartAt(source, following) || isUnicodeEscapeStart(source, following)) &&
|
|
3625
|
+
['in', 'instanceof'].includes(readIdentifier(source, following).value);
|
|
3626
|
+
const unambiguous = !hasIdentifierDivisor &&
|
|
3627
|
+
(tail === undefined ||
|
|
3628
|
+
[';', ',', ')', ']', '}', '?'].includes(tail) ||
|
|
3629
|
+
(tail === '.' && !isNumberStart(source[following + 1] ?? '')) ||
|
|
3630
|
+
// These binary operators cannot start the missing right-hand divisor. Unary +/-,
|
|
3631
|
+
// calls, indexing, templates and decimal starts still admit script expressions.
|
|
3632
|
+
['<', '>', '*', '%', '&', '|', '^'].includes(tail) ||
|
|
3633
|
+
source.startsWith('==', following) ||
|
|
3634
|
+
source.startsWith('!=', following) ||
|
|
3635
|
+
comparisonKeyword);
|
|
3636
|
+
return { keyword: true, module: unambiguous };
|
|
3637
|
+
}
|
|
3638
|
+
// A newline also permits a bare await identifier statement followed by another statement.
|
|
3639
|
+
if (trivia.lineBreak) {
|
|
3640
|
+
return { keyword: false, module: false };
|
|
3641
|
+
}
|
|
3642
|
+
if (isIdentifierStartAt(source, next) || isUnicodeEscapeStart(source, next)) {
|
|
3643
|
+
const name = readIdentifier(source, next).value;
|
|
3644
|
+
const operand = !['in', 'instanceof'].includes(name) && !(name === 'of' && inForHead);
|
|
3645
|
+
return { keyword: operand, module: operand };
|
|
3646
|
+
}
|
|
3647
|
+
const operand = ch !== undefined &&
|
|
3648
|
+
(isNumberStart(ch) ||
|
|
3649
|
+
(ch === '.' && isNumberStart(source[next + 1] ?? '')) ||
|
|
3650
|
+
["'", '"', '{', '~'].includes(ch) ||
|
|
3651
|
+
(ch === '!' && source[next + 1] !== '='));
|
|
3652
|
+
return { keyword: operand, module: operand };
|
|
3653
|
+
}
|
|
600
3654
|
function skipRegex(source, start) {
|
|
3655
|
+
const end = regexEnd(source, start);
|
|
3656
|
+
if (end === undefined) {
|
|
3657
|
+
throw new ModuleSpecifierError('Unterminated regex literal', 'invalid-source', start);
|
|
3658
|
+
}
|
|
3659
|
+
return end;
|
|
3660
|
+
}
|
|
3661
|
+
function regexEnd(source, start) {
|
|
601
3662
|
const n = source.length;
|
|
602
3663
|
let i = start + 1;
|
|
603
3664
|
let inClass = false;
|
|
@@ -606,7 +3667,7 @@ function skipRegex(source, start) {
|
|
|
606
3667
|
// A regex literal cannot contain a raw line terminator. Rejecting it here stops a wrong regex
|
|
607
3668
|
// start from swallowing the following lines.
|
|
608
3669
|
if (ch === '\n' || ch === '\r' || ch === '\u2028' || ch === '\u2029') {
|
|
609
|
-
|
|
3670
|
+
return undefined;
|
|
610
3671
|
}
|
|
611
3672
|
if (ch === '\\') {
|
|
612
3673
|
i += 2;
|
|
@@ -629,7 +3690,7 @@ function skipRegex(source, start) {
|
|
|
629
3690
|
}
|
|
630
3691
|
i++;
|
|
631
3692
|
}
|
|
632
|
-
|
|
3693
|
+
return undefined;
|
|
633
3694
|
}
|
|
634
3695
|
function skipRegexFlags(source, i) {
|
|
635
3696
|
const n = source.length;
|
|
@@ -803,7 +3864,7 @@ function isWhitespace(ch) {
|
|
|
803
3864
|
ch === '\f' ||
|
|
804
3865
|
ch === '\uFEFF' ||
|
|
805
3866
|
isLineTerminator(ch) ||
|
|
806
|
-
SPACE_SEPARATOR.test(ch));
|
|
3867
|
+
(ch !== undefined && SPACE_SEPARATOR.test(ch)));
|
|
807
3868
|
}
|
|
808
3869
|
/**
|
|
809
3870
|
* The ECMAScript `LineTerminator` characters. Line comments and the hashbang end at any of them,
|
|
@@ -812,11 +3873,62 @@ function isWhitespace(ch) {
|
|
|
812
3873
|
function isLineTerminator(ch) {
|
|
813
3874
|
return ch === '\n' || ch === '\r' || ch === '\u2028' || ch === '\u2029';
|
|
814
3875
|
}
|
|
815
|
-
|
|
816
|
-
|
|
3876
|
+
const IDENTIFIER_START = /^[$_\p{ID_Start}]$/u;
|
|
3877
|
+
const IDENTIFIER_PART = /^[$_\u200C\u200D\p{ID_Continue}]$/u;
|
|
3878
|
+
function isIdentifierStartAt(source, index) {
|
|
3879
|
+
const codePoint = readCodePoint(source, index);
|
|
3880
|
+
return codePoint !== undefined && isIdentifierStartCharacter(codePoint.character);
|
|
3881
|
+
}
|
|
3882
|
+
function isIdentifierPartAt(source, index) {
|
|
3883
|
+
const codePoint = readCodePoint(source, index);
|
|
3884
|
+
return codePoint !== undefined && isIdentifierPartCharacter(codePoint.character);
|
|
3885
|
+
}
|
|
3886
|
+
function isIdentifierStartCharacter(character) {
|
|
3887
|
+
return IDENTIFIER_START.test(character);
|
|
3888
|
+
}
|
|
3889
|
+
function isIdentifierPartCharacter(character) {
|
|
3890
|
+
return IDENTIFIER_PART.test(character);
|
|
3891
|
+
}
|
|
3892
|
+
function codePointLength(source, index) {
|
|
3893
|
+
return (source.codePointAt(index) ?? 0) > 0xffff ? 2 : 1;
|
|
3894
|
+
}
|
|
3895
|
+
function readCodePoint(source, index) {
|
|
3896
|
+
const point = source.codePointAt(index);
|
|
3897
|
+
if (point === undefined) {
|
|
3898
|
+
return undefined;
|
|
3899
|
+
}
|
|
3900
|
+
const end = index + (point > 0xffff ? 2 : 1);
|
|
3901
|
+
return { character: String.fromCodePoint(point), end };
|
|
3902
|
+
}
|
|
3903
|
+
function isUnicodeEscapeStart(source, index) {
|
|
3904
|
+
return source[index] === '\\' && source[index + 1] === 'u';
|
|
817
3905
|
}
|
|
818
|
-
function
|
|
819
|
-
|
|
3906
|
+
function readUnicodeEscape(source, start) {
|
|
3907
|
+
let end = start + 2;
|
|
3908
|
+
let digits;
|
|
3909
|
+
if (source[end] === '{') {
|
|
3910
|
+
const digitsStart = ++end;
|
|
3911
|
+
while (end < source.length && /[0-9A-Fa-f]/.test(source[end] ?? '')) {
|
|
3912
|
+
end++;
|
|
3913
|
+
}
|
|
3914
|
+
if (end === digitsStart || source[end] !== '}' || end - digitsStart > 6) {
|
|
3915
|
+
throw new ModuleSpecifierError('Invalid Unicode escape in identifier', 'invalid-source', start);
|
|
3916
|
+
}
|
|
3917
|
+
digits = source.slice(digitsStart, end);
|
|
3918
|
+
end++;
|
|
3919
|
+
}
|
|
3920
|
+
else {
|
|
3921
|
+
digits = source.slice(end, end + 4);
|
|
3922
|
+
if (digits.length !== 4 || !/^[0-9A-Fa-f]{4}$/.test(digits)) {
|
|
3923
|
+
throw new ModuleSpecifierError('Invalid Unicode escape in identifier', 'invalid-source', start);
|
|
3924
|
+
}
|
|
3925
|
+
end += 4;
|
|
3926
|
+
}
|
|
3927
|
+
const value = Number.parseInt(digits, 16);
|
|
3928
|
+
if (value > 0x10ffff) {
|
|
3929
|
+
throw new ModuleSpecifierError('Invalid Unicode escape in identifier', 'invalid-source', start);
|
|
3930
|
+
}
|
|
3931
|
+
return { character: String.fromCodePoint(value), end };
|
|
820
3932
|
}
|
|
821
3933
|
function isNumberStart(ch) {
|
|
822
3934
|
return /[0-9]/.test(ch);
|