es-check 9.6.4 → 9.7.2
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 +4 -1
- package/lib/browserslist.js +23 -13
- package/lib/cache.js +2 -1
- package/lib/check-runner/index.js +13 -31
- package/lib/check-runner/utils.js +80 -121
- package/lib/cli/constants.js +4 -7
- package/lib/cli/handler.js +66 -76
- package/lib/cli/parser.js +16 -12
- package/lib/cli/utils.js +21 -25
- package/lib/constants/es-features/15.js +17 -1
- package/lib/constants/es-features/16.js +34 -0
- package/lib/constants/es-features/17.js +90 -11
- package/lib/constants/es-features/9.js +2 -1
- package/lib/constants/es-features/globals.js +71 -0
- package/lib/constants/es-features/index.js +19 -19
- package/lib/constants/es-features/polyfills.js +15 -43
- package/lib/constants/featureParserMapping.js +2 -7
- package/lib/constants/index.js +59 -1
- package/lib/constants/polyfillableFeatures.js +12 -0
- package/lib/constants/versions.js +1 -1
- package/lib/detectFeatures.js +54 -50
- package/lib/helpers/ast.js +66 -154
- package/lib/helpers/astDetector.js +1647 -68
- package/lib/helpers/files.js +9 -5
- package/lib/helpers/index.js +7 -18
- package/lib/helpers/logger.js +3 -7
- package/lib/helpers/parsers.js +25 -20
- package/lib/helpers/sourcemap.js +40 -39
- package/package.json +116 -198
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
const { ES_FEATURES } = require("../constants");
|
|
2
|
-
const {
|
|
1
|
+
const { ES_FEATURES, MAP_CONSTRUCTORS } = require("../constants");
|
|
2
|
+
const { ES_GLOBAL_MIN_VERSION } = require("../constants/es-features/globals");
|
|
3
|
+
const { checkMap, getStaticPropertyName } = require("./ast");
|
|
3
4
|
|
|
4
5
|
const FUNCTION_TYPES = new Set([
|
|
5
6
|
"FunctionDeclaration",
|
|
@@ -8,6 +9,53 @@ const FUNCTION_TYPES = new Set([
|
|
|
8
9
|
"MethodDefinition",
|
|
9
10
|
]);
|
|
10
11
|
|
|
12
|
+
const FUNCTION_SCOPE_TYPES = new Set([
|
|
13
|
+
"FunctionDeclaration",
|
|
14
|
+
"FunctionExpression",
|
|
15
|
+
"ArrowFunctionExpression",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const GLOBAL_BUILTIN_NAMES = new Set(Object.keys(ES_GLOBAL_MIN_VERSION));
|
|
19
|
+
|
|
20
|
+
const DECLARATION_ID_PARENT_TYPES = new Set([
|
|
21
|
+
"ClassDeclaration",
|
|
22
|
+
"ClassExpression",
|
|
23
|
+
"FunctionDeclaration",
|
|
24
|
+
"FunctionExpression",
|
|
25
|
+
"VariableDeclarator",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const IMPORT_LOCAL_PARENT_TYPES = new Set([
|
|
29
|
+
"ImportDefaultSpecifier",
|
|
30
|
+
"ImportNamespaceSpecifier",
|
|
31
|
+
"ImportSpecifier",
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const PROPERTY_KEY_PARENT_TYPES = new Set(["MethodDefinition", "Property", "PropertyDefinition"]);
|
|
35
|
+
|
|
36
|
+
const LABEL_PARENT_TYPES = new Set(["BreakStatement", "ContinueStatement", "LabeledStatement"]);
|
|
37
|
+
|
|
38
|
+
const RECEIVER_EXIT_TYPES = new Set([
|
|
39
|
+
"ReturnStatement",
|
|
40
|
+
"ThrowStatement",
|
|
41
|
+
"BreakStatement",
|
|
42
|
+
"ContinueStatement",
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const POSSIBLY_THROWING_TYPES = new Set([
|
|
46
|
+
"CallExpression",
|
|
47
|
+
"NewExpression",
|
|
48
|
+
"MemberExpression",
|
|
49
|
+
"AwaitExpression",
|
|
50
|
+
"TaggedTemplateExpression",
|
|
51
|
+
"BinaryExpression",
|
|
52
|
+
"UnaryExpression",
|
|
53
|
+
"AssignmentExpression",
|
|
54
|
+
"UpdateExpression",
|
|
55
|
+
"SpreadElement",
|
|
56
|
+
"YieldExpression",
|
|
57
|
+
]);
|
|
58
|
+
|
|
11
59
|
const CHILD_KEYS = [
|
|
12
60
|
"body",
|
|
13
61
|
"declarations",
|
|
@@ -15,8 +63,8 @@ const CHILD_KEYS = [
|
|
|
15
63
|
"left",
|
|
16
64
|
"right",
|
|
17
65
|
"argument",
|
|
18
|
-
"arguments",
|
|
19
66
|
"callee",
|
|
67
|
+
"arguments",
|
|
20
68
|
"object",
|
|
21
69
|
"property",
|
|
22
70
|
"properties",
|
|
@@ -42,15 +90,13 @@ const CHILD_KEYS = [
|
|
|
42
90
|
"value",
|
|
43
91
|
"superClass",
|
|
44
92
|
"expressions",
|
|
93
|
+
"tag",
|
|
94
|
+
"quasi",
|
|
45
95
|
];
|
|
46
96
|
|
|
47
97
|
function normalizeNodeType(nodeType) {
|
|
48
98
|
if (nodeType === "ExportDeclaration") {
|
|
49
|
-
return [
|
|
50
|
-
"ExportNamedDeclaration",
|
|
51
|
-
"ExportDefaultDeclaration",
|
|
52
|
-
"ExportAllDeclaration",
|
|
53
|
-
];
|
|
99
|
+
return ["ExportNamedDeclaration", "ExportDefaultDeclaration", "ExportAllDeclaration"];
|
|
54
100
|
}
|
|
55
101
|
if (nodeType === "BigIntLiteral") {
|
|
56
102
|
return ["Literal"];
|
|
@@ -59,41 +105,563 @@ function normalizeNodeType(nodeType) {
|
|
|
59
105
|
}
|
|
60
106
|
|
|
61
107
|
function buildFeatureIndex(features) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
108
|
+
return Object.entries(features).reduce((index, [name, { astInfo }]) => {
|
|
109
|
+
const hasNodeType = Boolean(astInfo?.nodeType);
|
|
110
|
+
if (!hasNodeType) return index;
|
|
111
|
+
return addFeatureIndexEntries(index, name, astInfo);
|
|
112
|
+
}, {});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const featuresByNodeType = buildFeatureIndex(ES_FEATURES);
|
|
116
|
+
|
|
117
|
+
function createScope(parent = null) {
|
|
118
|
+
return {
|
|
119
|
+
parent,
|
|
120
|
+
names: new Set(),
|
|
121
|
+
mapReceivers: new Set(),
|
|
122
|
+
ownsThis: false,
|
|
123
|
+
isFunction: false,
|
|
124
|
+
receiverExits: null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function cloneScope(scope) {
|
|
129
|
+
if (!scope) return null;
|
|
130
|
+
const parent = cloneScope(scope.parent);
|
|
131
|
+
const mapReceivers = new Set(scope.mapReceivers);
|
|
132
|
+
return Object.assign({}, scope, { parent, mapReceivers });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function createThisScope(parent) {
|
|
136
|
+
const scope = createScope(parent);
|
|
137
|
+
scope.ownsThis = true;
|
|
138
|
+
return scope;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function mergeReceiverScopes(scope, branches) {
|
|
142
|
+
if (!scope) return;
|
|
143
|
+
if (branches.length === 0) return;
|
|
144
|
+
const counts = new Map();
|
|
145
|
+
visitArray(branches, (branch) => countMapReceivers(branch, counts));
|
|
146
|
+
const mapReceivers = new Set();
|
|
147
|
+
counts.forEach((count, key) => {
|
|
148
|
+
if (count === branches.length) mapReceivers.add(key);
|
|
149
|
+
});
|
|
150
|
+
scope.mapReceivers = mapReceivers;
|
|
151
|
+
const parents = branches.map((branch) => branch.parent);
|
|
152
|
+
mergeReceiverScopes(scope.parent, parents);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function countMapReceivers(scope, counts) {
|
|
156
|
+
scope.mapReceivers.forEach((key) => {
|
|
157
|
+
const count = counts.get(key) || 0;
|
|
158
|
+
counts.set(key, count + 1);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function visitArray(items, visitor) {
|
|
163
|
+
if (!Array.isArray(items)) return;
|
|
164
|
+
|
|
165
|
+
let index = 0;
|
|
166
|
+
while (index < items.length) {
|
|
167
|
+
visitor(items[index], index);
|
|
168
|
+
index += 1;
|
|
69
169
|
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function visitChildNodes(node, visitor) {
|
|
173
|
+
visitArray(CHILD_KEYS, (key) => {
|
|
174
|
+
const child = node[key];
|
|
175
|
+
if (!child) return;
|
|
176
|
+
|
|
177
|
+
if (Array.isArray(child)) {
|
|
178
|
+
visitArray(child, (item) => {
|
|
179
|
+
if (item?.type) visitor(item);
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (child.type) visitor(child);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function addFeatureIndexEntries(index, name, astInfo) {
|
|
189
|
+
const entry = { name, astInfo };
|
|
190
|
+
const types = normalizeNodeType(astInfo.nodeType);
|
|
191
|
+
|
|
192
|
+
visitArray(types, (type) => {
|
|
193
|
+
const existing = index[type] || [];
|
|
194
|
+
index[type] = existing.concat(entry);
|
|
195
|
+
});
|
|
196
|
+
|
|
70
197
|
return index;
|
|
71
198
|
}
|
|
72
199
|
|
|
73
|
-
|
|
200
|
+
function isDeclared(scope, name) {
|
|
201
|
+
let current = scope;
|
|
202
|
+
while (current) {
|
|
203
|
+
if (current.names.has(name)) return true;
|
|
204
|
+
current = current.parent;
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function addPatternNames(pattern, names) {
|
|
210
|
+
if (!pattern) return;
|
|
211
|
+
|
|
212
|
+
if (pattern.type === "Identifier") {
|
|
213
|
+
names.add(pattern.name);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (pattern.type === "RestElement") {
|
|
218
|
+
addPatternNames(pattern.argument, names);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (pattern.type === "AssignmentPattern") {
|
|
223
|
+
addPatternNames(pattern.left, names);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (pattern.type === "ArrayPattern") {
|
|
228
|
+
visitArray(pattern.elements, (element) => {
|
|
229
|
+
addPatternNames(element, names);
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (pattern.type === "ObjectPattern") {
|
|
235
|
+
visitArray(pattern.properties, (property) => {
|
|
236
|
+
if (property.type === "RestElement") {
|
|
237
|
+
addPatternNames(property.argument, names);
|
|
238
|
+
} else {
|
|
239
|
+
addPatternNames(property.value, names);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function addVariableNames(node, names) {
|
|
246
|
+
visitArray(node.declarations, (declaration) => {
|
|
247
|
+
addPatternNames(declaration.id, names);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function addImportNames(node, names) {
|
|
252
|
+
visitArray(node.specifiers, (specifier) => {
|
|
253
|
+
if (specifier.local?.name) names.add(specifier.local.name);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function collectDirectLexicalDeclarations(statements, names) {
|
|
258
|
+
visitArray(statements, (statement) => {
|
|
259
|
+
if (statement.type === "ImportDeclaration") {
|
|
260
|
+
addImportNames(statement, names);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (statement.type === "VariableDeclaration") {
|
|
265
|
+
if (statement.kind !== "var") addVariableNames(statement, names);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const isNamedFunction = statement.type === "FunctionDeclaration";
|
|
270
|
+
const isNamedClass = statement.type === "ClassDeclaration";
|
|
271
|
+
const hasName = Boolean(statement.id?.name);
|
|
272
|
+
const shouldAddName = (isNamedFunction || isNamedClass) && hasName;
|
|
273
|
+
if (shouldAddName) {
|
|
274
|
+
names.add(statement.id.name);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function isNodeRecord(node) {
|
|
280
|
+
const isObjectLike = Boolean(node);
|
|
281
|
+
if (!isObjectLike) return false;
|
|
282
|
+
const nodeType = typeof node;
|
|
283
|
+
return nodeType === "object";
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function collectHoistedDeclarations(node, names) {
|
|
287
|
+
const isNotNode = !isNodeRecord(node);
|
|
288
|
+
if (isNotNode) return;
|
|
289
|
+
|
|
290
|
+
const isVarDeclaration = node.type === "VariableDeclaration";
|
|
291
|
+
const isVarKind = node.kind === "var";
|
|
292
|
+
const shouldAddVarNames = isVarDeclaration && isVarKind;
|
|
293
|
+
if (shouldAddVarNames) {
|
|
294
|
+
addVariableNames(node, names);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const isFunctionDeclaration = node.type === "FunctionDeclaration";
|
|
299
|
+
const hasFunctionName = Boolean(node.id?.name);
|
|
300
|
+
const shouldAddFunctionName = isFunctionDeclaration && hasFunctionName;
|
|
301
|
+
if (shouldAddFunctionName) {
|
|
302
|
+
names.add(node.id.name);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (FUNCTION_SCOPE_TYPES.has(node.type)) return;
|
|
307
|
+
|
|
308
|
+
visitChildNodes(node, (child) => {
|
|
309
|
+
collectHoistedDeclarations(child, names);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function createProgramScope(ast) {
|
|
314
|
+
const scope = createThisScope(null);
|
|
315
|
+
collectDirectLexicalDeclarations(ast.body, scope.names);
|
|
316
|
+
collectHoistedDeclarations(ast, scope.names);
|
|
317
|
+
return scope;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function addAssignedGlobalName(expression, names) {
|
|
321
|
+
if (expression?.type === "SequenceExpression") {
|
|
322
|
+
visitArray(expression.expressions, (item) => {
|
|
323
|
+
addAssignedGlobalName(item, names);
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const isAssignment = expression?.type === "AssignmentExpression" && expression.operator === "=";
|
|
329
|
+
if (!isAssignment) return;
|
|
330
|
+
|
|
331
|
+
addGlobalPatternNames(expression.left, names);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function addGlobalPatternNames(pattern, names) {
|
|
335
|
+
const targetNames = new Set();
|
|
336
|
+
addPatternNames(pattern, targetNames);
|
|
337
|
+
|
|
338
|
+
targetNames.forEach((targetName) => {
|
|
339
|
+
if (GLOBAL_BUILTIN_NAMES.has(targetName)) names.add(targetName);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function createFunctionScope(node, parentScope) {
|
|
344
|
+
const scope = createScope(parentScope);
|
|
345
|
+
|
|
346
|
+
const isNamedFunctionExpression = node.type === "FunctionExpression";
|
|
347
|
+
const hasName = Boolean(node.id?.name);
|
|
348
|
+
const shouldAddName = isNamedFunctionExpression && hasName;
|
|
349
|
+
if (shouldAddName) {
|
|
350
|
+
scope.names.add(node.id.name);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
visitArray(node.params, (param) => {
|
|
354
|
+
addPatternNames(param, scope.names);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
collectHoistedDeclarations(node.body, scope.names);
|
|
358
|
+
return scope;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function createBlockScope(node, parentScope) {
|
|
362
|
+
const scope = createScope(parentScope);
|
|
363
|
+
collectDirectLexicalDeclarations(node.body, scope.names);
|
|
364
|
+
return scope;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function createCatchScope(node, parentScope) {
|
|
368
|
+
const scope = createScope(parentScope);
|
|
369
|
+
addPatternNames(node.param, scope.names);
|
|
370
|
+
return scope;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function createClassBodyScope(parentScope, classNode) {
|
|
374
|
+
const scope = createScope(parentScope);
|
|
375
|
+
if (classNode.id?.name) scope.names.add(classNode.id.name);
|
|
376
|
+
return scope;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function createLoopScope(node, parentScope) {
|
|
380
|
+
const scope = createScope(parentScope);
|
|
381
|
+
const declaration = node.init || node.left;
|
|
382
|
+
const isLexicalDeclaration =
|
|
383
|
+
declaration?.type === "VariableDeclaration" && declaration.kind !== "var";
|
|
384
|
+
if (isLexicalDeclaration) addVariableNames(declaration, scope.names);
|
|
385
|
+
return scope;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function createSwitchScope(node, parentScope) {
|
|
389
|
+
const scope = createScope(parentScope);
|
|
390
|
+
visitArray(node.cases, (switchCase) => {
|
|
391
|
+
collectDirectLexicalDeclarations(switchCase.consequent, scope.names);
|
|
392
|
+
});
|
|
393
|
+
return scope;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function isIdentifierReference(node, parent) {
|
|
397
|
+
if (!parent) return true;
|
|
398
|
+
|
|
399
|
+
if (isDeclarationIdentifier(node, parent)) return false;
|
|
400
|
+
if (isImportLocalIdentifier(node, parent)) return false;
|
|
401
|
+
if (parent.type === "MemberExpression") return isMemberExpressionReference(node, parent);
|
|
402
|
+
if (isPropertyKeyIdentifier(node, parent)) return false;
|
|
403
|
+
if (isLabelIdentifier(node, parent)) return false;
|
|
404
|
+
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function isDeclarationIdentifier(node, parent) {
|
|
409
|
+
const parentType = String(parent.type);
|
|
410
|
+
const hasDeclarationParent = DECLARATION_ID_PARENT_TYPES.has(parentType);
|
|
411
|
+
const isIdentifier = parent.id === node;
|
|
412
|
+
return hasDeclarationParent && isIdentifier;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function isImportLocalIdentifier(node, parent) {
|
|
416
|
+
const parentType = String(parent.type);
|
|
417
|
+
const hasImportParent = IMPORT_LOCAL_PARENT_TYPES.has(parentType);
|
|
418
|
+
const isLocal = parent.local === node;
|
|
419
|
+
const isImported = parent.imported === node;
|
|
420
|
+
const isImportName = isLocal || isImported;
|
|
421
|
+
return hasImportParent && isImportName;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function isMemberExpressionReference(node, parent) {
|
|
425
|
+
const isObject = parent.object === node;
|
|
426
|
+
if (isObject) return true;
|
|
427
|
+
return Boolean(parent.computed);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function isPropertyKeyIdentifier(node, parent) {
|
|
431
|
+
const parentType = String(parent.type);
|
|
432
|
+
const hasPropertyParent = PROPERTY_KEY_PARENT_TYPES.has(parentType);
|
|
433
|
+
if (!hasPropertyParent) return false;
|
|
434
|
+
|
|
435
|
+
const isKey = parent.key === node;
|
|
436
|
+
if (!isKey) return false;
|
|
437
|
+
if (parent.computed) return false;
|
|
438
|
+
|
|
439
|
+
const isProperty = parent.type === "Property";
|
|
440
|
+
const isShorthand = Boolean(parent.shorthand);
|
|
441
|
+
const isValue = parent.value === node;
|
|
442
|
+
const isShorthandValue = isProperty && isShorthand && isValue;
|
|
443
|
+
return !isShorthandValue;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function isLabelIdentifier(node, parent) {
|
|
447
|
+
const parentType = String(parent.type);
|
|
448
|
+
const hasLabelParent = LABEL_PARENT_TYPES.has(parentType);
|
|
449
|
+
const isLabel = parent.label === node;
|
|
450
|
+
return hasLabelParent && isLabel;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function getGlobalReferenceName(node, astInfo) {
|
|
454
|
+
if (astInfo.name) {
|
|
455
|
+
return getMatchingIdentifierName(node, astInfo.name);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const callee = node.callee;
|
|
459
|
+
const calleeObject = callee?.object;
|
|
460
|
+
const calleeProperty = callee?.property;
|
|
461
|
+
|
|
462
|
+
if (astInfo.callee) {
|
|
463
|
+
return getMatchingIdentifierName(callee, astInfo.callee);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (!astInfo.object) return null;
|
|
467
|
+
|
|
468
|
+
const matchingObjectName = getMatchingIdentifierName(calleeObject, astInfo.object);
|
|
469
|
+
if (matchingObjectName) return matchingObjectName;
|
|
470
|
+
|
|
471
|
+
const matchingCalleeName = getMatchingIdentifierName(callee, astInfo.object);
|
|
472
|
+
if (matchingCalleeName) return matchingCalleeName;
|
|
473
|
+
|
|
474
|
+
const nestedObjectName = getNestedObjectReferenceName(calleeObject, astInfo.object);
|
|
475
|
+
if (nestedObjectName) return nestedObjectName;
|
|
476
|
+
|
|
477
|
+
const hasMatchingProperty = calleeProperty?.name === astInfo.property;
|
|
478
|
+
if (!hasMatchingProperty) return null;
|
|
479
|
+
|
|
480
|
+
return getMatchingIdentifierName(calleeObject, astInfo.object);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function getMatchingIdentifierName(node, expectedName) {
|
|
484
|
+
const isIdentifier = node?.type === "Identifier";
|
|
485
|
+
if (!isIdentifier) return null;
|
|
486
|
+
|
|
487
|
+
const hasExpectedName = node.name === expectedName;
|
|
488
|
+
if (!hasExpectedName) return null;
|
|
489
|
+
|
|
490
|
+
return expectedName;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function getNestedObjectReferenceName(node, expectedName) {
|
|
494
|
+
const isMemberExpression = node?.type === "MemberExpression";
|
|
495
|
+
if (!isMemberExpression) return null;
|
|
496
|
+
|
|
497
|
+
const object = node.object;
|
|
498
|
+
return getMatchingIdentifierName(object, expectedName);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function isShadowedGlobalReference(node, astInfo, context) {
|
|
502
|
+
const name = getGlobalReferenceName(node, astInfo);
|
|
503
|
+
const hasName = Boolean(name);
|
|
504
|
+
if (!hasName) return false;
|
|
505
|
+
|
|
506
|
+
const isBuiltin = GLOBAL_BUILTIN_NAMES.has(name);
|
|
507
|
+
if (!isBuiltin) return false;
|
|
508
|
+
|
|
509
|
+
return isDeclared(context.scope, name);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function isNamedIdentifierMatch(node, astInfo, context) {
|
|
513
|
+
const hasName = Boolean(astInfo.name);
|
|
514
|
+
const isIdentifier = node.type === "Identifier";
|
|
515
|
+
const shouldCheckIdentifier = hasName && isIdentifier;
|
|
516
|
+
if (!shouldCheckIdentifier) return false;
|
|
517
|
+
|
|
518
|
+
if (!isIdentifierReference(node, context.parent)) return false;
|
|
519
|
+
if (isShadowedGlobalReference(node, astInfo, context)) return false;
|
|
520
|
+
|
|
521
|
+
return node.name === astInfo.name;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function hasOperatorMismatch(node, astInfo) {
|
|
525
|
+
const expectsOperator = Boolean(astInfo.operator);
|
|
526
|
+
const hasOperatorMismatch = expectsOperator && node.operator !== astInfo.operator;
|
|
527
|
+
if (hasOperatorMismatch) return true;
|
|
528
|
+
|
|
529
|
+
const expectsOperators = Boolean(astInfo.operators);
|
|
530
|
+
if (!expectsOperators) return false;
|
|
531
|
+
|
|
532
|
+
const hasAllowedOperator = astInfo.operators.includes(node.operator);
|
|
533
|
+
return !hasAllowedOperator;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function isTopLevelAwaitMatch(node, astInfo, context) {
|
|
537
|
+
const isTopLevelFeature = Boolean(astInfo.topLevel);
|
|
538
|
+
const isAwaitExpression = node.type === "AwaitExpression";
|
|
539
|
+
const shouldCheckTopLevel = isTopLevelFeature && isAwaitExpression;
|
|
540
|
+
if (!shouldCheckTopLevel) return false;
|
|
541
|
+
|
|
542
|
+
return context.isTopLevel === true;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function isPrivateBrandCheckMatch(node, astInfo) {
|
|
546
|
+
const checksPrivateLeft = Boolean(astInfo.leftIsPrivate);
|
|
547
|
+
const isBinaryExpression = node.type === "BinaryExpression";
|
|
548
|
+
const shouldCheckPrivateLeft = checksPrivateLeft && isBinaryExpression;
|
|
549
|
+
if (!shouldCheckPrivateLeft) return false;
|
|
550
|
+
|
|
551
|
+
return node.left?.type === "PrivateIdentifier";
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function isOptionalCatchBindingMatch(node, astInfo) {
|
|
555
|
+
const checksNoParam = Boolean(astInfo.noParam);
|
|
556
|
+
if (!checksNoParam) return false;
|
|
557
|
+
|
|
558
|
+
const isCatchClause = node.type === "CatchClause";
|
|
559
|
+
if (!isCatchClause) return false;
|
|
560
|
+
|
|
561
|
+
const hasNoParam = node.param == null;
|
|
562
|
+
return hasNoParam;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function isCallableNode(node) {
|
|
566
|
+
const isCallExpression = node.type === "CallExpression";
|
|
567
|
+
const isNewExpression = node.type === "NewExpression";
|
|
568
|
+
return isCallExpression || isNewExpression;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function isMapConstructorExpression(node, context) {
|
|
572
|
+
const isNewExpression = node?.type === "NewExpression";
|
|
573
|
+
const constructorName = node?.callee?.name;
|
|
574
|
+
const isMapConstructor = MAP_CONSTRUCTORS.has(constructorName);
|
|
575
|
+
const isMapConstructorCall = isNewExpression && isMapConstructor;
|
|
576
|
+
if (!isMapConstructorCall) return false;
|
|
577
|
+
|
|
578
|
+
return !isDeclared(context.scope, constructorName);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function getStaticReferenceKey(node) {
|
|
582
|
+
if (!node) return null;
|
|
583
|
+
|
|
584
|
+
if (node.type === "Identifier") return node.name;
|
|
585
|
+
if (node.type === "ThisExpression") return "this";
|
|
586
|
+
if (node.type !== "MemberExpression") return null;
|
|
587
|
+
const objectKey = getStaticReferenceKey(node.object);
|
|
588
|
+
const propertyName = getStaticPropertyKey(node, node.property);
|
|
589
|
+
const hasStaticMemberPath = Boolean(objectKey && propertyName);
|
|
590
|
+
if (!hasStaticMemberPath) return null;
|
|
591
|
+
|
|
592
|
+
return `${objectKey}.${propertyName}`;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function getStaticPropertyKey(node, property) {
|
|
596
|
+
if (property?.type === "PrivateIdentifier") return `#${property.name}`;
|
|
597
|
+
const name = getStaticPropertyName(node, property);
|
|
598
|
+
if (name == null) return null;
|
|
599
|
+
return JSON.stringify(name);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function getReceiverRootName(key) {
|
|
603
|
+
const rootName = key.split(".")[0];
|
|
604
|
+
if (rootName === "this") return null;
|
|
605
|
+
return rootName;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function isKnownMapReceiver(key, context) {
|
|
609
|
+
const rootName = getReceiverRootName(key);
|
|
610
|
+
let scope = context.scope;
|
|
611
|
+
|
|
612
|
+
while (scope) {
|
|
613
|
+
if (scope.mapReceivers.has(key)) return true;
|
|
614
|
+
const hasShadowingRoot = rootName && scope.names.has(rootName);
|
|
615
|
+
if (hasShadowingRoot) return false;
|
|
616
|
+
const hasThisBoundary = !rootName && scope.ownsThis;
|
|
617
|
+
if (hasThisBoundary) return false;
|
|
618
|
+
scope = scope.parent;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function isRequiredMapReceiverMatch(node, astInfo, context) {
|
|
625
|
+
if (!astInfo.requireMapReceiver) return true;
|
|
626
|
+
|
|
627
|
+
const receiver = node.callee?.object;
|
|
628
|
+
if (isMapConstructorExpression(receiver, context)) return true;
|
|
629
|
+
|
|
630
|
+
const receiverKey = getStaticReferenceKey(receiver);
|
|
631
|
+
if (!receiverKey) return false;
|
|
632
|
+
|
|
633
|
+
return isKnownMapReceiver(receiverKey, context);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function isCallableFeatureMatch(node, astInfo, context) {
|
|
637
|
+
const hasMatch = checkMap(node, astInfo);
|
|
638
|
+
if (!hasMatch) return false;
|
|
639
|
+
if (!isRequiredMapReceiverMatch(node, astInfo, context)) return false;
|
|
640
|
+
if (isShadowedGlobalReference(node, astInfo, context)) return false;
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
74
643
|
|
|
75
644
|
function matchesFeature(node, astInfo, context = {}) {
|
|
76
645
|
if (astInfo.childType) {
|
|
77
|
-
|
|
646
|
+
const children = node.elements || node.properties;
|
|
647
|
+
return children?.some((child) => child?.type === astInfo.childType) || false;
|
|
78
648
|
}
|
|
79
649
|
|
|
80
|
-
if (astInfo.
|
|
81
|
-
|
|
82
|
-
|
|
650
|
+
if (astInfo.await) return node.await === true;
|
|
651
|
+
if (astInfo.flags) return node.regex?.flags.includes(astInfo.flags) || false;
|
|
652
|
+
|
|
653
|
+
if (astInfo.name) return isNamedIdentifierMatch(node, astInfo, context);
|
|
83
654
|
|
|
84
655
|
if (astInfo.nodeType === "BigIntLiteral") {
|
|
85
656
|
return node.bigint !== undefined;
|
|
86
657
|
}
|
|
87
658
|
|
|
88
|
-
|
|
659
|
+
const hasWrongKind = astInfo.kind && node.kind !== astInfo.kind;
|
|
660
|
+
if (hasWrongKind) {
|
|
89
661
|
return false;
|
|
90
662
|
}
|
|
91
663
|
|
|
92
|
-
|
|
93
|
-
(astInfo.operator && node.operator !== astInfo.operator) ||
|
|
94
|
-
(astInfo.operators && !astInfo.operators.includes(node.operator));
|
|
95
|
-
|
|
96
|
-
if (hasOperatorMismatch) {
|
|
664
|
+
if (hasOperatorMismatch(node, astInfo)) {
|
|
97
665
|
return false;
|
|
98
666
|
}
|
|
99
667
|
|
|
@@ -101,66 +669,1075 @@ function matchesFeature(node, astInfo, context = {}) {
|
|
|
101
669
|
return node.superClass !== null;
|
|
102
670
|
}
|
|
103
671
|
|
|
104
|
-
if (astInfo.topLevel
|
|
105
|
-
return
|
|
672
|
+
if (astInfo.topLevel) {
|
|
673
|
+
return isTopLevelAwaitMatch(node, astInfo, context);
|
|
106
674
|
}
|
|
107
675
|
|
|
108
|
-
if (astInfo.leftIsPrivate
|
|
109
|
-
return node
|
|
676
|
+
if (astInfo.leftIsPrivate) {
|
|
677
|
+
return isPrivateBrandCheckMatch(node, astInfo);
|
|
110
678
|
}
|
|
111
679
|
|
|
112
680
|
if (astInfo.noParam) {
|
|
113
|
-
return node
|
|
681
|
+
return isOptionalCatchBindingMatch(node, astInfo);
|
|
114
682
|
}
|
|
115
683
|
|
|
116
|
-
if (node
|
|
117
|
-
return
|
|
684
|
+
if (isCallableNode(node)) {
|
|
685
|
+
return isCallableFeatureMatch(node, astInfo, context);
|
|
118
686
|
}
|
|
119
687
|
|
|
120
688
|
return true;
|
|
121
689
|
}
|
|
122
690
|
|
|
123
|
-
function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
691
|
+
function traverseFeatures(node, context, state) {
|
|
692
|
+
if (!isNodeRecord(node)) return;
|
|
693
|
+
recordTopLevelAssignment(node, context);
|
|
694
|
+
maybeRecordGlobalReference(node, context, state);
|
|
695
|
+
recordFeatureMatches(node, context, state);
|
|
696
|
+
const isFunction = FUNCTION_TYPES.has(node.type);
|
|
697
|
+
const newFunctionDepth = isFunction ? context.functionDepth + 1 : context.functionDepth;
|
|
698
|
+
const traversal = getScopedTraversal(node) || getFlowTraversal(node) || traverseChildFeatures;
|
|
699
|
+
return traversal(node, context, state, newFunctionDepth);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function recordFeatureMatches(node, context, state) {
|
|
703
|
+
const candidates = featuresByNodeType[node.type];
|
|
704
|
+
const matchContext = Object.assign({}, context, { isTopLevel: context.functionDepth === 0 });
|
|
705
|
+
visitArray(candidates, ({ name, astInfo }) => {
|
|
706
|
+
if (!state.remaining.has(name)) return;
|
|
707
|
+
if (!matchesFeature(node, astInfo, matchContext)) return;
|
|
708
|
+
state.foundFeatures[name] = true;
|
|
709
|
+
state.remaining.delete(name);
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function getScopedTraversal(node) {
|
|
714
|
+
if (FUNCTION_SCOPE_TYPES.has(node.type)) return traverseFunction;
|
|
715
|
+
if (node.type === "BlockStatement") return traverseBlock;
|
|
716
|
+
if (node.type === "StaticBlock") return traverseBlock;
|
|
717
|
+
if (node.type === "CatchClause") return traverseCatch;
|
|
718
|
+
if (node.type === "ClassBody") return traverseClassBody;
|
|
719
|
+
if (node.type === "MethodDefinition") return traverseMethod;
|
|
720
|
+
if (node.type === "Program") return traverseProgram;
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function getFlowTraversal(node) {
|
|
725
|
+
if (isLogicalAssignment(node)) return traverseLogicalAssignment;
|
|
726
|
+
if (node.type === "AssignmentPattern") return traverseAssignmentPattern;
|
|
727
|
+
if (node.type === "LogicalExpression") return traverseGuardedLogicalExpression;
|
|
728
|
+
if (node.type === "ConditionalExpression") return traverseConditionalExpression;
|
|
729
|
+
if (node.type === "IfStatement") return traverseIfStatement;
|
|
730
|
+
if (node.type === "ForStatement") return traverseForStatement;
|
|
731
|
+
if (node.type === "ForInStatement") return traverseForInOfStatement;
|
|
732
|
+
if (node.type === "ForOfStatement") return traverseForInOfStatement;
|
|
733
|
+
if (node.type === "WhileStatement") return traverseWhileStatement;
|
|
734
|
+
if (node.type === "DoWhileStatement") return traverseDoWhileStatement;
|
|
735
|
+
if (node.type === "SwitchStatement") return traverseSwitchStatement;
|
|
736
|
+
if (node.type === "TryStatement") return traverseTryStatement;
|
|
737
|
+
if (node.type === "LabeledStatement") return traverseLabeledStatement;
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function isLogicalAssignment(node) {
|
|
742
|
+
const isAssignment = node.type === "AssignmentExpression";
|
|
743
|
+
const isLogical = ["&&=", "||=", "??="].includes(node.operator);
|
|
744
|
+
return isAssignment && isLogical;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function traverseLogicalAssignment(node, context, state, newFunctionDepth) {
|
|
748
|
+
const childContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
749
|
+
traverseFeatures(node.left, childContext, state);
|
|
750
|
+
const key = getStaticReferenceKey(node.left);
|
|
751
|
+
const isMap = Boolean(key) && isKnownMapReceiver(key, context);
|
|
752
|
+
const branch = createReceiverBranchContext(
|
|
753
|
+
context,
|
|
754
|
+
node,
|
|
755
|
+
context.guardedGlobals,
|
|
756
|
+
newFunctionDepth,
|
|
757
|
+
);
|
|
758
|
+
traverseFeatures(node.right, branch, state);
|
|
759
|
+
recordReceiverPattern(node.left, node.right, branch);
|
|
760
|
+
const skipsWrite = isMap && node.operator !== "&&=";
|
|
761
|
+
if (skipsWrite) return;
|
|
762
|
+
const paths = isMap ? [branch.scope] : [context.scope, branch.scope];
|
|
763
|
+
mergeReceiverScopes(context.scope, paths);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function traverseChildFeatures(node, context, state, newFunctionDepth) {
|
|
767
|
+
recordPossibleReceiverThrow(node, context);
|
|
768
|
+
visitChildNodes(node, (child) => {
|
|
769
|
+
const isTypeof = node.type === "UnaryExpression" && node.operator === "typeof";
|
|
770
|
+
const isTypeofArgument = isTypeof && node.argument === child;
|
|
771
|
+
const assignmentScope = getChildAssignmentScope(node, child, context);
|
|
772
|
+
const typeofDepth = isTypeofArgument ? context.typeofDepth + 1 : context.typeofDepth;
|
|
773
|
+
const childContext = createChildContext(
|
|
774
|
+
context,
|
|
775
|
+
node,
|
|
776
|
+
context.guardedGlobals,
|
|
777
|
+
newFunctionDepth,
|
|
778
|
+
);
|
|
779
|
+
Object.assign(childContext, { assignmentScope, typeofDepth });
|
|
780
|
+
traverseFeatures(child, childContext, state);
|
|
781
|
+
});
|
|
782
|
+
recordPossibleReceiverThrow(node, context);
|
|
783
|
+
recordMapReceiver(node, context);
|
|
784
|
+
if (!RECEIVER_EXIT_TYPES.has(node.type)) return;
|
|
785
|
+
recordReceiverCompletion(context, node.type, node.label?.name, context.scope);
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function traverseFunction(node, context, state, newFunctionDepth) {
|
|
790
|
+
const functionScope = createFunctionScope(node, cloneScope(context.scope));
|
|
791
|
+
functionScope.isFunction = true;
|
|
792
|
+
functionScope.ownsThis = node.type !== "ArrowFunctionExpression";
|
|
793
|
+
const isMethod = context.parent?.type === "MethodDefinition";
|
|
794
|
+
if (isMethod) copyThisReceivers(functionScope, context.scope);
|
|
795
|
+
const isConstructor = isMethod && context.parent.kind === "constructor";
|
|
796
|
+
if (isConstructor) functionScope.receiverExits = new Set();
|
|
797
|
+
const childContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
798
|
+
Object.assign(childContext, { scope: functionScope, typeofDepth: 0, reachable: true });
|
|
799
|
+
childContext.receiverFlow = createReceiverFlow(functionScope);
|
|
800
|
+
childContext.catchReceiverThrows = false;
|
|
801
|
+
childContext.loopLabels = [];
|
|
802
|
+
visitArray(node.params, (param) => traverseFeatures(param, childContext, state));
|
|
803
|
+
const continues = traverseFeatures(node.body, childContext, state) !== false;
|
|
804
|
+
recordFunctionReceiverExits(functionScope, childContext.receiverFlow, continues);
|
|
805
|
+
return functionScope;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function traverseProgram(node, context, state, newFunctionDepth) {
|
|
809
|
+
const childContext = createAssignmentChildContext(context, node, newFunctionDepth);
|
|
810
|
+
return traverseStatements(node.body, childContext, state);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function traverseBlock(node, context, state, newFunctionDepth) {
|
|
814
|
+
const scope = createBlockScope(node, context.scope);
|
|
815
|
+
const childContext = createAssignmentChildContext(context, node, newFunctionDepth);
|
|
816
|
+
childContext.scope = scope;
|
|
817
|
+
return traverseStatements(node.body, childContext, state);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function traverseCatch(node, context, state, newFunctionDepth) {
|
|
821
|
+
const scope = createCatchScope(node, context.scope);
|
|
822
|
+
const childContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
823
|
+
childContext.scope = scope;
|
|
824
|
+
traverseFeatures(node.param, childContext, state);
|
|
825
|
+
return traverseFeatures(node.body, childContext, state);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function traverseAssignmentPattern(node, context, state, newFunctionDepth) {
|
|
829
|
+
const branch = traverseReceiverBranch(node.right, node, context, state, new Set());
|
|
830
|
+
mergeReceiverScopes(context.scope, [context.scope, branch.scope]);
|
|
831
|
+
const childContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
832
|
+
traverseFeatures(node.left, childContext, state);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function traverseStatements(statements, context, state) {
|
|
836
|
+
let childContext = context;
|
|
837
|
+
let continues = true;
|
|
838
|
+
visitArray(statements, (statement) => {
|
|
839
|
+
const result = traverseFeatures(statement, childContext, state);
|
|
840
|
+
if (result !== false) return;
|
|
841
|
+
continues = false;
|
|
842
|
+
const scope = cloneScope(childContext.scope);
|
|
843
|
+
childContext = Object.assign({}, childContext, { scope, reachable: false });
|
|
844
|
+
});
|
|
845
|
+
return continues;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function createReceiverFlow(scope) {
|
|
849
|
+
return { scope, exits: new Map() };
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function recordReceiverCompletion(context, type, label, scope) {
|
|
853
|
+
if (context.reachable === false) return;
|
|
854
|
+
const flow = context.receiverFlow;
|
|
855
|
+
if (!flow) return;
|
|
856
|
+
let target = scope;
|
|
857
|
+
while (target.names !== flow.scope.names) target = target.parent;
|
|
858
|
+
const key = `${type}:${label || ""}`;
|
|
859
|
+
const existing = flow.exits.get(key);
|
|
860
|
+
if (existing) {
|
|
861
|
+
mergeReceiverScopes(existing.scope, [existing.scope, target]);
|
|
862
|
+
return;
|
|
127
863
|
}
|
|
864
|
+
const snapshot = cloneScope(target);
|
|
865
|
+
flow.exits.set(key, { type, label, scope: snapshot });
|
|
866
|
+
}
|
|
128
867
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
868
|
+
function recordPossibleReceiverThrow(node, context) {
|
|
869
|
+
if (!context.catchReceiverThrows) return;
|
|
870
|
+
const isReference = node.type === "Identifier" && isIdentifierReference(node, context.parent);
|
|
871
|
+
const canThrow = isReference || POSSIBLY_THROWING_TYPES.has(node.type);
|
|
872
|
+
if (!canThrow) return;
|
|
873
|
+
recordReceiverCompletion(context, "ThrowStatement", null, context.scope);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function recordFunctionReceiverExits(scope, flow, continues) {
|
|
877
|
+
if (!scope.receiverExits) return;
|
|
878
|
+
flow.exits.forEach((exit) => {
|
|
879
|
+
if (exit.type === "ReturnStatement") scope.receiverExits.add(exit.scope);
|
|
880
|
+
});
|
|
881
|
+
if (continues) scope.receiverExits.add(cloneScope(scope));
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function traverseMethod(node, context, state, newFunctionDepth) {
|
|
885
|
+
const childContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
886
|
+
return traverseFeatures(node.value, childContext, state);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function traverseClassBody(node, context, state, newFunctionDepth) {
|
|
890
|
+
const classScope = createClassBodyScope(context.scope, context.parent);
|
|
891
|
+
const classContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
892
|
+
classContext.scope = classScope;
|
|
893
|
+
const instanceScope = createThisScope(cloneScope(classScope));
|
|
894
|
+
const staticScope = createThisScope(classScope);
|
|
895
|
+
const instanceContext = Object.assign({}, classContext, { scope: instanceScope });
|
|
896
|
+
const staticContext = Object.assign({}, classContext, { scope: staticScope });
|
|
897
|
+
visitArray(node.body, (element) => {
|
|
898
|
+
if (element.computed) traverseFeatures(element.key, classContext, state);
|
|
899
|
+
});
|
|
900
|
+
traverseClassInitializers(node, instanceContext, staticContext, state);
|
|
901
|
+
traverseClassConstructor(node, instanceContext, state);
|
|
902
|
+
traverseClassMethods(node, instanceContext, staticContext, state);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function traverseClassMethods(node, instanceContext, staticContext, state) {
|
|
906
|
+
visitArray(node.body, (element) => {
|
|
907
|
+
if (element.kind === "constructor") return;
|
|
908
|
+
const isMethod = element.type === "MethodDefinition";
|
|
909
|
+
const methodContext = element.static ? staticContext : instanceContext;
|
|
910
|
+
const isDeferred = isDeferredClassField(element);
|
|
911
|
+
if (isDeferred) {
|
|
912
|
+
const fieldContext = Object.assign({}, methodContext, { parent: element });
|
|
913
|
+
traverseFeatures(element.value, fieldContext, state);
|
|
914
|
+
return;
|
|
149
915
|
}
|
|
916
|
+
if (!isMethod) return;
|
|
917
|
+
traverseFeatures(element, methodContext, state);
|
|
918
|
+
});
|
|
919
|
+
}
|
|
150
920
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
921
|
+
function isDeferredClassField(node) {
|
|
922
|
+
const isField = node.type === "PropertyDefinition";
|
|
923
|
+
return isField && FUNCTION_SCOPE_TYPES.has(node.value?.type);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function traverseClassInitializers(node, instanceContext, staticContext, state) {
|
|
927
|
+
visitArray(node.body, (element) => {
|
|
928
|
+
const elementContext = element.static ? staticContext : instanceContext;
|
|
929
|
+
if (element.type === "StaticBlock") {
|
|
930
|
+
traverseFeatures(element, staticContext, state);
|
|
931
|
+
return;
|
|
162
932
|
}
|
|
933
|
+
if (element.type !== "PropertyDefinition") return;
|
|
934
|
+
recordFeatureMatches(element, elementContext, state);
|
|
935
|
+
const valueContext = Object.assign({}, elementContext, { parent: element });
|
|
936
|
+
if (!isDeferredClassField(element)) traverseFeatures(element.value, valueContext, state);
|
|
937
|
+
recordClassFieldMapReceiver(element, valueContext);
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function traverseClassConstructor(node, context, state) {
|
|
942
|
+
const constructor = node.body.find((element) => element.kind === "constructor");
|
|
943
|
+
if (!constructor) return;
|
|
944
|
+
const scope = traverseFeatures(constructor, context, state);
|
|
945
|
+
const exits = Array.from(scope.receiverExits);
|
|
946
|
+
mergeReceiverScopes(scope, exits);
|
|
947
|
+
copyThisReceivers(context.scope, scope);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function copyThisReceivers(target, source) {
|
|
951
|
+
const isThisKey = (key) => key === "this" || key.startsWith("this.");
|
|
952
|
+
target.mapReceivers = new Set(Array.from(source.mapReceivers).filter(isThisKey));
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function recordClassFieldMapReceiver(node, context) {
|
|
956
|
+
const propertyName = getStaticPropertyKey(node, node.key);
|
|
957
|
+
if (!propertyName) return;
|
|
958
|
+
const key = `this.${propertyName}`;
|
|
959
|
+
recordReceiverValue(key, node.value, context);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function getReceiverTrackingScope(key, context) {
|
|
963
|
+
const rootName = getReceiverRootName(key);
|
|
964
|
+
let scope = context.scope;
|
|
965
|
+
while (scope.parent) {
|
|
966
|
+
const ownsReceiver = rootName ? scope.names.has(rootName) : scope.ownsThis;
|
|
967
|
+
if (ownsReceiver) return scope;
|
|
968
|
+
scope = scope.parent;
|
|
969
|
+
}
|
|
970
|
+
return scope;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function clearReceiverDescendants(scope, key) {
|
|
974
|
+
const prefix = `${key}.`;
|
|
975
|
+
scope.mapReceivers.forEach((receiver) => {
|
|
976
|
+
if (receiver.startsWith(prefix)) scope.mapReceivers.delete(receiver);
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function markMapReceiver(scope, key) {
|
|
981
|
+
clearReceiverDescendants(scope, key);
|
|
982
|
+
scope.mapReceivers.add(key);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function markNonMapReceiver(scope, key) {
|
|
986
|
+
clearReceiverDescendants(scope, key);
|
|
987
|
+
scope.mapReceivers.delete(key);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function recordReceiverValue(key, value, context) {
|
|
991
|
+
const scope = getReceiverTrackingScope(key, context);
|
|
992
|
+
if (isMapConstructorExpression(value, context)) {
|
|
993
|
+
markMapReceiver(scope, key);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
markNonMapReceiver(scope, key);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function recordMapReceiver(node, context) {
|
|
1000
|
+
if (node.type === "VariableDeclarator") {
|
|
1001
|
+
if (node.init) recordReceiverPattern(node.id, node.init, context);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
if (node.type === "AssignmentExpression") {
|
|
1005
|
+
const value = node.operator === "=" ? node.right : null;
|
|
1006
|
+
recordReceiverPattern(node.left, value, context);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
if (node.type === "UpdateExpression") recordReceiverPattern(node.argument, null, context);
|
|
1010
|
+
const isDelete = node.type === "UnaryExpression" && node.operator === "delete";
|
|
1011
|
+
if (isDelete) recordReceiverPattern(node.argument, null, context);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function recordReceiverPattern(node, value, context) {
|
|
1015
|
+
const key = getStaticReferenceKey(node);
|
|
1016
|
+
if (key) {
|
|
1017
|
+
recordReceiverValue(key, value, context);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
if (node?.type === "MemberExpression") {
|
|
1021
|
+
clearDynamicReceiverDescendants(node.object, context);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (node?.type === "RestElement") recordReceiverPattern(node.argument, null, context);
|
|
1025
|
+
if (node?.type === "AssignmentPattern") recordReceiverPattern(node.left, null, context);
|
|
1026
|
+
if (node?.type === "ArrayPattern") {
|
|
1027
|
+
visitArray(node.elements, (element) => recordReceiverPattern(element, null, context));
|
|
1028
|
+
}
|
|
1029
|
+
if (node?.type === "ObjectPattern") {
|
|
1030
|
+
visitArray(node.properties, (property) => {
|
|
1031
|
+
const target = property.type === "RestElement" ? property.argument : property.value;
|
|
1032
|
+
recordReceiverPattern(target, null, context);
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function clearDynamicReceiverDescendants(node, context) {
|
|
1038
|
+
const key = getStaticReferenceKey(node);
|
|
1039
|
+
if (key) {
|
|
1040
|
+
const scope = getReceiverTrackingScope(key, context);
|
|
1041
|
+
clearReceiverDescendants(scope, key);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
if (node?.type === "MemberExpression") clearDynamicReceiverDescendants(node.object, context);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function getChildAssignmentScope(node, child, context) {
|
|
1048
|
+
if (!context.assignmentScope) return null;
|
|
1049
|
+
if (isTransparentAssignmentContainer(node)) return context.assignmentScope;
|
|
1050
|
+
if (isTryAssignmentChild(node, child)) return context.assignmentScope;
|
|
1051
|
+
if (isSwitchAssignmentChild(node, child)) return context.assignmentScope;
|
|
1052
|
+
if (isDoWhileBody(node, child)) return context.assignmentScope;
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function isTransparentAssignmentContainer(node) {
|
|
1057
|
+
const transparentTypes = ["Program", "ExpressionStatement", "SequenceExpression"];
|
|
1058
|
+
return transparentTypes.includes(node.type);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function isTryAssignmentChild(node, child) {
|
|
1062
|
+
const isTry = node.type === "TryStatement";
|
|
1063
|
+
const isTryBlock = node.block === child;
|
|
1064
|
+
const isFinallyBlock = node.finalizer === child;
|
|
1065
|
+
const isTryBody = isTryBlock || isFinallyBlock;
|
|
1066
|
+
return isTry && isTryBody;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function isSwitchAssignmentChild(node, child) {
|
|
1070
|
+
if (node.type === "SwitchStatement") return isOnlyDefaultCase(node, child);
|
|
1071
|
+
if (node.type !== "SwitchCase") return false;
|
|
1072
|
+
|
|
1073
|
+
const isDefault = node.test == null;
|
|
1074
|
+
const isConsequent = node.consequent.includes(child);
|
|
1075
|
+
return isDefault && isConsequent;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function isOnlyDefaultCase(node, child) {
|
|
1079
|
+
const [onlyCase] = node.cases;
|
|
1080
|
+
const hasOneCase = node.cases.length === 1;
|
|
1081
|
+
const isDefault = onlyCase?.test == null;
|
|
1082
|
+
const isChild = onlyCase === child;
|
|
1083
|
+
const isOnlyDefault = hasOneCase && isDefault;
|
|
1084
|
+
return isOnlyDefault && isChild;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function isDoWhileBody(node, child) {
|
|
1088
|
+
const isDoWhile = node.type === "DoWhileStatement";
|
|
1089
|
+
const isBody = node.body === child;
|
|
1090
|
+
return isDoWhile && isBody;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function recordTopLevelAssignment(node, context) {
|
|
1094
|
+
const isAssignment = node.type === "AssignmentExpression";
|
|
1095
|
+
if (!isAssignment) return;
|
|
1096
|
+
if (!context.assignmentScope) return;
|
|
1097
|
+
|
|
1098
|
+
addAssignedGlobalName(node, context.assignmentScope.names);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function createChildContext(context, parent, guardedGlobals, newFunctionDepth) {
|
|
1102
|
+
return Object.assign({}, context, {
|
|
1103
|
+
functionDepth: newFunctionDepth,
|
|
1104
|
+
parent,
|
|
1105
|
+
assignmentScope: null,
|
|
1106
|
+
guardedGlobals,
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function createAssignmentChildContext(context, parent, newFunctionDepth) {
|
|
1111
|
+
const childContext = createChildContext(
|
|
1112
|
+
context,
|
|
1113
|
+
parent,
|
|
1114
|
+
context.guardedGlobals,
|
|
1115
|
+
newFunctionDepth,
|
|
1116
|
+
);
|
|
1117
|
+
childContext.assignmentScope = context.assignmentScope;
|
|
1118
|
+
return childContext;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function createReceiverBranchContext(context, parent, guardedGlobals, newFunctionDepth) {
|
|
1122
|
+
const branchContext = createChildContext(context, parent, guardedGlobals, newFunctionDepth);
|
|
1123
|
+
branchContext.scope = cloneScope(context.scope);
|
|
1124
|
+
return branchContext;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function traverseGuardedLogicalExpression(node, context, state, newFunctionDepth) {
|
|
1128
|
+
const leftContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
1129
|
+
traverseFeatures(node.left, leftContext, state);
|
|
1130
|
+
const guardedNames = getLogicalRightGuardNames(node);
|
|
1131
|
+
const guardedGlobals = mergeGuardedGlobals(context.guardedGlobals, guardedNames);
|
|
1132
|
+
const rightContext = createReceiverBranchContext(context, node, guardedGlobals, newFunctionDepth);
|
|
1133
|
+
traverseFeatures(node.right, rightContext, state);
|
|
1134
|
+
mergeReceiverScopes(context.scope, [context.scope, rightContext.scope]);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function getLogicalRightGuardNames(node) {
|
|
1138
|
+
if (node.operator === "??") return new Set();
|
|
1139
|
+
if (node.operator === "&&") return collectTrueBranchTypeofNames(node.left);
|
|
1140
|
+
return collectFalseBranchTypeofNames(node.left);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function traverseConditionalExpression(node, context, state, newFunctionDepth) {
|
|
1144
|
+
return traverseIfStatement(node, context, state, newFunctionDepth);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function traverseIfStatement(node, context, state, newFunctionDepth) {
|
|
1148
|
+
const testContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
1149
|
+
traverseFeatures(node.test, testContext, state);
|
|
1150
|
+
const consequentNames = collectTrueBranchTypeofNames(node.test);
|
|
1151
|
+
const alternateNames = collectFalseBranchTypeofNames(node.test);
|
|
1152
|
+
const consequent = traverseReceiverBranch(node.consequent, node, context, state, consequentNames);
|
|
1153
|
+
const alternate = traverseReceiverBranch(node.alternate, node, context, state, alternateNames);
|
|
1154
|
+
const branches = [consequent, alternate];
|
|
1155
|
+
const continuing = branches.filter((branch) => branch.continues);
|
|
1156
|
+
const incoming = continuing.length > 0 ? continuing : branches;
|
|
1157
|
+
mergeReceiverScopes(
|
|
1158
|
+
context.scope,
|
|
1159
|
+
incoming.map((branch) => branch.scope),
|
|
1160
|
+
);
|
|
1161
|
+
return continuing.length > 0;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function traverseReceiverBranch(node, parent, context, state, guardNames) {
|
|
1165
|
+
const guardedGlobals = mergeGuardedGlobals(context.guardedGlobals, guardNames);
|
|
1166
|
+
const childContext = createReceiverBranchContext(
|
|
1167
|
+
context,
|
|
1168
|
+
parent,
|
|
1169
|
+
guardedGlobals,
|
|
1170
|
+
context.functionDepth,
|
|
1171
|
+
);
|
|
1172
|
+
const continues = traverseFeatures(node, childContext, state) !== false;
|
|
1173
|
+
return { scope: childContext.scope, continues };
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function traverseForStatement(node, context, state, depth) {
|
|
1177
|
+
const guards = context.guardedGlobals;
|
|
1178
|
+
const loopScope = createLoopScope(node, context.scope);
|
|
1179
|
+
const loopContext = createAssignmentChildContext(context, node, depth);
|
|
1180
|
+
loopContext.scope = loopScope;
|
|
1181
|
+
traverseFeatures(node.init, loopContext, state);
|
|
1182
|
+
const childContext = createChildContext(loopContext, node, guards, depth);
|
|
1183
|
+
traverseLoopTest(node.test, childContext, state);
|
|
1184
|
+
return traverseConditionalLoop(node, childContext, state);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function traverseForInOfStatement(node, context, state, newFunctionDepth) {
|
|
1188
|
+
const loopScope = createLoopScope(node, context.scope);
|
|
1189
|
+
const loopContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
1190
|
+
loopContext.scope = loopScope;
|
|
1191
|
+
traverseFeatures(node.right, loopContext, state);
|
|
1192
|
+
const iteration = createReceiverFlowContext(loopContext, node);
|
|
1193
|
+
traverseFeatures(node.left, iteration, state);
|
|
1194
|
+
recordIterationReceiver(node.left, iteration);
|
|
1195
|
+
const continues = traverseFeatures(node.body, iteration, state) !== false;
|
|
1196
|
+
const paths = getLoopReceiverPaths(iteration, continues, context.loopLabels);
|
|
1197
|
+
const incoming = [loopScope].concat(paths.breaks, paths.iterations);
|
|
1198
|
+
return finishReceiverFlow(loopContext, iteration.receiverFlow, incoming);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function recordIterationReceiver(node, context) {
|
|
1202
|
+
if (node.type !== "VariableDeclaration") {
|
|
1203
|
+
recordReceiverPattern(node, null, context);
|
|
1204
|
+
return;
|
|
163
1205
|
}
|
|
1206
|
+
visitArray(node.declarations, (declaration) => {
|
|
1207
|
+
recordReceiverPattern(declaration.id, null, context);
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function traverseWhileStatement(node, context, state, newFunctionDepth) {
|
|
1212
|
+
const testContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
1213
|
+
traverseLoopTest(node.test, testContext, state);
|
|
1214
|
+
return traverseConditionalLoop(node, testContext, state);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function traverseDoWhileStatement(node, context, state) {
|
|
1218
|
+
const iteration = createReceiverFlowContext(context, node);
|
|
1219
|
+
iteration.assignmentScope = context.assignmentScope;
|
|
1220
|
+
const continues = traverseFeatures(node.body, iteration, state) !== false;
|
|
1221
|
+
const paths = getLoopReceiverPaths(iteration, continues, context.loopLabels);
|
|
1222
|
+
const tested = traverseLoopUpdate(node, iteration, paths.iterations, state);
|
|
1223
|
+
const conditionExits = isAlwaysTrue(node.test) ? [] : tested;
|
|
1224
|
+
const incoming = paths.breaks.concat(conditionExits);
|
|
1225
|
+
return finishReceiverFlow(context, iteration.receiverFlow, incoming);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function createReceiverFlowContext(context, node) {
|
|
1229
|
+
const branch = createReceiverBranchContext(
|
|
1230
|
+
context,
|
|
1231
|
+
node,
|
|
1232
|
+
context.guardedGlobals,
|
|
1233
|
+
context.functionDepth,
|
|
1234
|
+
);
|
|
1235
|
+
branch.receiverFlow = createReceiverFlow(branch.scope);
|
|
1236
|
+
branch.loopLabels = [];
|
|
1237
|
+
return branch;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
function consumeReceiverExits(flow, type, labels = [], allowUnlabeled = true) {
|
|
1241
|
+
const scopes = new Set();
|
|
1242
|
+
flow.exits.forEach((exit, key) => {
|
|
1243
|
+
const matchesType = exit.type === type;
|
|
1244
|
+
const matchesLabel = labels.includes(exit.label) || (allowUnlabeled && !exit.label);
|
|
1245
|
+
const matches = matchesType && matchesLabel;
|
|
1246
|
+
if (!matches) return;
|
|
1247
|
+
scopes.add(exit.scope);
|
|
1248
|
+
flow.exits.delete(key);
|
|
1249
|
+
});
|
|
1250
|
+
return Array.from(scopes);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function finishReceiverFlow(context, flow, incoming) {
|
|
1254
|
+
flow.exits.forEach(({ type, label, scope }) => {
|
|
1255
|
+
recordReceiverCompletion(context, type, label, scope);
|
|
1256
|
+
});
|
|
1257
|
+
mergeReceiverScopes(context.scope, incoming);
|
|
1258
|
+
return incoming.length > 0;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
function getLoopReceiverPaths(context, continues, labels) {
|
|
1262
|
+
const flow = context.receiverFlow;
|
|
1263
|
+
const breaks = consumeReceiverExits(flow, "BreakStatement", labels);
|
|
1264
|
+
const resumed = consumeReceiverExits(flow, "ContinueStatement", labels);
|
|
1265
|
+
const normal = continues ? [context.scope] : [];
|
|
1266
|
+
const iterations = normal.concat(resumed);
|
|
1267
|
+
return { breaks, iterations };
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function traverseConditionalLoop(node, context, state) {
|
|
1271
|
+
const iteration = createReceiverFlowContext(context, node);
|
|
1272
|
+
const continues = traverseFeatures(node.body, iteration, state) !== false;
|
|
1273
|
+
const paths = getLoopReceiverPaths(iteration, continues, context.loopLabels);
|
|
1274
|
+
const updated = traverseLoopUpdate(node, iteration, paths.iterations, state);
|
|
1275
|
+
const canFinish = Boolean(node.test) && !isAlwaysTrue(node.test);
|
|
1276
|
+
const conditionExits = canFinish ? [context.scope].concat(updated) : [];
|
|
1277
|
+
const incoming = paths.breaks.concat(conditionExits);
|
|
1278
|
+
return finishReceiverFlow(context, iteration.receiverFlow, incoming);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function traverseLoopUpdate(node, context, incoming, state) {
|
|
1282
|
+
const updateContext = createReceiverBranchContext(
|
|
1283
|
+
context,
|
|
1284
|
+
context.parent,
|
|
1285
|
+
context.guardedGlobals,
|
|
1286
|
+
context.functionDepth,
|
|
1287
|
+
);
|
|
1288
|
+
mergeReceiverScopes(updateContext.scope, incoming);
|
|
1289
|
+
if (incoming.length === 0) updateContext.reachable = false;
|
|
1290
|
+
traverseFeatures(node.update, updateContext, state);
|
|
1291
|
+
traverseLoopTest(node.test, updateContext, state);
|
|
1292
|
+
if (incoming.length === 0) return [];
|
|
1293
|
+
return [updateContext.scope];
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function traverseLoopTest(node, context, state) {
|
|
1297
|
+
if (!node) return;
|
|
1298
|
+
const cache = getReceiverCache(node, state);
|
|
1299
|
+
const key = getLoopTestKey(context, state);
|
|
1300
|
+
let result = cache.get(key);
|
|
1301
|
+
if (!result) {
|
|
1302
|
+
result = analyzeLoopTest(node, context, state);
|
|
1303
|
+
cache.set(key, result);
|
|
1304
|
+
}
|
|
1305
|
+
const restored = restoreFinalizerResult(context.scope, result);
|
|
1306
|
+
forwardFinalizerExits(context, restored);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function getLoopTestKey(context, state) {
|
|
1310
|
+
const scope = getReceiverScopeState(context.scope);
|
|
1311
|
+
const guards = Array.from(context.guardedGlobals).sort();
|
|
1312
|
+
const hasFeatures = state.remaining.size > 0 || state.remainingGlobals.size > 0;
|
|
1313
|
+
const isReachable = context.reachable !== false;
|
|
1314
|
+
const catchesThrows = Boolean(context.catchReceiverThrows);
|
|
1315
|
+
const tracksAssignments = Boolean(context.assignmentScope);
|
|
1316
|
+
return JSON.stringify([
|
|
1317
|
+
scope,
|
|
1318
|
+
guards,
|
|
1319
|
+
hasFeatures,
|
|
1320
|
+
isReachable,
|
|
1321
|
+
catchesThrows,
|
|
1322
|
+
tracksAssignments,
|
|
1323
|
+
]);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function analyzeLoopTest(node, context, state) {
|
|
1327
|
+
const branch = createReceiverFlowContext(context, context.parent);
|
|
1328
|
+
branch.assignmentScope = context.assignmentScope;
|
|
1329
|
+
const continues = traverseFeatures(node, branch, state) !== false;
|
|
1330
|
+
const exits = Array.from(branch.receiverFlow.exits.values());
|
|
1331
|
+
return { scope: branch.scope, continues, exits };
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function isAlwaysTrue(node) {
|
|
1335
|
+
const isLiteral = node?.type === "Literal";
|
|
1336
|
+
const alwaysTrue = isLiteral && node.value === true;
|
|
1337
|
+
return alwaysTrue;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function traverseLabeledStatement(node, context, state) {
|
|
1341
|
+
const branch = createReceiverFlowContext(context, node);
|
|
1342
|
+
branch.loopLabels = (context.loopLabels || []).concat(node.label.name);
|
|
1343
|
+
branch.assignmentScope = context.assignmentScope;
|
|
1344
|
+
const continues = traverseFeatures(node.body, branch, state) !== false;
|
|
1345
|
+
const breaks = consumeReceiverExits(
|
|
1346
|
+
branch.receiverFlow,
|
|
1347
|
+
"BreakStatement",
|
|
1348
|
+
[node.label.name],
|
|
1349
|
+
false,
|
|
1350
|
+
);
|
|
1351
|
+
const normal = continues ? [branch.scope] : [];
|
|
1352
|
+
return finishReceiverFlow(context, branch.receiverFlow, normal.concat(breaks));
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function traverseSwitchStatement(node, context, state, newFunctionDepth) {
|
|
1356
|
+
const testContext = createChildContext(context, node, context.guardedGlobals, newFunctionDepth);
|
|
1357
|
+
traverseFeatures(node.discriminant, testContext, state);
|
|
1358
|
+
const scope = createSwitchScope(node, context.scope);
|
|
1359
|
+
const switchContext = Object.assign({}, testContext, { scope });
|
|
1360
|
+
switchContext.receiverFlow = createReceiverFlow(scope);
|
|
1361
|
+
const branches = new Set();
|
|
1362
|
+
let fallthrough = null;
|
|
1363
|
+
visitArray(node.cases, (switchCase, index) => {
|
|
1364
|
+
const branch = traverseSwitchCase(switchCase, node, switchContext, state, fallthrough, context);
|
|
1365
|
+
const isExit = branch.continues && index === node.cases.length - 1;
|
|
1366
|
+
if (isExit) branches.add(branch.scope);
|
|
1367
|
+
fallthrough = branch.continues ? branch.scope : null;
|
|
1368
|
+
});
|
|
1369
|
+
const hasDefault = node.cases.some((switchCase) => switchCase.test === null);
|
|
1370
|
+
if (!hasDefault) branches.add(scope);
|
|
1371
|
+
const exits = Array.from(branches);
|
|
1372
|
+
const breaks = consumeReceiverExits(switchContext.receiverFlow, "BreakStatement");
|
|
1373
|
+
return finishReceiverFlow(
|
|
1374
|
+
testContext,
|
|
1375
|
+
switchContext.receiverFlow,
|
|
1376
|
+
exits.concat(breaks).map((branch) => branch.parent),
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
function traverseSwitchCase(node, parent, context, state, fallthrough, outerContext) {
|
|
1381
|
+
const branch = createReceiverBranchContext(
|
|
1382
|
+
context,
|
|
1383
|
+
parent,
|
|
1384
|
+
context.guardedGlobals,
|
|
1385
|
+
context.functionDepth,
|
|
1386
|
+
);
|
|
1387
|
+
if (fallthrough) mergeReceiverScopes(branch.scope, [branch.scope, fallthrough]);
|
|
1388
|
+
if (isOnlyDefaultCase(parent, node)) branch.assignmentScope = outerContext.assignmentScope;
|
|
1389
|
+
traverseFeatures(node.test, branch, state);
|
|
1390
|
+
branch.continues = traverseStatements(node.consequent, branch, state);
|
|
1391
|
+
return branch;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
function traverseTryStatement(node, context, state) {
|
|
1395
|
+
const branch = createReceiverFlowContext(context, node);
|
|
1396
|
+
branch.assignmentScope = context.assignmentScope;
|
|
1397
|
+
branch.catchReceiverThrows = true;
|
|
1398
|
+
const continues = traverseFeatures(node.block, branch, state) !== false;
|
|
1399
|
+
const normal = continues ? [cloneScope(branch.scope)] : [];
|
|
1400
|
+
const caught = traverseReceiverHandler(node, branch, state);
|
|
1401
|
+
const incoming = normal.concat(caught);
|
|
1402
|
+
if (node.finalizer) return traverseReceiverFinalizer(node, context, branch, incoming, state);
|
|
1403
|
+
return finishReceiverFlow(context, branch.receiverFlow, incoming);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function traverseReceiverHandler(node, context, state) {
|
|
1407
|
+
if (!node.handler) return [];
|
|
1408
|
+
const thrown = consumeReceiverExits(context.receiverFlow, "ThrowStatement");
|
|
1409
|
+
const handler = createReceiverBranchContext(
|
|
1410
|
+
context,
|
|
1411
|
+
node,
|
|
1412
|
+
context.guardedGlobals,
|
|
1413
|
+
context.functionDepth,
|
|
1414
|
+
);
|
|
1415
|
+
mergeReceiverScopes(handler.scope, thrown);
|
|
1416
|
+
if (thrown.length === 0) handler.reachable = false;
|
|
1417
|
+
const continues = traverseFeatures(node.handler, handler, state) !== false;
|
|
1418
|
+
const canContinue = continues && thrown.length > 0;
|
|
1419
|
+
if (!canContinue) return [];
|
|
1420
|
+
return [handler.scope];
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
function traverseReceiverFinalizer(node, context, branch, incoming, state) {
|
|
1424
|
+
const flow = branch.receiverFlow;
|
|
1425
|
+
const exits = Array.from(flow.exits.values());
|
|
1426
|
+
const groups = groupFinalizerReceivers(branch.scope, incoming, exits);
|
|
1427
|
+
const receiverState = getFinalizerReceiverState(node, branch, groups, state);
|
|
1428
|
+
flow.exits.clear();
|
|
1429
|
+
const normal = groups.flatMap((group) =>
|
|
1430
|
+
finalizeReceiverPaths(node, branch, group, receiverState),
|
|
1431
|
+
);
|
|
1432
|
+
return finishReceiverFlow(context, flow, normal);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function groupFinalizerReceivers(scope, incoming, exits) {
|
|
1436
|
+
const normal = cloneScope(scope);
|
|
1437
|
+
mergeReceiverScopes(normal, incoming);
|
|
1438
|
+
const paths =
|
|
1439
|
+
incoming.length > 0 ? [{ scope: normal, type: null, label: null }].concat(exits) : exits;
|
|
1440
|
+
const groups = new Map();
|
|
1441
|
+
paths.forEach((path) => {
|
|
1442
|
+
const key = JSON.stringify(getReceiverScopeState(path.scope));
|
|
1443
|
+
const existing = groups.get(key);
|
|
1444
|
+
if (existing) {
|
|
1445
|
+
existing.completions.add(path);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
const completions = new Set([path]);
|
|
1449
|
+
groups.set(key, { scope: path.scope, completions });
|
|
1450
|
+
});
|
|
1451
|
+
return Array.from(groups.values());
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
function getReceiverScopeState(scope) {
|
|
1455
|
+
if (!scope) return null;
|
|
1456
|
+
const receivers = Array.from(scope.mapReceivers).sort();
|
|
1457
|
+
const names = Array.from(scope.names).sort();
|
|
1458
|
+
const parent = getReceiverScopeState(scope.parent);
|
|
1459
|
+
return [receivers, names, scope.ownsThis, parent];
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
function getFinalizerReceiverState(node, context, groups, state) {
|
|
1463
|
+
if (groups.length === 1) return state;
|
|
1464
|
+
const analysis = createReceiverFlowContext(context, node);
|
|
1465
|
+
analysis.assignmentScope = context.assignmentScope;
|
|
1466
|
+
const scopes = groups.map((group) => group.scope);
|
|
1467
|
+
mergeReceiverScopes(analysis.scope, scopes);
|
|
1468
|
+
const hasFeatures = state.remaining.size > 0 || state.remainingGlobals.size > 0;
|
|
1469
|
+
if (hasFeatures) traverseFeatures(node.finalizer, analysis, state);
|
|
1470
|
+
return {
|
|
1471
|
+
remaining: new Set(),
|
|
1472
|
+
remainingGlobals: new Set(),
|
|
1473
|
+
foundFeatures: {},
|
|
1474
|
+
receiverCache: state.receiverCache,
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
function finalizeReceiverPaths(node, context, group, state) {
|
|
1479
|
+
const result = traverseFinalizerGroup(node, context, group, state);
|
|
1480
|
+
forwardFinalizerExits(context, result);
|
|
1481
|
+
if (!result.continues) return [];
|
|
1482
|
+
let normal = false;
|
|
1483
|
+
group.completions.forEach(({ type, label }) => {
|
|
1484
|
+
if (type) recordReceiverCompletion(context, type, label, result.scope);
|
|
1485
|
+
else normal = true;
|
|
1486
|
+
});
|
|
1487
|
+
return normal ? [result.scope] : [];
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
function traverseFinalizerGroup(node, context, group, state) {
|
|
1491
|
+
const branch = createReceiverFlowContext(context, node);
|
|
1492
|
+
branch.assignmentScope = context.assignmentScope;
|
|
1493
|
+
mergeReceiverScopes(branch.scope, [group.scope]);
|
|
1494
|
+
const cache = getFinalizerCache(node.finalizer, state);
|
|
1495
|
+
const key = JSON.stringify(getReceiverScopeState(branch.scope));
|
|
1496
|
+
const cached = cache?.get(key);
|
|
1497
|
+
if (cached) return restoreFinalizerResult(branch.scope, cached);
|
|
1498
|
+
const continues = traverseFeatures(node.finalizer, branch, state) !== false;
|
|
1499
|
+
const exits = Array.from(branch.receiverFlow.exits.values());
|
|
1500
|
+
const result = { scope: branch.scope, continues, exits };
|
|
1501
|
+
if (cache) cache.set(key, result);
|
|
1502
|
+
return result;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
function getFinalizerCache(node, state) {
|
|
1506
|
+
const hasFeatures = state.remaining.size > 0 || state.remainingGlobals.size > 0;
|
|
1507
|
+
if (hasFeatures) return null;
|
|
1508
|
+
return getReceiverCache(node, state);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function getReceiverCache(node, state) {
|
|
1512
|
+
if (!state.receiverCache.has(node)) state.receiverCache.set(node, new Map());
|
|
1513
|
+
return state.receiverCache.get(node);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
function restoreFinalizerResult(scope, cached) {
|
|
1517
|
+
mergeReceiverScopes(scope, [cached.scope]);
|
|
1518
|
+
const exits = cached.exits.map((exit) => {
|
|
1519
|
+
const snapshot = cloneScope(scope);
|
|
1520
|
+
mergeReceiverScopes(snapshot, [exit.scope]);
|
|
1521
|
+
return Object.assign({}, exit, { scope: snapshot });
|
|
1522
|
+
});
|
|
1523
|
+
return { scope, continues: cached.continues, exits };
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
function forwardFinalizerExits(context, result) {
|
|
1527
|
+
result.exits.forEach(({ type, label, scope }) => {
|
|
1528
|
+
recordReceiverCompletion(context, type, label, scope);
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
function maybeRecordGlobalReference(node, context, state) {
|
|
1533
|
+
const isIdentifier = node.type === "Identifier";
|
|
1534
|
+
if (!isIdentifier) return;
|
|
1535
|
+
if (context.typeofDepth > 0) return;
|
|
1536
|
+
|
|
1537
|
+
const name = node.name;
|
|
1538
|
+
const isKnownGlobal = state.remainingGlobals.has(name);
|
|
1539
|
+
if (!isKnownGlobal) return;
|
|
1540
|
+
if (context.guardedGlobals.has(name)) return;
|
|
1541
|
+
|
|
1542
|
+
if (!isIdentifierReference(node, context.parent)) return;
|
|
1543
|
+
if (isDeclared(context.scope, name)) return;
|
|
1544
|
+
|
|
1545
|
+
state.foundFeatures[name] = true;
|
|
1546
|
+
state.remainingGlobals.delete(name);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
function mergeGuardedGlobals(existing, names) {
|
|
1550
|
+
if (names.size === 0) return existing;
|
|
1551
|
+
const merged = new Set(existing);
|
|
1552
|
+
names.forEach((name) => {
|
|
1553
|
+
merged.add(name);
|
|
1554
|
+
});
|
|
1555
|
+
return merged;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function collectTrueBranchTypeofNames(node) {
|
|
1559
|
+
const names = new Set();
|
|
1560
|
+
collectBranchTypeofNames(node, true, names);
|
|
1561
|
+
return names;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
function collectFalseBranchTypeofNames(node) {
|
|
1565
|
+
const names = new Set();
|
|
1566
|
+
collectBranchTypeofNames(node, false, names);
|
|
1567
|
+
return names;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
function collectBranchTypeofNames(node, branchValue, names) {
|
|
1571
|
+
const isNotNode = !isNodeRecord(node);
|
|
1572
|
+
if (isNotNode) return;
|
|
1573
|
+
if (FUNCTION_SCOPE_TYPES.has(node.type)) return;
|
|
1574
|
+
|
|
1575
|
+
const isUnaryNot = node.type === "UnaryExpression" && node.operator === "!";
|
|
1576
|
+
if (isUnaryNot) {
|
|
1577
|
+
collectBranchTypeofNames(node.argument, !branchValue, names);
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
if (addTypeofComparisonName(node, branchValue, names)) return;
|
|
1581
|
+
if (node.type === "ConditionalExpression") {
|
|
1582
|
+
collectConditionalTypeofNames(node, branchValue, names);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
if (shouldSkipBranchGuard(node, branchValue)) return;
|
|
1586
|
+
|
|
1587
|
+
visitChildNodes(node, (child) => {
|
|
1588
|
+
collectBranchTypeofNames(child, branchValue, names);
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
function addTypeofComparisonName(node, branchValue, names) {
|
|
1593
|
+
const comparison = getTypeofComparison(node);
|
|
1594
|
+
if (!comparison) return false;
|
|
1595
|
+
|
|
1596
|
+
const guardedBranch = getTypeofComparisonGuardBranch(comparison);
|
|
1597
|
+
if (guardedBranch === branchValue) {
|
|
1598
|
+
names.add(comparison.name);
|
|
1599
|
+
}
|
|
1600
|
+
return true;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function getTypeofComparisonGuardBranch(comparison) {
|
|
1604
|
+
const isEqual = comparison.operator === "===" || comparison.operator === "==";
|
|
1605
|
+
const isUndefined = comparison.value === "undefined";
|
|
1606
|
+
if (isUndefined) return !isEqual;
|
|
1607
|
+
return isEqual;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
function getTypeofComparison(node) {
|
|
1611
|
+
if (node.type !== "BinaryExpression") return null;
|
|
1612
|
+
|
|
1613
|
+
const leftComparison = getTypeofComparisonSide(node.left, node.right);
|
|
1614
|
+
if (leftComparison) {
|
|
1615
|
+
return normalizeTypeofComparison(leftComparison, node.operator);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
const rightComparison = getTypeofComparisonSide(node.right, node.left);
|
|
1619
|
+
if (rightComparison) {
|
|
1620
|
+
return normalizeTypeofComparison(rightComparison, node.operator, true);
|
|
1621
|
+
}
|
|
1622
|
+
return getBooleanTypeofComparison(node);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function getBooleanTypeofComparison(node) {
|
|
1626
|
+
if (!isTypeofComparisonOperator(node.operator)) return null;
|
|
1627
|
+
|
|
1628
|
+
const isLeftBoolean = typeof node.left.value === "boolean";
|
|
1629
|
+
const valueNode = isLeftBoolean ? node.left : node.right;
|
|
1630
|
+
const expression = isLeftBoolean ? node.right : node.left;
|
|
1631
|
+
const isBoolean = valueNode.type === "Literal" && typeof valueNode.value === "boolean";
|
|
1632
|
+
if (!isBoolean) return null;
|
|
1633
|
+
|
|
1634
|
+
const comparison = getTypeofComparison(expression);
|
|
1635
|
+
if (!comparison) return null;
|
|
1636
|
+
const isEqual = node.operator === "===" || node.operator === "==";
|
|
1637
|
+
const preservesPolarity = isEqual === valueNode.value;
|
|
1638
|
+
const guardedBranch = getTypeofComparisonGuardBranch(comparison) === preservesPolarity;
|
|
1639
|
+
const operator = guardedBranch ? "!==" : "===";
|
|
1640
|
+
return { name: comparison.name, value: "undefined", operator };
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
function normalizeTypeofComparison(comparison, operator, isReversed = false) {
|
|
1644
|
+
if (isTypeofComparisonOperator(operator)) {
|
|
1645
|
+
return Object.assign({}, comparison, { operator });
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
const isRelational = operator === "<" || operator === ">";
|
|
1649
|
+
const isMinifiedGuard = isRelational && comparison.value === "u";
|
|
1650
|
+
if (!isMinifiedGuard) return null;
|
|
1651
|
+
|
|
1652
|
+
const isUndefined = (operator === ">") !== isReversed;
|
|
1653
|
+
const normalizedOperator = isUndefined ? "===" : "!==";
|
|
1654
|
+
return Object.assign({}, comparison, { value: "undefined", operator: normalizedOperator });
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
function isTypeofComparisonOperator(operator) {
|
|
1658
|
+
const comparisonOperators = ["==", "===", "!=", "!=="];
|
|
1659
|
+
return comparisonOperators.includes(operator);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function getTypeofComparisonSide(typeofNode, valueNode) {
|
|
1663
|
+
const isTypeof = typeofNode.type === "UnaryExpression" && typeofNode.operator === "typeof";
|
|
1664
|
+
const isString = valueNode?.type === "Literal" && typeof valueNode.value === "string";
|
|
1665
|
+
const isInvalidComparison = !isTypeof || !isString;
|
|
1666
|
+
if (isInvalidComparison) return null;
|
|
1667
|
+
|
|
1668
|
+
const target = getTypeofIdentifierTarget(typeofNode.argument);
|
|
1669
|
+
if (!target) return null;
|
|
1670
|
+
return { name: target, value: valueNode.value };
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
function getTypeofIdentifierTarget(argument) {
|
|
1674
|
+
const targetName = argument?.type === "Identifier" ? argument.name : null;
|
|
1675
|
+
if (!targetName) return null;
|
|
1676
|
+
|
|
1677
|
+
const isGlobalTarget = GLOBAL_BUILTIN_NAMES.has(targetName);
|
|
1678
|
+
if (!isGlobalTarget) return null;
|
|
1679
|
+
return targetName;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
function collectConditionalTypeofNames(node, branchValue, names) {
|
|
1683
|
+
if (!branchValue) return;
|
|
1684
|
+
|
|
1685
|
+
if (isFalseLiteral(node.alternate)) {
|
|
1686
|
+
collectBranchTypeofNames(node.consequent, true, names);
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
if (isFalseLiteral(node.consequent)) {
|
|
1691
|
+
collectBranchTypeofNames(node.alternate, true, names);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
function isFalseLiteral(node) {
|
|
1696
|
+
const isLiteral = node?.type === "Literal";
|
|
1697
|
+
const isFalse = node?.value === false;
|
|
1698
|
+
return isLiteral && isFalse;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function shouldSkipBranchGuard(node, branchValue) {
|
|
1702
|
+
if (node.type === "BinaryExpression") return true;
|
|
1703
|
+
const isLogical = node.type === "LogicalExpression";
|
|
1704
|
+
if (!isLogical) return false;
|
|
1705
|
+
|
|
1706
|
+
const isAnd = node.operator === "&&";
|
|
1707
|
+
const isOr = node.operator === "||";
|
|
1708
|
+
const isNullish = node.operator === "??";
|
|
1709
|
+
const isFalsyAnd = isAnd && !branchValue;
|
|
1710
|
+
const isTruthyOr = isOr && branchValue;
|
|
1711
|
+
if (isFalsyAnd) return true;
|
|
1712
|
+
if (isTruthyOr) return true;
|
|
1713
|
+
if (isNullish) return true;
|
|
1714
|
+
return false;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
function detectFeaturesFromAST(ast) {
|
|
1718
|
+
const foundFeatures = Object.create(null);
|
|
1719
|
+
visitArray(Object.keys(ES_FEATURES), (key) => {
|
|
1720
|
+
foundFeatures[key] = false;
|
|
1721
|
+
});
|
|
1722
|
+
visitArray(Object.keys(ES_GLOBAL_MIN_VERSION), (key) => {
|
|
1723
|
+
if (foundFeatures[key] === undefined) foundFeatures[key] = false;
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
const remaining = new Set(Object.keys(ES_FEATURES));
|
|
1727
|
+
const remainingGlobals = new Set(Object.keys(ES_GLOBAL_MIN_VERSION));
|
|
1728
|
+
const scope = createProgramScope(ast);
|
|
1729
|
+
traverseFeatures(
|
|
1730
|
+
ast,
|
|
1731
|
+
{
|
|
1732
|
+
scope,
|
|
1733
|
+
functionDepth: 0,
|
|
1734
|
+
parent: null,
|
|
1735
|
+
assignmentScope: scope,
|
|
1736
|
+
typeofDepth: 0,
|
|
1737
|
+
guardedGlobals: new Set(),
|
|
1738
|
+
},
|
|
1739
|
+
{ foundFeatures, remaining, remainingGlobals, receiverCache: new WeakMap() },
|
|
1740
|
+
);
|
|
164
1741
|
|
|
165
1742
|
return foundFeatures;
|
|
166
1743
|
}
|
|
@@ -170,4 +1747,6 @@ module.exports = {
|
|
|
170
1747
|
buildFeatureIndex,
|
|
171
1748
|
matchesFeature,
|
|
172
1749
|
detectFeaturesFromAST,
|
|
1750
|
+
getGlobalReferenceName,
|
|
1751
|
+
isIdentifierReference,
|
|
173
1752
|
};
|