@mintlify/common 1.0.1269 → 1.0.1271
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/dist/mdx/estreeReferences.d.ts +39 -0
- package/dist/mdx/estreeReferences.js +963 -0
- package/dist/mdx/plugins/remark/firstPartyMarkdownComponents.d.ts +3 -0
- package/dist/mdx/plugins/remark/firstPartyMarkdownComponents.js +29 -0
- package/dist/mdx/plugins/remark/remarkMdxRemoveUnknownJsx/index.d.ts +1 -1
- package/dist/mdx/plugins/remark/remarkVisibilityForMarkdown.d.ts +7 -1
- package/dist/mdx/plugins/remark/remarkVisibilityForMarkdown.js +581 -177
- package/dist/mdx/plugins/remark/staticEvaluator.d.ts +55 -0
- package/dist/mdx/plugins/remark/staticEvaluator.js +639 -0
- package/dist/mdx/plugins/remark/unfoldComponentsForMarkdown.d.ts +71 -0
- package/dist/mdx/plugins/remark/unfoldComponentsForMarkdown.js +1414 -0
- package/dist/mdx/remark-core.d.ts +7 -0
- package/dist/mdx/remark-core.js +32 -0
- package/dist/mdx/remark.d.ts +1 -7
- package/dist/mdx/remark.js +2 -32
- package/dist/mdx/snippets/getExportMap.js +33 -5
- package/dist/mdx/snippets/resolveAllImports.js +3 -2
- package/dist/mdx/snippets/resolveImport/exportCollision.d.ts +1 -0
- package/dist/mdx/snippets/resolveImport/exportCollision.js +2 -1
- package/dist/mdx/snippets/resolveImport/index.d.ts +11 -1
- package/dist/mdx/snippets/resolveImport/index.js +129 -16
- package/dist/mdx/snippets/resolveImport/resolveComponentWithContent.js +38 -5
- package/dist/mdx/snippets/resolveImport/snippetDeclarations.d.ts +56 -0
- package/dist/mdx/snippets/resolveImport/snippetDeclarations.js +655 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
import { isObject } from '../types/guards.js';
|
|
2
|
+
export const NON_CODE_KEYS = new Set([
|
|
3
|
+
'loc',
|
|
4
|
+
'range',
|
|
5
|
+
'start',
|
|
6
|
+
'end',
|
|
7
|
+
'comments',
|
|
8
|
+
'leadingComments',
|
|
9
|
+
'trailingComments',
|
|
10
|
+
]);
|
|
11
|
+
const FUNCTION_NODE_TYPES = new Set([
|
|
12
|
+
'ArrowFunctionExpression',
|
|
13
|
+
'FunctionExpression',
|
|
14
|
+
'FunctionDeclaration',
|
|
15
|
+
]);
|
|
16
|
+
/** Collects the names a binding pattern declares (untyped estree records). */
|
|
17
|
+
export function collectPatternNames(pattern, names) {
|
|
18
|
+
if (!isObject(pattern))
|
|
19
|
+
return;
|
|
20
|
+
const type = pattern.type;
|
|
21
|
+
if (type === 'Identifier') {
|
|
22
|
+
if (typeof pattern.name === 'string')
|
|
23
|
+
names.add(pattern.name);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (type === 'AssignmentPattern') {
|
|
27
|
+
collectPatternNames(pattern.left, names);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (type === 'RestElement') {
|
|
31
|
+
collectPatternNames(pattern.argument, names);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (type === 'ObjectPattern' && Array.isArray(pattern.properties)) {
|
|
35
|
+
for (const property of pattern.properties) {
|
|
36
|
+
if (!isObject(property))
|
|
37
|
+
continue;
|
|
38
|
+
if (property.type === 'RestElement')
|
|
39
|
+
collectPatternNames(property.argument, names);
|
|
40
|
+
else if (property.type === 'Property')
|
|
41
|
+
collectPatternNames(property.value, names);
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (type === 'ArrayPattern' && Array.isArray(pattern.elements)) {
|
|
46
|
+
for (const element of pattern.elements)
|
|
47
|
+
collectPatternNames(element, names);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function forEachCodeChild(node, callback) {
|
|
51
|
+
for (const [key, child] of Object.entries(node)) {
|
|
52
|
+
if (!NON_CODE_KEYS.has(key))
|
|
53
|
+
callback(child);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** `var` declarations hoist to the enclosing function, so they shadow its whole body. */
|
|
57
|
+
function collectVarNames(node, names) {
|
|
58
|
+
if (Array.isArray(node)) {
|
|
59
|
+
for (const child of node)
|
|
60
|
+
collectVarNames(child, names);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!isObject(node) || typeof node.type !== 'string')
|
|
64
|
+
return;
|
|
65
|
+
if (FUNCTION_NODE_TYPES.has(node.type) || node.type === 'ClassBody')
|
|
66
|
+
return;
|
|
67
|
+
if (node.type === 'VariableDeclaration' && node.kind === 'var') {
|
|
68
|
+
const declarations = Array.isArray(node.declarations) ? node.declarations : [];
|
|
69
|
+
for (const declarator of declarations) {
|
|
70
|
+
if (isObject(declarator))
|
|
71
|
+
collectPatternNames(declarator.id, names);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
forEachCodeChild(node, (child) => collectVarNames(child, names));
|
|
75
|
+
}
|
|
76
|
+
/** Block-scoped declarations made directly in a statement list. */
|
|
77
|
+
function collectLexicalNames(statements) {
|
|
78
|
+
const names = new Set();
|
|
79
|
+
for (const statement of statements) {
|
|
80
|
+
if (!isObject(statement))
|
|
81
|
+
continue;
|
|
82
|
+
if (statement.type === 'VariableDeclaration' && statement.kind !== 'var') {
|
|
83
|
+
const declarations = Array.isArray(statement.declarations) ? statement.declarations : [];
|
|
84
|
+
for (const declarator of declarations) {
|
|
85
|
+
if (isObject(declarator))
|
|
86
|
+
collectPatternNames(declarator.id, names);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if ((statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') &&
|
|
90
|
+
isObject(statement.id) &&
|
|
91
|
+
typeof statement.id.name === 'string') {
|
|
92
|
+
names.add(statement.id.name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return names;
|
|
96
|
+
}
|
|
97
|
+
function withNames(shadowed, names) {
|
|
98
|
+
const next = new Set(shadowed);
|
|
99
|
+
for (const name of names)
|
|
100
|
+
next.add(name);
|
|
101
|
+
return next;
|
|
102
|
+
}
|
|
103
|
+
function isJsxComponentName(name) {
|
|
104
|
+
return typeof name === 'string' && !/^[a-z]/.test(name) && !name.includes('-');
|
|
105
|
+
}
|
|
106
|
+
const MUTATING_METHODS = new Set([
|
|
107
|
+
'push',
|
|
108
|
+
'pop',
|
|
109
|
+
'shift',
|
|
110
|
+
'unshift',
|
|
111
|
+
'splice',
|
|
112
|
+
'sort',
|
|
113
|
+
'reverse',
|
|
114
|
+
'fill',
|
|
115
|
+
'copyWithin',
|
|
116
|
+
'set',
|
|
117
|
+
'add',
|
|
118
|
+
'delete',
|
|
119
|
+
'clear',
|
|
120
|
+
// Inherited by every object from `Object.prototype`
|
|
121
|
+
'__defineGetter__',
|
|
122
|
+
'__defineSetter__',
|
|
123
|
+
]);
|
|
124
|
+
const OBJECT_MUTATING_STATICS = new Set([
|
|
125
|
+
'assign',
|
|
126
|
+
'defineProperty',
|
|
127
|
+
'defineProperties',
|
|
128
|
+
'setPrototypeOf',
|
|
129
|
+
]);
|
|
130
|
+
const MUTATING_STATICS = new Map([
|
|
131
|
+
['Object', OBJECT_MUTATING_STATICS],
|
|
132
|
+
['Reflect', new Set(['set', 'defineProperty', 'deleteProperty', 'setPrototypeOf'])],
|
|
133
|
+
]);
|
|
134
|
+
/**
|
|
135
|
+
* Property names that reach `Object` or its mutating statics from any plain value, since every
|
|
136
|
+
* value's `constructor` chain leads to `Object` (or to `Function`, which compiles code).
|
|
137
|
+
*/
|
|
138
|
+
function isObjectReachingName(name) {
|
|
139
|
+
return name === 'constructor' || OBJECT_MUTATING_STATICS.has(name);
|
|
140
|
+
}
|
|
141
|
+
function propertyName(member) {
|
|
142
|
+
const property = member.property;
|
|
143
|
+
if (!isObject(property))
|
|
144
|
+
return undefined;
|
|
145
|
+
if (member.computed !== true && typeof property.name === 'string')
|
|
146
|
+
return property.name;
|
|
147
|
+
return property.type === 'Literal' && typeof property.value === 'string'
|
|
148
|
+
? property.value
|
|
149
|
+
: undefined;
|
|
150
|
+
}
|
|
151
|
+
const GLOBAL_OBJECT_NAMES = new Set(['globalThis', 'window', 'self', 'global']);
|
|
152
|
+
// Globals that change objects or run code without a traceable write, and the global object names
|
|
153
|
+
// that expose them as properties
|
|
154
|
+
const UNSAFE_GLOBAL_NAMES = new Set([
|
|
155
|
+
'Object',
|
|
156
|
+
'Reflect',
|
|
157
|
+
'Function',
|
|
158
|
+
'eval',
|
|
159
|
+
...GLOBAL_OBJECT_NAMES,
|
|
160
|
+
]);
|
|
161
|
+
// Global object identifiers read through a static key that is not unsafe, such as
|
|
162
|
+
// `window.location`. Any other use (aliases, computed keys, `window.Object`) can reach an unsafe
|
|
163
|
+
// global without naming it.
|
|
164
|
+
const SAFE_GLOBAL_OWNERS = new WeakSet();
|
|
165
|
+
function hasDynamicKey(node, key) {
|
|
166
|
+
const literalKey = isObject(key) &&
|
|
167
|
+
key.type === 'Literal' &&
|
|
168
|
+
(typeof key.value === 'string' || typeof key.value === 'number');
|
|
169
|
+
return node.computed === true && !literalKey;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* A call through a computed key that is not a literal, such as `value[key].assign(...)`, can reach
|
|
173
|
+
* `constructor` and from there `Object.assign`, so the key cannot be ruled out statically.
|
|
174
|
+
*/
|
|
175
|
+
function calleeHasDynamicKey(callee) {
|
|
176
|
+
let current = unwrapExpression(callee);
|
|
177
|
+
while (isObject(current) && current.type === 'MemberExpression') {
|
|
178
|
+
if (hasDynamicKey(current, current.property))
|
|
179
|
+
return true;
|
|
180
|
+
current = unwrapExpression(current.object);
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A pattern key that may pull `constructor`, a mutating `Object` static, or an unsafe global such
|
|
186
|
+
* as `Function` out of a value.
|
|
187
|
+
*/
|
|
188
|
+
function visitPatternKeys(pattern, visit) {
|
|
189
|
+
var _a;
|
|
190
|
+
if (!isObject(pattern))
|
|
191
|
+
return;
|
|
192
|
+
switch (pattern.type) {
|
|
193
|
+
case 'AssignmentPattern':
|
|
194
|
+
visitPatternKeys(pattern.left, visit);
|
|
195
|
+
return;
|
|
196
|
+
case 'RestElement':
|
|
197
|
+
visitPatternKeys(pattern.argument, visit);
|
|
198
|
+
return;
|
|
199
|
+
case 'ArrayPattern':
|
|
200
|
+
if (Array.isArray(pattern.elements)) {
|
|
201
|
+
for (const element of pattern.elements)
|
|
202
|
+
visitPatternKeys(element, visit);
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
case 'ObjectPattern': {
|
|
206
|
+
const properties = Array.isArray(pattern.properties) ? pattern.properties : [];
|
|
207
|
+
for (const property of properties) {
|
|
208
|
+
if (!isObject(property))
|
|
209
|
+
continue;
|
|
210
|
+
if (property.type === 'RestElement') {
|
|
211
|
+
visitPatternKeys(property.argument, visit);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const key = property.key;
|
|
215
|
+
const name = property.computed !== true && isObject(key) && typeof key.name === 'string'
|
|
216
|
+
? key.name
|
|
217
|
+
: isObject(key) && key.type === 'Literal'
|
|
218
|
+
? String(key.value)
|
|
219
|
+
: undefined;
|
|
220
|
+
if (hasDynamicKey(property, key) ||
|
|
221
|
+
(name !== undefined && (isObjectReachingName(name) || UNSAFE_GLOBAL_NAMES.has(name)))) {
|
|
222
|
+
(_a = visit.unknownWrite) === null || _a === void 0 ? void 0 : _a.call(visit);
|
|
223
|
+
}
|
|
224
|
+
visitPatternKeys(property.value, visit);
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
default:
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// `Object` and `Reflect` identifiers used as `Object.method(...)` with a static method name. Any
|
|
233
|
+
// other use (aliases, destructuring, `.call`, computed names) can reach a mutating static.
|
|
234
|
+
const DIRECT_STATIC_OWNERS = new WeakSet();
|
|
235
|
+
function markDirectStaticCall(call) {
|
|
236
|
+
const callee = call.callee;
|
|
237
|
+
if (!isObject(callee) || callee.type !== 'MemberExpression')
|
|
238
|
+
return;
|
|
239
|
+
const owner = callee.object;
|
|
240
|
+
if (propertyName(callee) !== undefined &&
|
|
241
|
+
isObject(owner) &&
|
|
242
|
+
owner.type === 'Identifier' &&
|
|
243
|
+
typeof owner.name === 'string' &&
|
|
244
|
+
MUTATING_STATICS.has(owner.name)) {
|
|
245
|
+
DIRECT_STATIC_OWNERS.add(owner);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** The object a call changes in place, if the callee is a known mutating method. */
|
|
249
|
+
function mutatedByCall(call) {
|
|
250
|
+
const callee = call.callee;
|
|
251
|
+
if (!isObject(callee) || callee.type !== 'MemberExpression')
|
|
252
|
+
return undefined;
|
|
253
|
+
const method = propertyName(callee);
|
|
254
|
+
if (method === undefined)
|
|
255
|
+
return undefined;
|
|
256
|
+
const owner = callee.object;
|
|
257
|
+
if (isObject(owner) && owner.type === 'Identifier' && typeof owner.name === 'string') {
|
|
258
|
+
const statics = MUTATING_STATICS.get(owner.name);
|
|
259
|
+
if (statics !== undefined) {
|
|
260
|
+
return statics.has(method) && Array.isArray(call.arguments) ? call.arguments[0] : undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return MUTATING_METHODS.has(method) ? owner : undefined;
|
|
264
|
+
}
|
|
265
|
+
function visitIdentifier(identifier, shadowed, visit) {
|
|
266
|
+
var _a;
|
|
267
|
+
if (isObject(identifier) &&
|
|
268
|
+
typeof identifier.name === 'string' &&
|
|
269
|
+
!shadowed.has(identifier.name)) {
|
|
270
|
+
if (identifier.name === 'eval' ||
|
|
271
|
+
identifier.name === 'Function' ||
|
|
272
|
+
(GLOBAL_OBJECT_NAMES.has(identifier.name) && !SAFE_GLOBAL_OWNERS.has(identifier)) ||
|
|
273
|
+
(MUTATING_STATICS.has(identifier.name) && !DIRECT_STATIC_OWNERS.has(identifier))) {
|
|
274
|
+
(_a = visit.unknownWrite) === null || _a === void 0 ? void 0 : _a.call(visit);
|
|
275
|
+
}
|
|
276
|
+
visit.reference(identifier);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/** References made by a binding pattern: destructuring defaults and computed keys. */
|
|
280
|
+
function visitPatternReferences(pattern, shadowed, visit) {
|
|
281
|
+
if (!isObject(pattern))
|
|
282
|
+
return;
|
|
283
|
+
switch (pattern.type) {
|
|
284
|
+
case 'AssignmentPattern':
|
|
285
|
+
visitPatternReferences(pattern.left, shadowed, visit);
|
|
286
|
+
visitReferences(pattern.right, shadowed, visit);
|
|
287
|
+
return;
|
|
288
|
+
case 'RestElement':
|
|
289
|
+
visitPatternReferences(pattern.argument, shadowed, visit);
|
|
290
|
+
return;
|
|
291
|
+
case 'ObjectPattern': {
|
|
292
|
+
const properties = Array.isArray(pattern.properties) ? pattern.properties : [];
|
|
293
|
+
for (const property of properties) {
|
|
294
|
+
if (!isObject(property))
|
|
295
|
+
continue;
|
|
296
|
+
if (property.type === 'RestElement') {
|
|
297
|
+
visitPatternReferences(property.argument, shadowed, visit);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (property.computed === true)
|
|
301
|
+
visitReferences(property.key, shadowed, visit);
|
|
302
|
+
visitPatternReferences(property.value, shadowed, visit);
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
case 'ArrayPattern': {
|
|
307
|
+
const elements = Array.isArray(pattern.elements) ? pattern.elements : [];
|
|
308
|
+
for (const element of elements)
|
|
309
|
+
visitPatternReferences(element, shadowed, visit);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
case 'MemberExpression':
|
|
313
|
+
visitReferences(pattern, shadowed, visit);
|
|
314
|
+
return;
|
|
315
|
+
default:
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Visits every identifier that refers to a module-level binding: function, block, catch, and
|
|
321
|
+
* loop bindings shadow their scopes, and property keys and intrinsic JSX tags are not references.
|
|
322
|
+
*/
|
|
323
|
+
export function visitReferences(node, shadowed, visit) {
|
|
324
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
325
|
+
if (Array.isArray(node)) {
|
|
326
|
+
for (const child of node)
|
|
327
|
+
visitReferences(child, shadowed, visit);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (!isObject(node) || typeof node.type !== 'string')
|
|
331
|
+
return;
|
|
332
|
+
const type = node.type;
|
|
333
|
+
if (FUNCTION_NODE_TYPES.has(type)) {
|
|
334
|
+
if (visit.eagerOnly === true)
|
|
335
|
+
return;
|
|
336
|
+
const params = Array.isArray(node.params) ? node.params : [];
|
|
337
|
+
const paramNames = new Set();
|
|
338
|
+
for (const param of params)
|
|
339
|
+
collectPatternNames(param, paramNames);
|
|
340
|
+
if (type === 'FunctionExpression' && isObject(node.id) && typeof node.id.name === 'string') {
|
|
341
|
+
paramNames.add(node.id.name);
|
|
342
|
+
}
|
|
343
|
+
// Parameter defaults see the parameters and the outer scope, never the body's declarations
|
|
344
|
+
const paramScope = withNames(shadowed, paramNames);
|
|
345
|
+
for (const param of params) {
|
|
346
|
+
visitPatternKeys(param, visit);
|
|
347
|
+
visitPatternReferences(param, paramScope, visit);
|
|
348
|
+
}
|
|
349
|
+
const bodyNames = new Set();
|
|
350
|
+
collectVarNames(node.body, bodyNames);
|
|
351
|
+
const body = node.body;
|
|
352
|
+
const blockBody = isObject(body) && Array.isArray(body.body) ? body.body : undefined;
|
|
353
|
+
if (blockBody !== undefined) {
|
|
354
|
+
for (const name of collectLexicalNames(blockBody))
|
|
355
|
+
bodyNames.add(name);
|
|
356
|
+
}
|
|
357
|
+
visitReferences(blockBody !== null && blockBody !== void 0 ? blockBody : body, withNames(paramScope, bodyNames), visit);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
switch (type) {
|
|
361
|
+
case 'Identifier':
|
|
362
|
+
visitIdentifier(node, shadowed, visit);
|
|
363
|
+
return;
|
|
364
|
+
case 'AssignmentExpression':
|
|
365
|
+
(_a = visit.write) === null || _a === void 0 ? void 0 : _a.call(visit, node.left, shadowed);
|
|
366
|
+
visitPatternKeys(node.left, visit);
|
|
367
|
+
visitReferences(node.left, shadowed, visit);
|
|
368
|
+
visitReferences(node.right, shadowed, visit);
|
|
369
|
+
return;
|
|
370
|
+
case 'UpdateExpression':
|
|
371
|
+
(_b = visit.write) === null || _b === void 0 ? void 0 : _b.call(visit, node.argument, shadowed);
|
|
372
|
+
visitReferences(node.argument, shadowed, visit);
|
|
373
|
+
return;
|
|
374
|
+
case 'UnaryExpression':
|
|
375
|
+
if (node.operator === 'delete') {
|
|
376
|
+
const deleted = unwrapExpression(node.argument);
|
|
377
|
+
if (isObject(deleted) && deleted.type === 'MemberExpression') {
|
|
378
|
+
(_c = visit.mutate) === null || _c === void 0 ? void 0 : _c.call(visit, deleted.object, shadowed);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
visitReferences(node.argument, shadowed, visit);
|
|
382
|
+
return;
|
|
383
|
+
case 'CallExpression': {
|
|
384
|
+
const mutated = mutatedByCall(node);
|
|
385
|
+
if (mutated !== undefined)
|
|
386
|
+
(_d = visit.mutate) === null || _d === void 0 ? void 0 : _d.call(visit, mutated, shadowed);
|
|
387
|
+
if (calleeHasDynamicKey(node.callee))
|
|
388
|
+
(_e = visit.unknownWrite) === null || _e === void 0 ? void 0 : _e.call(visit);
|
|
389
|
+
markDirectStaticCall(node);
|
|
390
|
+
forEachCodeChild(node, (child) => visitReferences(child, shadowed, visit));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
case 'NewExpression':
|
|
394
|
+
case 'TaggedTemplateExpression':
|
|
395
|
+
if (calleeHasDynamicKey(type === 'NewExpression' ? node.callee : node.tag)) {
|
|
396
|
+
(_f = visit.unknownWrite) === null || _f === void 0 ? void 0 : _f.call(visit);
|
|
397
|
+
}
|
|
398
|
+
forEachCodeChild(node, (child) => visitReferences(child, shadowed, visit));
|
|
399
|
+
return;
|
|
400
|
+
case 'ClassDeclaration':
|
|
401
|
+
case 'ClassExpression': {
|
|
402
|
+
visitReferences(node.superClass, shadowed, visit);
|
|
403
|
+
const ownName = type === 'ClassExpression' && isObject(node.id) && typeof node.id.name === 'string'
|
|
404
|
+
? [node.id.name]
|
|
405
|
+
: [];
|
|
406
|
+
visitReferences(node.body, withNames(shadowed, ownName), visit);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
case 'BlockStatement':
|
|
410
|
+
case 'StaticBlock': {
|
|
411
|
+
const body = Array.isArray(node.body) ? node.body : [];
|
|
412
|
+
visitReferences(body, withNames(shadowed, collectLexicalNames(body)), visit);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
case 'SwitchStatement': {
|
|
416
|
+
visitReferences(node.discriminant, shadowed, visit);
|
|
417
|
+
const cases = Array.isArray(node.cases) ? node.cases : [];
|
|
418
|
+
const statements = cases.flatMap((switchCase) => isObject(switchCase) && Array.isArray(switchCase.consequent) ? switchCase.consequent : []);
|
|
419
|
+
visitReferences(cases, withNames(shadowed, collectLexicalNames(statements)), visit);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
case 'ForStatement': {
|
|
423
|
+
const inner = withNames(shadowed, collectLexicalNames([node.init]));
|
|
424
|
+
forEachCodeChild(node, (child) => visitReferences(child, inner, visit));
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
case 'ForInStatement':
|
|
428
|
+
case 'ForOfStatement': {
|
|
429
|
+
const left = node.left;
|
|
430
|
+
const inner = withNames(shadowed, collectLexicalNames([left]));
|
|
431
|
+
// The iterable is evaluated before the loop bindings exist
|
|
432
|
+
visitReferences(node.right, shadowed, visit);
|
|
433
|
+
if (isObject(left) && left.type === 'VariableDeclaration') {
|
|
434
|
+
visitReferences(left, inner, visit);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
(_g = visit.write) === null || _g === void 0 ? void 0 : _g.call(visit, left, shadowed);
|
|
438
|
+
visitPatternKeys(left, visit);
|
|
439
|
+
visitReferences(left, shadowed, visit);
|
|
440
|
+
}
|
|
441
|
+
visitReferences(node.body, inner, visit);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
case 'CatchClause': {
|
|
445
|
+
const names = new Set();
|
|
446
|
+
collectPatternNames(node.param, names);
|
|
447
|
+
const inner = withNames(shadowed, names);
|
|
448
|
+
visitPatternKeys(node.param, visit);
|
|
449
|
+
visitPatternReferences(node.param, inner, visit);
|
|
450
|
+
visitReferences(node.body, inner, visit);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
case 'VariableDeclarator':
|
|
454
|
+
visitPatternKeys(node.id, visit);
|
|
455
|
+
visitPatternReferences(node.id, shadowed, visit);
|
|
456
|
+
visitReferences(node.init, shadowed, visit);
|
|
457
|
+
return;
|
|
458
|
+
case 'MemberExpression': {
|
|
459
|
+
// `value.constructor` reaches `Object` from any plain object, and its mutating statics can
|
|
460
|
+
// be read off whatever that was stored in. Direct `Object.assign(...)` calls are tracked.
|
|
461
|
+
const name = propertyName(node);
|
|
462
|
+
const owner = node.object;
|
|
463
|
+
const isDirectStatic = isObject(owner) &&
|
|
464
|
+
owner.type === 'Identifier' &&
|
|
465
|
+
typeof owner.name === 'string' &&
|
|
466
|
+
MUTATING_STATICS.has(owner.name);
|
|
467
|
+
if (name !== undefined && isObjectReachingName(name) && !isDirectStatic) {
|
|
468
|
+
(_h = visit.unknownWrite) === null || _h === void 0 ? void 0 : _h.call(visit);
|
|
469
|
+
}
|
|
470
|
+
if (name !== undefined && UNSAFE_GLOBAL_NAMES.has(name))
|
|
471
|
+
(_j = visit.unknownWrite) === null || _j === void 0 ? void 0 : _j.call(visit);
|
|
472
|
+
if (name !== undefined &&
|
|
473
|
+
!isObjectReachingName(name) &&
|
|
474
|
+
isObject(owner) &&
|
|
475
|
+
owner.type === 'Identifier' &&
|
|
476
|
+
typeof owner.name === 'string' &&
|
|
477
|
+
GLOBAL_OBJECT_NAMES.has(owner.name)) {
|
|
478
|
+
SAFE_GLOBAL_OWNERS.add(owner);
|
|
479
|
+
}
|
|
480
|
+
visitReferences(node.object, shadowed, visit);
|
|
481
|
+
if (node.computed === true)
|
|
482
|
+
visitReferences(node.property, shadowed, visit);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
case 'Property':
|
|
486
|
+
case 'PropertyDefinition':
|
|
487
|
+
case 'MethodDefinition':
|
|
488
|
+
if (node.computed === true)
|
|
489
|
+
visitReferences(node.key, shadowed, visit);
|
|
490
|
+
// Instance field initializers run per construction, not when the class is defined
|
|
491
|
+
if (visit.eagerOnly === true && type === 'PropertyDefinition' && node.static !== true)
|
|
492
|
+
return;
|
|
493
|
+
visitReferences(node.value, shadowed, visit);
|
|
494
|
+
return;
|
|
495
|
+
case 'LabeledStatement':
|
|
496
|
+
visitReferences(node.body, shadowed, visit);
|
|
497
|
+
return;
|
|
498
|
+
case 'WithStatement':
|
|
499
|
+
(_k = visit.unknownWrite) === null || _k === void 0 ? void 0 : _k.call(visit);
|
|
500
|
+
visitReferences(node.object, shadowed, visit);
|
|
501
|
+
visitReferences(node.body, shadowed, visit);
|
|
502
|
+
return;
|
|
503
|
+
case 'JSXOpeningElement':
|
|
504
|
+
case 'JSXClosingElement': {
|
|
505
|
+
const name = node.name;
|
|
506
|
+
if (isObject(name) && name.type === 'JSXIdentifier' && isJsxComponentName(name.name)) {
|
|
507
|
+
visitIdentifier(name, shadowed, visit);
|
|
508
|
+
}
|
|
509
|
+
else if (isObject(name) && name.type === 'JSXMemberExpression') {
|
|
510
|
+
visitReferences(name, shadowed, visit);
|
|
511
|
+
}
|
|
512
|
+
visitReferences(node.attributes, shadowed, visit);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
case 'JSXMemberExpression': {
|
|
516
|
+
const object = node.object;
|
|
517
|
+
if (isObject(object) && object.type === 'JSXIdentifier')
|
|
518
|
+
visitIdentifier(object, shadowed, visit);
|
|
519
|
+
else
|
|
520
|
+
visitReferences(object, shadowed, visit);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
case 'JSXAttribute':
|
|
524
|
+
visitReferences(node.value, shadowed, visit);
|
|
525
|
+
return;
|
|
526
|
+
case 'BreakStatement':
|
|
527
|
+
case 'ContinueStatement':
|
|
528
|
+
case 'MetaProperty':
|
|
529
|
+
case 'JSXIdentifier':
|
|
530
|
+
case 'JSXNamespacedName':
|
|
531
|
+
return;
|
|
532
|
+
default:
|
|
533
|
+
forEachCodeChild(node, (child) => visitReferences(child, shadowed, visit));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
export function collectFreeReferences(node, eagerOnly = false) {
|
|
537
|
+
const references = new Set();
|
|
538
|
+
visitReferences(node, new Set(), {
|
|
539
|
+
reference: (identifier) => {
|
|
540
|
+
if (typeof identifier.name === 'string')
|
|
541
|
+
references.add(identifier.name);
|
|
542
|
+
},
|
|
543
|
+
eagerOnly,
|
|
544
|
+
});
|
|
545
|
+
return references;
|
|
546
|
+
}
|
|
547
|
+
/** Marks a write through an object that cannot be traced to particular module bindings. */
|
|
548
|
+
export const UNKNOWN_WRITE_TARGET = '*';
|
|
549
|
+
const WRAPPER_NODE_TYPES = new Set([
|
|
550
|
+
'ParenthesizedExpression',
|
|
551
|
+
'TSAsExpression',
|
|
552
|
+
'TSSatisfiesExpression',
|
|
553
|
+
'TSNonNullExpression',
|
|
554
|
+
'TSTypeAssertion',
|
|
555
|
+
'ChainExpression',
|
|
556
|
+
]);
|
|
557
|
+
/** A static property or index name, when the key cannot change at runtime. */
|
|
558
|
+
function staticMemberKey(member) {
|
|
559
|
+
const property = member.property;
|
|
560
|
+
if (!isObject(property))
|
|
561
|
+
return undefined;
|
|
562
|
+
if (member.computed !== true)
|
|
563
|
+
return typeof property.name === 'string' ? property.name : undefined;
|
|
564
|
+
if (property.type === 'Literal' &&
|
|
565
|
+
(typeof property.value === 'string' || typeof property.value === 'number')) {
|
|
566
|
+
return String(property.value);
|
|
567
|
+
}
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
function propertyKeyName(property) {
|
|
571
|
+
if (property.computed === true) {
|
|
572
|
+
const key = property.key;
|
|
573
|
+
if (isObject(key) &&
|
|
574
|
+
key.type === 'Literal' &&
|
|
575
|
+
(typeof key.value === 'string' || typeof key.value === 'number')) {
|
|
576
|
+
return String(key.value);
|
|
577
|
+
}
|
|
578
|
+
return undefined;
|
|
579
|
+
}
|
|
580
|
+
const key = property.key;
|
|
581
|
+
if (isObject(key) && typeof key.name === 'string')
|
|
582
|
+
return key.name;
|
|
583
|
+
if (isObject(key) && key.type === 'Literal' && typeof key.value === 'string')
|
|
584
|
+
return key.value;
|
|
585
|
+
return undefined;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* The value a member chain reads out of a fresh object or array. A spread or a key that is not
|
|
589
|
+
* static can hide a shared object, which the caller treats as an unknown write. A missing
|
|
590
|
+
* property is a write on the fresh wrapper itself.
|
|
591
|
+
*/
|
|
592
|
+
function readFreshMember(wrapper, member) {
|
|
593
|
+
const key = staticMemberKey(member);
|
|
594
|
+
if (key === undefined)
|
|
595
|
+
return 'unknown';
|
|
596
|
+
const node = unwrapExpression(wrapper);
|
|
597
|
+
if (!isObject(node))
|
|
598
|
+
return 'unknown';
|
|
599
|
+
if (node.type === 'ObjectExpression') {
|
|
600
|
+
const properties = Array.isArray(node.properties) ? node.properties : [];
|
|
601
|
+
let found = 'contained';
|
|
602
|
+
for (const property of properties) {
|
|
603
|
+
if (!isObject(property))
|
|
604
|
+
continue;
|
|
605
|
+
if (property.type === 'SpreadElement') {
|
|
606
|
+
found = 'unknown';
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (property.type !== 'Property')
|
|
610
|
+
continue;
|
|
611
|
+
const name = propertyKeyName(property);
|
|
612
|
+
if (name === undefined) {
|
|
613
|
+
found = 'unknown';
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
if (name === key)
|
|
617
|
+
found = property.value;
|
|
618
|
+
}
|
|
619
|
+
return found;
|
|
620
|
+
}
|
|
621
|
+
if (node.type === 'ArrayExpression') {
|
|
622
|
+
if (!/^\d+$/.test(key))
|
|
623
|
+
return 'contained';
|
|
624
|
+
const index = Number(key);
|
|
625
|
+
const elements = Array.isArray(node.elements) ? node.elements : [];
|
|
626
|
+
let cursor = 0;
|
|
627
|
+
for (const element of elements) {
|
|
628
|
+
if (isObject(element) && element.type === 'SpreadElement')
|
|
629
|
+
return 'unknown';
|
|
630
|
+
if (cursor === index)
|
|
631
|
+
return element !== null && element !== void 0 ? element : 'contained';
|
|
632
|
+
cursor += 1;
|
|
633
|
+
}
|
|
634
|
+
return 'contained';
|
|
635
|
+
}
|
|
636
|
+
return 'unknown';
|
|
637
|
+
}
|
|
638
|
+
/** The expression inside parentheses and type assertions, which do not change what is written. */
|
|
639
|
+
function unwrapExpression(node) {
|
|
640
|
+
let current = node;
|
|
641
|
+
while (isObject(current) &&
|
|
642
|
+
typeof current.type === 'string' &&
|
|
643
|
+
WRAPPER_NODE_TYPES.has(current.type)) {
|
|
644
|
+
current = current.expression;
|
|
645
|
+
}
|
|
646
|
+
return current;
|
|
647
|
+
}
|
|
648
|
+
/** Names bound without an initializer: parameters, loop variables, and catch bindings. */
|
|
649
|
+
function collectUntraceableBindingNames(node, names) {
|
|
650
|
+
if (Array.isArray(node)) {
|
|
651
|
+
for (const child of node)
|
|
652
|
+
collectUntraceableBindingNames(child, names);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (!isObject(node) || typeof node.type !== 'string')
|
|
656
|
+
return;
|
|
657
|
+
if (FUNCTION_NODE_TYPES.has(node.type) && Array.isArray(node.params)) {
|
|
658
|
+
for (const param of node.params)
|
|
659
|
+
collectPatternNames(param, names);
|
|
660
|
+
}
|
|
661
|
+
if (node.type === 'CatchClause')
|
|
662
|
+
collectPatternNames(node.param, names);
|
|
663
|
+
if (node.type === 'VariableDeclarator' && node.init == null)
|
|
664
|
+
collectPatternNames(node.id, names);
|
|
665
|
+
if ((node.type === 'ForOfStatement' || node.type === 'ForInStatement') && isObject(node.left)) {
|
|
666
|
+
const left = node.left;
|
|
667
|
+
if (left.type === 'VariableDeclaration' && Array.isArray(left.declarations)) {
|
|
668
|
+
for (const declarator of left.declarations) {
|
|
669
|
+
if (isObject(declarator))
|
|
670
|
+
collectPatternNames(declarator.id, names);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
forEachCodeChild(node, (child) => collectUntraceableBindingNames(child, names));
|
|
675
|
+
}
|
|
676
|
+
/** Every declared local name mapped to the names its initializer references. */
|
|
677
|
+
function collectLocalInitializers(node, initializers) {
|
|
678
|
+
if (Array.isArray(node)) {
|
|
679
|
+
for (const child of node)
|
|
680
|
+
collectLocalInitializers(child, initializers);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (!isObject(node))
|
|
684
|
+
return;
|
|
685
|
+
const addSources = (target, value) => {
|
|
686
|
+
var _a;
|
|
687
|
+
const names = new Set();
|
|
688
|
+
collectPatternNames(target, names);
|
|
689
|
+
const references = collectFreeReferences(value);
|
|
690
|
+
for (const name of names) {
|
|
691
|
+
const existing = (_a = initializers.get(name)) !== null && _a !== void 0 ? _a : new Set();
|
|
692
|
+
for (const reference of references)
|
|
693
|
+
existing.add(reference);
|
|
694
|
+
initializers.set(name, existing);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
if (node.type === 'VariableDeclarator')
|
|
698
|
+
addSources(node.id, node.init);
|
|
699
|
+
// A later assignment can make a local alias a module object too
|
|
700
|
+
if (node.type === 'AssignmentExpression')
|
|
701
|
+
addSources(unwrapExpression(node.left), node.right);
|
|
702
|
+
forEachCodeChild(node, (child) => collectLocalInitializers(child, initializers));
|
|
703
|
+
}
|
|
704
|
+
const CALL_NODE_TYPES = new Set(['CallExpression', 'NewExpression', 'TaggedTemplateExpression']);
|
|
705
|
+
/**
|
|
706
|
+
* Whether `node` may evaluate to a value read through a computed key that is not a literal, or to
|
|
707
|
+
* a value derived from one: a tainted name, a member of one, or a wrapper holding one.
|
|
708
|
+
*/
|
|
709
|
+
function mayBeTainted(node, tainted) {
|
|
710
|
+
const value = unwrapExpression(node);
|
|
711
|
+
if (!isObject(value))
|
|
712
|
+
return false;
|
|
713
|
+
switch (value.type) {
|
|
714
|
+
case 'Identifier':
|
|
715
|
+
return typeof value.name === 'string' && tainted.has(value.name);
|
|
716
|
+
case 'MemberExpression':
|
|
717
|
+
return hasDynamicKey(value, value.property) || mayBeTainted(value.object, tainted);
|
|
718
|
+
case 'LogicalExpression':
|
|
719
|
+
return mayBeTainted(value.left, tainted) || mayBeTainted(value.right, tainted);
|
|
720
|
+
case 'ConditionalExpression':
|
|
721
|
+
return mayBeTainted(value.consequent, tainted) || mayBeTainted(value.alternate, tainted);
|
|
722
|
+
case 'SequenceExpression': {
|
|
723
|
+
const expressions = Array.isArray(value.expressions) ? value.expressions : [];
|
|
724
|
+
return mayBeTainted(expressions[expressions.length - 1], tainted);
|
|
725
|
+
}
|
|
726
|
+
case 'AwaitExpression':
|
|
727
|
+
case 'SpreadElement':
|
|
728
|
+
return mayBeTainted(value.argument, tainted);
|
|
729
|
+
case 'ArrayExpression':
|
|
730
|
+
return Array.isArray(value.elements)
|
|
731
|
+
? value.elements.some((element) => mayBeTainted(element, tainted))
|
|
732
|
+
: false;
|
|
733
|
+
case 'ObjectExpression':
|
|
734
|
+
return Array.isArray(value.properties)
|
|
735
|
+
? value.properties.some((property) => isObject(property) &&
|
|
736
|
+
mayBeTainted(property.type === 'SpreadElement' ? property : property.value, tainted))
|
|
737
|
+
: false;
|
|
738
|
+
default:
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/** Adds the names bound from a tainted value, such as `const C = value[key]` or `const D = C`. */
|
|
743
|
+
function collectTaintedNames(node, tainted) {
|
|
744
|
+
if (Array.isArray(node)) {
|
|
745
|
+
for (const child of node)
|
|
746
|
+
collectTaintedNames(child, tainted);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (!isObject(node))
|
|
750
|
+
return;
|
|
751
|
+
if (node.type === 'VariableDeclarator' && mayBeTainted(node.init, tainted)) {
|
|
752
|
+
collectPatternNames(node.id, tainted);
|
|
753
|
+
}
|
|
754
|
+
if (node.type === 'AssignmentExpression' && mayBeTainted(node.right, tainted)) {
|
|
755
|
+
collectPatternNames(unwrapExpression(node.left), tainted);
|
|
756
|
+
}
|
|
757
|
+
forEachCodeChild(node, (child) => collectTaintedNames(child, tainted));
|
|
758
|
+
}
|
|
759
|
+
const REFLECT_INVOKERS = new Set(['apply', 'construct']);
|
|
760
|
+
/**
|
|
761
|
+
* Whether `node` calls a tainted value: directly, through a member such as `.call` or `[0]`, or
|
|
762
|
+
* through `Reflect.apply`. Such a value may be `Function` or an `Object` static, either of which
|
|
763
|
+
* can change objects without a traceable write.
|
|
764
|
+
*/
|
|
765
|
+
function callsTainted(node, tainted, eagerOnly) {
|
|
766
|
+
var _a;
|
|
767
|
+
if (Array.isArray(node))
|
|
768
|
+
return node.some((child) => callsTainted(child, tainted, eagerOnly));
|
|
769
|
+
if (!isObject(node) || typeof node.type !== 'string')
|
|
770
|
+
return false;
|
|
771
|
+
if (eagerOnly && FUNCTION_NODE_TYPES.has(node.type))
|
|
772
|
+
return false;
|
|
773
|
+
if (CALL_NODE_TYPES.has(node.type)) {
|
|
774
|
+
const callee = node.type === 'TaggedTemplateExpression' ? node.tag : node.callee;
|
|
775
|
+
if (mayBeTainted(callee, tainted))
|
|
776
|
+
return true;
|
|
777
|
+
const member = unwrapExpression(callee);
|
|
778
|
+
const args = Array.isArray(node.arguments) ? node.arguments : [];
|
|
779
|
+
if (isObject(member) &&
|
|
780
|
+
member.type === 'MemberExpression' &&
|
|
781
|
+
isObject(member.object) &&
|
|
782
|
+
member.object.type === 'Identifier' &&
|
|
783
|
+
member.object.name === 'Reflect' &&
|
|
784
|
+
REFLECT_INVOKERS.has((_a = propertyName(member)) !== null && _a !== void 0 ? _a : '') &&
|
|
785
|
+
mayBeTainted(args[0], tainted)) {
|
|
786
|
+
return true;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
let found = false;
|
|
790
|
+
forEachCodeChild(node, (child) => {
|
|
791
|
+
found || (found = callsTainted(child, tainted, eagerOnly));
|
|
792
|
+
});
|
|
793
|
+
return found;
|
|
794
|
+
}
|
|
795
|
+
/** Whether `node` calls anything while it runs, outside function bodies. */
|
|
796
|
+
export function hasEagerCall(node) {
|
|
797
|
+
if (Array.isArray(node))
|
|
798
|
+
return node.some(hasEagerCall);
|
|
799
|
+
if (!isObject(node) || typeof node.type !== 'string')
|
|
800
|
+
return false;
|
|
801
|
+
if (FUNCTION_NODE_TYPES.has(node.type))
|
|
802
|
+
return false;
|
|
803
|
+
if (CALL_NODE_TYPES.has(node.type))
|
|
804
|
+
return true;
|
|
805
|
+
if (node.type === 'PropertyDefinition' && node.static !== true) {
|
|
806
|
+
return node.computed === true && hasEagerCall(node.key);
|
|
807
|
+
}
|
|
808
|
+
let found = false;
|
|
809
|
+
forEachCodeChild(node, (child) => {
|
|
810
|
+
found || (found = hasEagerCall(child));
|
|
811
|
+
});
|
|
812
|
+
return found;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Module-level names reassigned, and names whose objects are written through, in `node`. A write
|
|
816
|
+
* through a local counts as a write to every name the local's initializer references, since the
|
|
817
|
+
* local may alias that object; a local initialized from fresh values passes nothing on.
|
|
818
|
+
*/
|
|
819
|
+
export function collectModuleWrites(node, eagerOnly = false) {
|
|
820
|
+
const writes = { reassigned: new Set(), mutated: new Set() };
|
|
821
|
+
const initializers = new Map();
|
|
822
|
+
collectLocalInitializers(node, initializers);
|
|
823
|
+
// Initializers are tracked by name, so a name also bound without one can never be traced
|
|
824
|
+
const untraceable = new Set();
|
|
825
|
+
collectUntraceableBindingNames(node, untraceable);
|
|
826
|
+
const tainted = new Set();
|
|
827
|
+
let taintedCount = -1;
|
|
828
|
+
while (tainted.size !== taintedCount) {
|
|
829
|
+
taintedCount = tainted.size;
|
|
830
|
+
collectTaintedNames(node, tainted);
|
|
831
|
+
}
|
|
832
|
+
if (callsTainted(node, tainted, eagerOnly))
|
|
833
|
+
writes.mutated.add(UNKNOWN_WRITE_TARGET);
|
|
834
|
+
const markObject = (root, shadowed, seen) => {
|
|
835
|
+
if (!shadowed.has(root)) {
|
|
836
|
+
writes.mutated.add(root);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (seen.has(root))
|
|
840
|
+
return;
|
|
841
|
+
seen.add(root);
|
|
842
|
+
// Parameters, loop variables, and catch bindings may hold any module object
|
|
843
|
+
const sources = initializers.get(root);
|
|
844
|
+
if (sources === undefined || untraceable.has(root)) {
|
|
845
|
+
writes.mutated.add(UNKNOWN_WRITE_TARGET);
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
for (const source of sources)
|
|
849
|
+
markObject(source, new Set(), seen);
|
|
850
|
+
};
|
|
851
|
+
const mutate = (object, shadowed) => {
|
|
852
|
+
const members = [];
|
|
853
|
+
let root = unwrapExpression(object);
|
|
854
|
+
while (isObject(root) && root.type === 'MemberExpression') {
|
|
855
|
+
members.push(root);
|
|
856
|
+
root = unwrapExpression(root.object);
|
|
857
|
+
}
|
|
858
|
+
if (!isObject(root))
|
|
859
|
+
return;
|
|
860
|
+
if (root.type === 'ObjectExpression' || root.type === 'ArrayExpression') {
|
|
861
|
+
// Members were collected outer-first; the one next to the literal is last
|
|
862
|
+
let current = root;
|
|
863
|
+
for (let index = members.length - 1; index >= 0; index -= 1) {
|
|
864
|
+
const member = members[index];
|
|
865
|
+
if (member === undefined)
|
|
866
|
+
return;
|
|
867
|
+
const read = readFreshMember(current, member);
|
|
868
|
+
if (read === 'contained')
|
|
869
|
+
return;
|
|
870
|
+
if (read === 'unknown') {
|
|
871
|
+
writes.mutated.add(UNKNOWN_WRITE_TARGET);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
current = read;
|
|
875
|
+
const next = unwrapExpression(current);
|
|
876
|
+
if (!isObject(next) ||
|
|
877
|
+
(next.type !== 'ObjectExpression' && next.type !== 'ArrayExpression')) {
|
|
878
|
+
mutate(current, shadowed);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
current = next;
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
switch (root.type) {
|
|
886
|
+
case 'Identifier':
|
|
887
|
+
if (typeof root.name === 'string')
|
|
888
|
+
markObject(root.name, shadowed, new Set());
|
|
889
|
+
return;
|
|
890
|
+
case 'ConditionalExpression':
|
|
891
|
+
mutate(root.consequent, shadowed);
|
|
892
|
+
mutate(root.alternate, shadowed);
|
|
893
|
+
return;
|
|
894
|
+
case 'LogicalExpression':
|
|
895
|
+
mutate(root.left, shadowed);
|
|
896
|
+
mutate(root.right, shadowed);
|
|
897
|
+
return;
|
|
898
|
+
case 'SequenceExpression': {
|
|
899
|
+
const expressions = Array.isArray(root.expressions) ? root.expressions : [];
|
|
900
|
+
mutate(expressions[expressions.length - 1], shadowed);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
// Fresh values and instances cannot be module objects
|
|
904
|
+
case 'ThisExpression':
|
|
905
|
+
case 'ObjectExpression':
|
|
906
|
+
case 'ArrayExpression':
|
|
907
|
+
case 'NewExpression':
|
|
908
|
+
case 'Literal':
|
|
909
|
+
case 'TemplateLiteral':
|
|
910
|
+
return;
|
|
911
|
+
default:
|
|
912
|
+
writes.mutated.add(UNKNOWN_WRITE_TARGET);
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
const write = (written, shadowed) => {
|
|
916
|
+
const target = unwrapExpression(written);
|
|
917
|
+
if (!isObject(target))
|
|
918
|
+
return;
|
|
919
|
+
switch (target.type) {
|
|
920
|
+
case 'Identifier':
|
|
921
|
+
if (typeof target.name === 'string' && !shadowed.has(target.name)) {
|
|
922
|
+
writes.reassigned.add(target.name);
|
|
923
|
+
}
|
|
924
|
+
return;
|
|
925
|
+
case 'MemberExpression':
|
|
926
|
+
mutate(target.object, shadowed);
|
|
927
|
+
return;
|
|
928
|
+
case 'AssignmentPattern':
|
|
929
|
+
write(target.left, shadowed);
|
|
930
|
+
return;
|
|
931
|
+
case 'RestElement':
|
|
932
|
+
write(target.argument, shadowed);
|
|
933
|
+
return;
|
|
934
|
+
case 'ObjectPattern': {
|
|
935
|
+
const properties = Array.isArray(target.properties) ? target.properties : [];
|
|
936
|
+
for (const property of properties) {
|
|
937
|
+
if (!isObject(property))
|
|
938
|
+
continue;
|
|
939
|
+
write(property.type === 'RestElement' ? property.argument : property.value, shadowed);
|
|
940
|
+
}
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
case 'ArrayPattern': {
|
|
944
|
+
const elements = Array.isArray(target.elements) ? target.elements : [];
|
|
945
|
+
for (const element of elements)
|
|
946
|
+
write(element, shadowed);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
default:
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
visitReferences(node, new Set(), {
|
|
954
|
+
reference: () => undefined,
|
|
955
|
+
write,
|
|
956
|
+
mutate,
|
|
957
|
+
unknownWrite: () => {
|
|
958
|
+
writes.mutated.add(UNKNOWN_WRITE_TARGET);
|
|
959
|
+
},
|
|
960
|
+
eagerOnly,
|
|
961
|
+
});
|
|
962
|
+
return writes;
|
|
963
|
+
}
|