@vencord-companion/webpack-ast-parser 0.0.1
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/WebpackAstParser.d.ts +225 -0
- package/dist/WebpackAstParser.js +1597 -0
- package/dist/WebpackAstParser.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +27 -0
- package/dist/util.js +62 -0
- package/dist/util.js.map +1 -0
- package/package.json +30 -0
|
@@ -0,0 +1,1597 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Format } from "@sadan4/devtools-pretty-printer";
|
|
8
|
+
import { isAccessorDeclaration } from "ts-api-utils";
|
|
9
|
+
import { createSourceFile, isArrowFunction, isBinaryExpression, isBlock, isCallExpression, isClassDeclaration, isConstructorDeclaration, isExpressionStatement, isFunctionExpression, isIdentifier, isMethodDeclaration, isNewExpression, isNumericLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isPropertyDeclaration, isSemicolonClassElement, isSpreadAssignment, isStringLiteralLike, isVariableDeclaration, ScriptKind, ScriptTarget, } from "typescript";
|
|
10
|
+
import { AstParser, findObjectLiteralByKey, findParent, findParentLimited, findReturnIdentifier, findReturnPropertyAccessExpression, getLeadingIdentifier, isSyntaxList, lastParent, nonNull, } from "@vencord-companion/ast-parser";
|
|
11
|
+
import { Cache, CacheGetter } from "@vencord-companion/shared/decorators";
|
|
12
|
+
import { NoopLogger } from "@vencord-companion/shared/Logger";
|
|
13
|
+
import { zeroRange } from "@vencord-companion/shared/Range";
|
|
14
|
+
import { allEntries, assertNotHover, containsPosition, formatModule, fromEntries } from "./util";
|
|
15
|
+
let logger = NoopLogger;
|
|
16
|
+
export function setLogger(l) {
|
|
17
|
+
logger = l;
|
|
18
|
+
}
|
|
19
|
+
export class WebpackAstParser extends AstParser {
|
|
20
|
+
static defaultModuleCache = () => {
|
|
21
|
+
throw new Error("No default module cache set, please set one with WebpackAstParser.setDefaultModuleCache");
|
|
22
|
+
};
|
|
23
|
+
static setDefaultModuleCache(cache) {
|
|
24
|
+
if (typeof cache === "function") {
|
|
25
|
+
this.defaultModuleCache = cache;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.defaultModuleCache = () => cache;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
static defaultModuleDepManager = () => {
|
|
32
|
+
throw new Error("No default module dependency manager set, please set one with WebpackAstParser.setDefaultModuleDepManager");
|
|
33
|
+
};
|
|
34
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
35
|
+
static setDefaultModuleDepManager(manager) {
|
|
36
|
+
if (typeof manager === "function") {
|
|
37
|
+
this.defaultModuleDepManager = manager;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.defaultModuleDepManager = () => manager;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* This is set on {@link RangeExportMap} when the default export is commonjs and has no properties, eg, string literal, function
|
|
45
|
+
*/
|
|
46
|
+
static SYM_CJS_DEFAULT = Symbol.for("CommonJS Default Export");
|
|
47
|
+
/**
|
|
48
|
+
* set on raw export maps to give a hover hint for LSP
|
|
49
|
+
*/
|
|
50
|
+
static SYM_HOVER = Symbol.for("WebpackAstParser.Hover");
|
|
51
|
+
/**
|
|
52
|
+
*
|
|
53
|
+
* @param text the module text
|
|
54
|
+
* @returns a {@link WebpackAstParser}
|
|
55
|
+
*
|
|
56
|
+
* NOTE: you probably want {@link WebpackAstParser.withFormattedModule|withFormattedModule}
|
|
57
|
+
*/
|
|
58
|
+
static withFormattedText(text) {
|
|
59
|
+
return new this(Format(text));
|
|
60
|
+
}
|
|
61
|
+
static withFormattedModule(text, moduleId) {
|
|
62
|
+
return this.withFormattedText(formatModule(text, moduleId));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The webpack instance passed to this module
|
|
66
|
+
*
|
|
67
|
+
* The `n` of
|
|
68
|
+
* ```
|
|
69
|
+
* function (e, t, n) {
|
|
70
|
+
// webpack module contents
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
* @CacheGetter
|
|
74
|
+
*/
|
|
75
|
+
get wreq() {
|
|
76
|
+
return this.findWebpackArg(2);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* where {@link WebpackAstParser.wreq this.wreq} is used
|
|
80
|
+
* @CacheGetter
|
|
81
|
+
*/
|
|
82
|
+
get uses() {
|
|
83
|
+
return this.wreq && this.vars.get(this.wreq);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The module id of the current module
|
|
87
|
+
* @CacheGetter
|
|
88
|
+
*/
|
|
89
|
+
get moduleId() {
|
|
90
|
+
if (this.text.startsWith("// Webpack Module ")) {
|
|
91
|
+
const [, id] = this.text.match(/^\/\/ Webpack Module (\d+) /) ?? [];
|
|
92
|
+
return id || null;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* @CacheGetter
|
|
98
|
+
*/
|
|
99
|
+
get moduleCache() {
|
|
100
|
+
return WebpackAstParser.defaultModuleCache(this);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* @CacheGetter
|
|
104
|
+
*/
|
|
105
|
+
get moduleDepManager() {
|
|
106
|
+
return WebpackAstParser.defaultModuleDepManager(this);
|
|
107
|
+
}
|
|
108
|
+
constructor(text) {
|
|
109
|
+
super(text);
|
|
110
|
+
}
|
|
111
|
+
createSourceFile() {
|
|
112
|
+
return createSourceFile("module.js", this.text, ScriptTarget.ESNext, true, ScriptKind.JS);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* @param paramIndex the index of the param 0, 1, 2 etc...
|
|
116
|
+
* @param start finds a webpack arg from the source tree
|
|
117
|
+
* @returns the identifier of the param if found or undef
|
|
118
|
+
*
|
|
119
|
+
* NOTE: you should probably not use this
|
|
120
|
+
*
|
|
121
|
+
* @see {@link findWreq_t}
|
|
122
|
+
* @see {@link findWreq_e}
|
|
123
|
+
* @see {@link wreq}
|
|
124
|
+
*/
|
|
125
|
+
findWebpackArg(paramIndex, start = this.sourceFile) {
|
|
126
|
+
for (const n of start.getChildren()) {
|
|
127
|
+
if (isSyntaxList(n) || isExpressionStatement(n) || isBinaryExpression(n))
|
|
128
|
+
return this.findWebpackArg(paramIndex, n);
|
|
129
|
+
if (isFunctionExpression(n)) {
|
|
130
|
+
if (n.parameters.length > 3 || n.parameters.length < paramIndex + 1)
|
|
131
|
+
return;
|
|
132
|
+
const p = n.parameters[paramIndex].name;
|
|
133
|
+
if (!p)
|
|
134
|
+
return;
|
|
135
|
+
if (!isIdentifier(p))
|
|
136
|
+
return;
|
|
137
|
+
return p;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
getModulesThatRequireThisModule() {
|
|
142
|
+
if (!this.moduleId) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const guh = this.moduleDepManager.getModDeps(this.moduleId);
|
|
146
|
+
return {
|
|
147
|
+
lazy: guh.lazyUses,
|
|
148
|
+
sync: guh.syncUses,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* @Cache
|
|
153
|
+
*/
|
|
154
|
+
getModulesThatThisModuleRequires() {
|
|
155
|
+
if (!this.wreq || !this.uses)
|
|
156
|
+
return null;
|
|
157
|
+
// flatmaps because .map(...).filter(x => x !== false) isn't a valid typeguard
|
|
158
|
+
/**
|
|
159
|
+
* things like wreq(moduleid)
|
|
160
|
+
*/
|
|
161
|
+
const wreqCalls = this.uses.uses
|
|
162
|
+
.map((x) => x.location)
|
|
163
|
+
.flatMap((v) => {
|
|
164
|
+
const p = findParent(v, isCallExpression);
|
|
165
|
+
if (!p || p.expression !== v)
|
|
166
|
+
return [];
|
|
167
|
+
if (p.arguments.length === 1 && isNumericLiteral(p.arguments[0]))
|
|
168
|
+
return p.arguments[0].text;
|
|
169
|
+
return [];
|
|
170
|
+
});
|
|
171
|
+
const lazyModules = this.uses.uses
|
|
172
|
+
.map((x) => x.location)
|
|
173
|
+
.flatMap((v) => {
|
|
174
|
+
const [, prop] = getLeadingIdentifier(v);
|
|
175
|
+
if (prop?.text !== "bind")
|
|
176
|
+
return [];
|
|
177
|
+
const call = findParent(v, isCallExpression);
|
|
178
|
+
if (!call)
|
|
179
|
+
return [];
|
|
180
|
+
if (call.arguments.length === 2 && isNumericLiteral(call.arguments[1]))
|
|
181
|
+
return call.arguments[1].text;
|
|
182
|
+
return [];
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
lazy: lazyModules,
|
|
186
|
+
sync: wreqCalls,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async tryGetFreshModuleFallbackToCache(moduleId) {
|
|
190
|
+
try {
|
|
191
|
+
return await this.moduleCache.getLatestModuleFromNum(moduleId);
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
logger.warn(e);
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
return await this.moduleCache.getModuleFromNum(String(moduleId));
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
logger.warn(e);
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* @param idNode the numeric literal node that is the module id
|
|
206
|
+
* This is called internally by {@link generateDefinitions}
|
|
207
|
+
*
|
|
208
|
+
* given
|
|
209
|
+
* ```js
|
|
210
|
+
* var mod = n(123456);
|
|
211
|
+
* // ^^^^^^
|
|
212
|
+
* ```
|
|
213
|
+
* returns the definitions for that module
|
|
214
|
+
*/
|
|
215
|
+
generateDirectModuleDefinition(idNode) {
|
|
216
|
+
const maybeCall = idNode.parent;
|
|
217
|
+
if (!isCallExpression(maybeCall)) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (maybeCall.arguments.length !== 1) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const func = maybeCall.expression;
|
|
224
|
+
if (!isIdentifier(func)) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!this.isUseOf(func, this.wreq)) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const filePath = this.moduleCache.getModuleFilepath(idNode.text);
|
|
231
|
+
if (!filePath) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
return [
|
|
235
|
+
{
|
|
236
|
+
locationType: "file_path",
|
|
237
|
+
filePath,
|
|
238
|
+
range: zeroRange,
|
|
239
|
+
},
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
async generateDefinitions(position) {
|
|
243
|
+
if (!this.uses)
|
|
244
|
+
throw new Error("Wreq isn't used anywhere");
|
|
245
|
+
const selectedNode = this.getTokenAtOffset(this.offsetAt(position));
|
|
246
|
+
if (selectedNode && isNumericLiteral(selectedNode)) {
|
|
247
|
+
return this.generateDirectModuleDefinition(selectedNode);
|
|
248
|
+
}
|
|
249
|
+
const accessChain = findParent(selectedNode, isPropertyAccessExpression);
|
|
250
|
+
if (!accessChain)
|
|
251
|
+
return;
|
|
252
|
+
const importChain = this.flattenPropertyAccessExpression(accessChain);
|
|
253
|
+
if (!importChain)
|
|
254
|
+
return;
|
|
255
|
+
let [requiredModule, ...names] = importChain;
|
|
256
|
+
if (!this.isIdentifier(requiredModule))
|
|
257
|
+
return;
|
|
258
|
+
const dec = this.getVarInfoFromUse(requiredModule);
|
|
259
|
+
if (!dec) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const moduleId = this.getModuleIdForImport(dec);
|
|
263
|
+
if (!moduleId)
|
|
264
|
+
return;
|
|
265
|
+
const res = await this.tryGetFreshModuleFallbackToCache(moduleId);
|
|
266
|
+
if (res == null)
|
|
267
|
+
return;
|
|
268
|
+
let cur = WebpackAstParser.withFormattedModule(res, moduleId);
|
|
269
|
+
if (names.length < 1) {
|
|
270
|
+
return [
|
|
271
|
+
{
|
|
272
|
+
range: zeroRange,
|
|
273
|
+
locationType: "inline",
|
|
274
|
+
content: cur.text,
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
}
|
|
278
|
+
while (true) {
|
|
279
|
+
// check for an explicit re-export before falling back to checking for a whole module re-export
|
|
280
|
+
const ret = cur.doesReExportFromExport(names.map((x) => x.text));
|
|
281
|
+
if (!ret) {
|
|
282
|
+
// if no explicit re-export was found, try a whole module re-export
|
|
283
|
+
const wholeModuleExportId = cur.doesReExportWholeModule();
|
|
284
|
+
if (wholeModuleExportId) {
|
|
285
|
+
const content = await this.tryGetFreshModuleFallbackToCache(wholeModuleExportId);
|
|
286
|
+
if (content) {
|
|
287
|
+
cur = WebpackAstParser.withFormattedModule(content, wholeModuleExportId);
|
|
288
|
+
// go again with the new module
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
const [importSourceId] = ret;
|
|
295
|
+
[, names] = ret;
|
|
296
|
+
const res = await this.tryGetFreshModuleFallbackToCache(importSourceId)
|
|
297
|
+
.catch(logger.error);
|
|
298
|
+
if (!res) {
|
|
299
|
+
logger.error("Failed to get data from client");
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
cur = WebpackAstParser.withFormattedModule(res, importSourceId);
|
|
303
|
+
}
|
|
304
|
+
const maybeRange = cur
|
|
305
|
+
.findExportLocation(names.map((x) => x.text));
|
|
306
|
+
return [
|
|
307
|
+
{
|
|
308
|
+
range: maybeRange,
|
|
309
|
+
locationType: "inline",
|
|
310
|
+
content: cur.text,
|
|
311
|
+
},
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
async generateHover(position) {
|
|
315
|
+
if (!this.uses)
|
|
316
|
+
throw new Error("Wreq isn't used anywhere");
|
|
317
|
+
const selectedNode = this.getTokenAtOffset(this.offsetAt(position));
|
|
318
|
+
const accessChain = findParent(selectedNode, isPropertyAccessExpression);
|
|
319
|
+
if (!accessChain)
|
|
320
|
+
return;
|
|
321
|
+
const importChain = this.flattenPropertyAccessExpression(accessChain);
|
|
322
|
+
if (!importChain)
|
|
323
|
+
return;
|
|
324
|
+
let [requiredModule, ...names] = importChain;
|
|
325
|
+
if (names.length < 1) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (!this.isIdentifier(requiredModule))
|
|
329
|
+
return;
|
|
330
|
+
const dec = this.getVarInfoFromUse(requiredModule);
|
|
331
|
+
if (!dec) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const moduleId = this.getModuleIdForImport(dec);
|
|
335
|
+
if (!moduleId)
|
|
336
|
+
return;
|
|
337
|
+
const res = await this.tryGetFreshModuleFallbackToCache(moduleId);
|
|
338
|
+
if (res == null)
|
|
339
|
+
return;
|
|
340
|
+
let cur = WebpackAstParser.withFormattedModule(res, moduleId);
|
|
341
|
+
while (true) {
|
|
342
|
+
// check for an explicit re-export before falling back to checking for a whole module re-export
|
|
343
|
+
const ret = cur.doesReExportFromExport(names.map((x) => x.text));
|
|
344
|
+
if (!ret) {
|
|
345
|
+
// if no explicit re-export was found, try a whole module re-export
|
|
346
|
+
const wholeModuleExportId = cur.doesReExportWholeModule();
|
|
347
|
+
if (wholeModuleExportId) {
|
|
348
|
+
const content = await this.tryGetFreshModuleFallbackToCache(wholeModuleExportId);
|
|
349
|
+
if (content) {
|
|
350
|
+
cur = WebpackAstParser.withFormattedModule(content, wholeModuleExportId);
|
|
351
|
+
// go again with the new module
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
const [importSourceId] = ret;
|
|
358
|
+
[, names] = ret;
|
|
359
|
+
const res = await this.tryGetFreshModuleFallbackToCache(importSourceId);
|
|
360
|
+
if (!res) {
|
|
361
|
+
logger.error("Failed to get new module");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
cur = WebpackAstParser.withFormattedModule(res, importSourceId);
|
|
365
|
+
}
|
|
366
|
+
const hoverText = cur.findHoverText(names.map((x) => x.text));
|
|
367
|
+
const hoverRange = this.makeRangeFromAstNode(names.at(-1));
|
|
368
|
+
return hoverText === undefined ? undefined : [hoverRange, hoverText];
|
|
369
|
+
}
|
|
370
|
+
doesReExportFromExport(exportName) {
|
|
371
|
+
const map = this.getExportMapRaw();
|
|
372
|
+
const exp = this.getNestedExportFromMap(exportName, map);
|
|
373
|
+
const last = exp?.at(-1);
|
|
374
|
+
if (!last)
|
|
375
|
+
return;
|
|
376
|
+
// TODO: handle more cases than just property access
|
|
377
|
+
if (!isPropertyAccessExpression(last))
|
|
378
|
+
return;
|
|
379
|
+
const [imported, ...chain] = this.flattenPropertyAccessExpression(last) ?? [];
|
|
380
|
+
if (!this.isIdentifier(imported) || chain.length === 0)
|
|
381
|
+
return;
|
|
382
|
+
const importedId = this.getIdOfImportedVar(imported);
|
|
383
|
+
if (!importedId)
|
|
384
|
+
return;
|
|
385
|
+
return [importedId, chain];
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* gets the module id from a require
|
|
389
|
+
* given
|
|
390
|
+
* ```js
|
|
391
|
+
* var mod = n(123456);
|
|
392
|
+
* ```
|
|
393
|
+
* @argument dec the variable info for that mod
|
|
394
|
+
* @see {@link getVariableInitializer} which can than be passed into {@link vars|vars.get}
|
|
395
|
+
* @returns `123456`
|
|
396
|
+
*/
|
|
397
|
+
getModuleIdForImport(dec) {
|
|
398
|
+
if (!dec)
|
|
399
|
+
return;
|
|
400
|
+
if (dec.declarations.length !== 1)
|
|
401
|
+
return;
|
|
402
|
+
const init = findParent(dec.declarations[0], isVariableDeclaration)?.initializer;
|
|
403
|
+
if (!init || !isCallExpression(init) || !isIdentifier(init.expression))
|
|
404
|
+
return;
|
|
405
|
+
// make sure init is a call to wreq
|
|
406
|
+
const initInfo = this.getVarInfoFromUse(init.expression);
|
|
407
|
+
if (initInfo !== this.uses) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
// TODO: support string literals here
|
|
411
|
+
if (init.arguments.length !== 1 || !isNumericLiteral(init.arguments[0]))
|
|
412
|
+
return;
|
|
413
|
+
const num = +init.arguments[0].text;
|
|
414
|
+
return num;
|
|
415
|
+
}
|
|
416
|
+
async generateReferences(position) {
|
|
417
|
+
if (!this.moduleId)
|
|
418
|
+
throw new Error("Could not find module id of module to search for references of");
|
|
419
|
+
const moduleExports = this.getExportMap();
|
|
420
|
+
const where = this.getModulesThatRequireThisModule();
|
|
421
|
+
const locs = [];
|
|
422
|
+
const exportedNames = Object.entries(moduleExports)
|
|
423
|
+
.filter(([, exportRange]) => containsPosition(exportRange, position));
|
|
424
|
+
// TODO: support jumping from object literals
|
|
425
|
+
for (const [_exportedName] of exportedNames) {
|
|
426
|
+
// needed to workaround a v8 bug which crashes when a breakpoint falls on the for loop
|
|
427
|
+
const exportedName = _exportedName;
|
|
428
|
+
const seen = {};
|
|
429
|
+
const left = where?.sync.map((x) => [x, this.moduleId, exportedName]) ?? [];
|
|
430
|
+
let cur;
|
|
431
|
+
while ((cur = left.pop())) {
|
|
432
|
+
const [modId, importedId, exportedName] = cur;
|
|
433
|
+
if (seen[importedId]?.has(modId)) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
(seen[importedId] ||= new Set()).add(modId);
|
|
437
|
+
const modText = await this.moduleCache.getModuleFromNum(modId);
|
|
438
|
+
if (!modText)
|
|
439
|
+
continue;
|
|
440
|
+
const parser = new WebpackAstParser(modText);
|
|
441
|
+
const uses = parser.getUsesOfImport(importedId, exportedName);
|
|
442
|
+
// FIXME: this covers up a bug in {@link doesReExport}
|
|
443
|
+
// if (uses.length === 0)
|
|
444
|
+
// continue;
|
|
445
|
+
const exportedAs = parser.doesReExportFromImport(importedId, exportedName);
|
|
446
|
+
if (exportedAs) {
|
|
447
|
+
const where = parser.getModulesThatRequireThisModule();
|
|
448
|
+
left.push(...where?.sync.map((x) => [x, parser.moduleId, exportedAs]) ?? []);
|
|
449
|
+
}
|
|
450
|
+
locs.push(...uses.map((x) => {
|
|
451
|
+
const maybeFilePath = this.moduleCache.getModuleFilepath(modId);
|
|
452
|
+
if (maybeFilePath) {
|
|
453
|
+
return {
|
|
454
|
+
range: x,
|
|
455
|
+
locationType: "file_path",
|
|
456
|
+
filePath: maybeFilePath,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
range: x,
|
|
461
|
+
locationType: "inline",
|
|
462
|
+
content: this.text,
|
|
463
|
+
};
|
|
464
|
+
}));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return locs;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* checks if this module re-exports another whole module and not just parts of it
|
|
471
|
+
* ```js
|
|
472
|
+
* e.exports = n(moduleId);
|
|
473
|
+
* ```
|
|
474
|
+
* @returns the module ID if it does, undefined otherwise
|
|
475
|
+
* @Cache
|
|
476
|
+
*/
|
|
477
|
+
doesReExportWholeModule() {
|
|
478
|
+
// we can't export anything if we don't import anything
|
|
479
|
+
if (!this.wreq)
|
|
480
|
+
return;
|
|
481
|
+
// Check for a re-export of the whole module before decl
|
|
482
|
+
// if the whole module is exported, then we don't need to do any more work
|
|
483
|
+
// e.exports = n(moduleId);
|
|
484
|
+
for (const { location: use } of this.uses.uses) {
|
|
485
|
+
const assignment = findParent(use, this.isAssignmentExpression);
|
|
486
|
+
if (!assignment) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
// lhs
|
|
490
|
+
const lhs = assignment.left;
|
|
491
|
+
if (!isPropertyAccessExpression(lhs)) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const [module, exports] = this.flattenPropertyAccessExpression(lhs) ?? [];
|
|
495
|
+
if (!module || !isIdentifier(module) || !this.isUseOf(module, this.findWreq_e()) || exports?.text !== "exports") {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const rhs = assignment.right;
|
|
499
|
+
if (!isCallExpression(rhs) || rhs.expression !== use || rhs.arguments.length !== 1) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const [arg] = rhs.arguments;
|
|
503
|
+
if (!isNumericLiteral(arg)) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
return arg.text;
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Figure out if this module re-exports another given the module id of the other and the name of the export from the other module
|
|
512
|
+
* @param moduleId the module id that {@link exportName} is from
|
|
513
|
+
* @param exportName the name of the re-exported export
|
|
514
|
+
*/
|
|
515
|
+
doesReExportFromImport(moduleId, exportName) {
|
|
516
|
+
// we can't re-export anything if we don't import anything
|
|
517
|
+
if (!this.wreq || !this.moduleId)
|
|
518
|
+
return;
|
|
519
|
+
if (this.doesReExportWholeModule()) {
|
|
520
|
+
return exportName;
|
|
521
|
+
}
|
|
522
|
+
const decl = this.getImportedVar(moduleId);
|
|
523
|
+
if (!decl)
|
|
524
|
+
return;
|
|
525
|
+
// FIXME: handle re-exports as cjs default, Object.entries ignores symbols
|
|
526
|
+
const maybeReExports = Object.entries(this.getExportMapRaw())
|
|
527
|
+
.filter(([, _v]) => {
|
|
528
|
+
/** FIXME: properly handle this with {@link exportName} */
|
|
529
|
+
if (!Array.isArray(_v))
|
|
530
|
+
return false;
|
|
531
|
+
const [v] = _v;
|
|
532
|
+
if (isIdentifier(v)) {
|
|
533
|
+
return this.isUseOf(v, decl);
|
|
534
|
+
}
|
|
535
|
+
else if (isPropertyAccessExpression(v)) {
|
|
536
|
+
const [module, reExport] = getLeadingIdentifier(v);
|
|
537
|
+
if (!module)
|
|
538
|
+
return false;
|
|
539
|
+
// you cant discriminate against destructured unions
|
|
540
|
+
return this.isUseOf(module, decl) && reExport.text === exportName;
|
|
541
|
+
}
|
|
542
|
+
logger.warn(`[WebpackAstParser] Unhandled type for reExport: ${v.kind}. Module ID: ${this.moduleId}`);
|
|
543
|
+
return false;
|
|
544
|
+
})
|
|
545
|
+
.map(([k]) => k);
|
|
546
|
+
if (maybeReExports.length !== 1) {
|
|
547
|
+
if (maybeReExports.length > 1) {
|
|
548
|
+
throw new Error(`Found more than one reExport for wreq(${moduleId}).${String(exportName)} in ${this.moduleId}`);
|
|
549
|
+
}
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
return maybeReExports[0];
|
|
553
|
+
}
|
|
554
|
+
// TODO: add tests for this func
|
|
555
|
+
/**
|
|
556
|
+
* @returns a map of exported names to the nodes that they are exported from
|
|
557
|
+
* @Cache
|
|
558
|
+
*/
|
|
559
|
+
getExportMapRaw() {
|
|
560
|
+
return {
|
|
561
|
+
...this.getExportMapRawWreq_d() ?? {},
|
|
562
|
+
...this.getExportMapRawWreq_t() ?? {},
|
|
563
|
+
...this.getExportMapRawWreq_e() ?? {},
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* FIXME: this is not in line with {@link getExportMapWreq_d}
|
|
568
|
+
* @Cache
|
|
569
|
+
*/
|
|
570
|
+
getExportMapRawWreq_d() {
|
|
571
|
+
const wreqD = this.findWreq_d();
|
|
572
|
+
if (!wreqD)
|
|
573
|
+
return;
|
|
574
|
+
const [, exports] = wreqD.arguments;
|
|
575
|
+
return Object.fromEntries(exports.properties
|
|
576
|
+
.map((x) => {
|
|
577
|
+
if (!isPropertyAssignment(x)
|
|
578
|
+
|| !(isArrowFunction(x.initializer)
|
|
579
|
+
|| isFunctionExpression(x.initializer)))
|
|
580
|
+
return false;
|
|
581
|
+
const tailingIdent = findReturnIdentifier(x.initializer)
|
|
582
|
+
?? findReturnPropertyAccessExpression(x.initializer);
|
|
583
|
+
if (this.tryParseStoreForExport(tailingIdent) != null) {
|
|
584
|
+
logger.warn("Getting raw export map for a module that has a store export "
|
|
585
|
+
+ "this is not supported and should be handled. "
|
|
586
|
+
+ "this will probably lead to errors.");
|
|
587
|
+
}
|
|
588
|
+
let ret;
|
|
589
|
+
if (tailingIdent) {
|
|
590
|
+
ret = this.tryParseClassDeclaration(tailingIdent, [x.name]);
|
|
591
|
+
ret ||= this.rawMakeExportMapRecursive(tailingIdent);
|
|
592
|
+
}
|
|
593
|
+
return ret != null && [x.name.getText(), ret];
|
|
594
|
+
})
|
|
595
|
+
.filter((x) => x !== false));
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* @Cache
|
|
599
|
+
*/
|
|
600
|
+
getExportMapRawWreq_e() {
|
|
601
|
+
const wreqE = this.findWreq_e();
|
|
602
|
+
if (!wreqE)
|
|
603
|
+
return;
|
|
604
|
+
const uses = this.vars.get(wreqE);
|
|
605
|
+
if (!uses)
|
|
606
|
+
return;
|
|
607
|
+
const exportAssignments = uses.uses
|
|
608
|
+
.filter(({ location }) => {
|
|
609
|
+
const [, moduleProp] = getLeadingIdentifier(location);
|
|
610
|
+
return moduleProp?.text === "exports";
|
|
611
|
+
})
|
|
612
|
+
.map((x) => x.location)
|
|
613
|
+
.map((x) => {
|
|
614
|
+
let name = this.flattenPropertyAccessExpression(lastParent(x, isPropertyAccessExpression))?.[2]?.text;
|
|
615
|
+
name ||= WebpackAstParser.SYM_CJS_DEFAULT;
|
|
616
|
+
const ret = findParent(x, isBinaryExpression)?.right;
|
|
617
|
+
return ret && [name, [ret]];
|
|
618
|
+
})
|
|
619
|
+
.filter((x) => x !== undefined);
|
|
620
|
+
if (exportAssignments.length === 0)
|
|
621
|
+
return;
|
|
622
|
+
return Object.fromEntries(exportAssignments);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* @Cache
|
|
626
|
+
*/
|
|
627
|
+
getExportMapRawWreq_t() {
|
|
628
|
+
const wreqT = this.findWreq_t();
|
|
629
|
+
if (!wreqT)
|
|
630
|
+
return;
|
|
631
|
+
const uses = this.vars.get(wreqT);
|
|
632
|
+
if (!uses)
|
|
633
|
+
return;
|
|
634
|
+
return Object.fromEntries(uses.uses
|
|
635
|
+
.map(({ location }) => {
|
|
636
|
+
const [, exportAssignment] = getLeadingIdentifier(location);
|
|
637
|
+
const binary = findParent(location, isBinaryExpression);
|
|
638
|
+
if (exportAssignment && binary?.right) {
|
|
639
|
+
return [exportAssignment.text, [binary.right]];
|
|
640
|
+
}
|
|
641
|
+
return undefined;
|
|
642
|
+
})
|
|
643
|
+
.filter((x) => x !== undefined));
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* @Cache
|
|
647
|
+
*/
|
|
648
|
+
getExportMap() {
|
|
649
|
+
return {
|
|
650
|
+
...this.getExportMapWreq_d() ?? {},
|
|
651
|
+
...this.getExportMapWreq_t() ?? {},
|
|
652
|
+
...this.getExportMapWreq_e() ?? {},
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
getImportedVar(moduleId) {
|
|
656
|
+
if (!this.wreq)
|
|
657
|
+
throw new Error("Wreq is not used in this file");
|
|
658
|
+
const uses = this.uses.uses.find(({ location }) => {
|
|
659
|
+
const call = findParent(location, isCallExpression);
|
|
660
|
+
return (call?.arguments.length === 1 && call.arguments[0].getText() === moduleId);
|
|
661
|
+
});
|
|
662
|
+
const ret = findParent(uses?.location, isVariableDeclaration)?.name;
|
|
663
|
+
if (this.isIdentifier(ret))
|
|
664
|
+
return ret;
|
|
665
|
+
}
|
|
666
|
+
// TODO: trace over ```js
|
|
667
|
+
// var foo = wreq(1)
|
|
668
|
+
// , used = n.n(foo);
|
|
669
|
+
// TODO: add tests
|
|
670
|
+
getIdOfImportedVar(variable) {
|
|
671
|
+
const uses = (this.getVarInfoFromUse(variable) ?? this.vars.get(variable))?.declarations[0];
|
|
672
|
+
if (!uses)
|
|
673
|
+
return;
|
|
674
|
+
const decl = findParent(uses, isVariableDeclaration);
|
|
675
|
+
if (!decl)
|
|
676
|
+
return;
|
|
677
|
+
const initExpr = decl.initializer;
|
|
678
|
+
if (!initExpr || !isCallExpression(initExpr))
|
|
679
|
+
return;
|
|
680
|
+
const [id] = initExpr.arguments;
|
|
681
|
+
if (!this.isLiteralish(id)) {
|
|
682
|
+
logger.warn("id is not literalish");
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
return id.getText();
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* @param moduleId the imported module id where {@link exportName} is used
|
|
689
|
+
* @param exportName the string of the exported name or {@link SYM_CJS_DEFAULT} for the default export
|
|
690
|
+
* TODO: support nested exports eg: `wreq(123).ZP.storeMethod()`
|
|
691
|
+
* @returns the ranges where the export is used in this file
|
|
692
|
+
*/
|
|
693
|
+
getUsesOfImport(moduleId, exportName) {
|
|
694
|
+
if (!this.wreq)
|
|
695
|
+
throw new Error("Wreq is not used in this file");
|
|
696
|
+
if (typeof exportName === "symbol" && exportName !== WebpackAstParser.SYM_CJS_DEFAULT) {
|
|
697
|
+
throw new Error("Invalid symbol for exportName");
|
|
698
|
+
}
|
|
699
|
+
const uses = [];
|
|
700
|
+
for (const { location } of this.vars.get(this.wreq)?.uses ?? []) {
|
|
701
|
+
if (!isCallExpression(location.parent))
|
|
702
|
+
continue;
|
|
703
|
+
if (location.parent.arguments[0].getText() !== moduleId)
|
|
704
|
+
continue;
|
|
705
|
+
const norm = location?.parent?.parent;
|
|
706
|
+
if (norm && isVariableDeclaration(norm)) {
|
|
707
|
+
if (!isIdentifier(norm.name))
|
|
708
|
+
continue;
|
|
709
|
+
const importUses = this.vars.get(norm.name);
|
|
710
|
+
// handle things like `var foo = wreq(1), bar = wreq.n(foo)`
|
|
711
|
+
nmd: {
|
|
712
|
+
if (importUses?.uses.length === 1) {
|
|
713
|
+
const loc = importUses.uses[0].location;
|
|
714
|
+
const call = findParent(loc, isCallExpression);
|
|
715
|
+
if (!call
|
|
716
|
+
|| call.arguments.length !== 1
|
|
717
|
+
|| call.arguments[0] !== loc)
|
|
718
|
+
break nmd;
|
|
719
|
+
// ensure the call is `n.n(...)`
|
|
720
|
+
const funcExpr = call.expression;
|
|
721
|
+
// ensure something like `foo.bar`
|
|
722
|
+
if (!isPropertyAccessExpression(funcExpr)
|
|
723
|
+
|| !isIdentifier(funcExpr.name)
|
|
724
|
+
|| !isIdentifier(funcExpr.expression))
|
|
725
|
+
break nmd;
|
|
726
|
+
// ensure the first part is wreq
|
|
727
|
+
if (!this.isUseOf(funcExpr.expression, this.wreq)
|
|
728
|
+
|| funcExpr.name.text !== "n")
|
|
729
|
+
break nmd;
|
|
730
|
+
const decl = findParent(funcExpr, isVariableDeclaration)?.name;
|
|
731
|
+
if (!decl || !isIdentifier(decl))
|
|
732
|
+
break nmd;
|
|
733
|
+
this.vars
|
|
734
|
+
.get(decl)
|
|
735
|
+
?.uses?.map((x) => x.location.parent)
|
|
736
|
+
.filter(isCallExpression)
|
|
737
|
+
.map((calledUse) => {
|
|
738
|
+
if (exportName === WebpackAstParser.SYM_CJS_DEFAULT) {
|
|
739
|
+
// TODO: handle default exports other than just functions
|
|
740
|
+
return isCallExpression(calledUse.parent)
|
|
741
|
+
? [this.makeRangeFromAstNode(calledUse)]
|
|
742
|
+
: undefined;
|
|
743
|
+
}
|
|
744
|
+
else if (typeof exportName === "string") {
|
|
745
|
+
const expr = findParent(calledUse, isPropertyAccessExpression);
|
|
746
|
+
if (!(!!expr
|
|
747
|
+
&& expr.expression === calledUse
|
|
748
|
+
&& expr.name.text === exportName))
|
|
749
|
+
return undefined;
|
|
750
|
+
return [this.makeRangeFromAstNode(expr.name)];
|
|
751
|
+
}
|
|
752
|
+
throw new Error("Invalid exportName");
|
|
753
|
+
})
|
|
754
|
+
.filter((x) => x !== undefined)
|
|
755
|
+
.forEach((use) => {
|
|
756
|
+
const final = use.at(-1);
|
|
757
|
+
if (!final)
|
|
758
|
+
throw new Error("Final is undefined, this should have been filtered out by the previous line as there should be no empty arrays");
|
|
759
|
+
uses.push(final);
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
for (const { location } of importUses?.uses ?? []) {
|
|
764
|
+
if (!isPropertyAccessExpression(location.parent))
|
|
765
|
+
continue;
|
|
766
|
+
if (!isIdentifier(location.parent.name))
|
|
767
|
+
continue;
|
|
768
|
+
if (location.parent.name.getText() !== exportName)
|
|
769
|
+
continue;
|
|
770
|
+
uses.push(this.makeRangeFromAstNode(location.parent.name));
|
|
771
|
+
}
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const direct = location.parent;
|
|
775
|
+
if (isCallExpression(direct)) {
|
|
776
|
+
if (!isPropertyAccessExpression(direct.parent))
|
|
777
|
+
continue;
|
|
778
|
+
if (!isIdentifier(direct.parent.name))
|
|
779
|
+
continue;
|
|
780
|
+
if (direct.parent.name.text !== exportName)
|
|
781
|
+
continue;
|
|
782
|
+
uses.push(this.makeRangeFromAstNode(direct.parent.name));
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return uses;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* @Cache
|
|
789
|
+
*/
|
|
790
|
+
getExportMapWreq_t() {
|
|
791
|
+
const wreqT = this.findWreq_t();
|
|
792
|
+
if (!wreqT)
|
|
793
|
+
return undefined;
|
|
794
|
+
const uses = this.vars.get(wreqT);
|
|
795
|
+
if (!uses)
|
|
796
|
+
return undefined;
|
|
797
|
+
return Object.fromEntries(uses.uses
|
|
798
|
+
.map(({ location }) => {
|
|
799
|
+
const [, exportAssignment] = getLeadingIdentifier(location);
|
|
800
|
+
const binary = findParent(location, isBinaryExpression);
|
|
801
|
+
if (exportAssignment && binary && isIdentifier(binary?.right)) {
|
|
802
|
+
return [
|
|
803
|
+
exportAssignment.text,
|
|
804
|
+
[
|
|
805
|
+
this.makeRangeFromAstNode(exportAssignment),
|
|
806
|
+
this.makeRangeFromAstNode(binary.right),
|
|
807
|
+
this.makeRangeFromFunctionDef(binary.right),
|
|
808
|
+
].filter((x) => !!x),
|
|
809
|
+
];
|
|
810
|
+
}
|
|
811
|
+
return exportAssignment
|
|
812
|
+
? [
|
|
813
|
+
exportAssignment.text,
|
|
814
|
+
[this.makeRangeFromAstNode(exportAssignment)],
|
|
815
|
+
]
|
|
816
|
+
: false;
|
|
817
|
+
})
|
|
818
|
+
.filter((x) => x !== false));
|
|
819
|
+
}
|
|
820
|
+
rawMakeExportMapRecursive(node) {
|
|
821
|
+
if (!node)
|
|
822
|
+
throw new Error("node should not be undefined");
|
|
823
|
+
if (isObjectLiteralExpression(node)) {
|
|
824
|
+
const props = node.properties
|
|
825
|
+
.map((x) => {
|
|
826
|
+
if (isSpreadAssignment(x)) {
|
|
827
|
+
if (!isIdentifier(x.expression)) {
|
|
828
|
+
logger.error("Spread assignment is not an identifier, this should be handled");
|
|
829
|
+
}
|
|
830
|
+
const spread = this.rawMakeExportMapRecursive(x.expression);
|
|
831
|
+
if (Array.isArray(spread)) {
|
|
832
|
+
logger.warn("Identifier in object spread is not an object, this should be handled");
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
const { [WebpackAstParser.SYM_CJS_DEFAULT]: _default, [WebpackAstParser.SYM_HOVER]: _, ...rest } = spread;
|
|
836
|
+
return Object.entries(rest);
|
|
837
|
+
}
|
|
838
|
+
return [[x.name.getText(), this.rawMakeExportMapRecursive(x)]];
|
|
839
|
+
})
|
|
840
|
+
.filter((x) => x !== false)
|
|
841
|
+
.flat();
|
|
842
|
+
if (props.length !== 0)
|
|
843
|
+
props.push([WebpackAstParser.SYM_CJS_DEFAULT, [node.getChildAt(0)]]);
|
|
844
|
+
return fromEntries(props);
|
|
845
|
+
}
|
|
846
|
+
else if (this.isLiteralish(node)) {
|
|
847
|
+
return [node];
|
|
848
|
+
}
|
|
849
|
+
else if (isPropertyAssignment(node)) {
|
|
850
|
+
const objRange = this.rawMakeExportMapRecursive(node.initializer);
|
|
851
|
+
if (Array.isArray(objRange))
|
|
852
|
+
return [node.name, ...[objRange].flat()];
|
|
853
|
+
return {
|
|
854
|
+
[node.name.getText()]: objRange,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
else if (this.isFunctionish(node)) {
|
|
858
|
+
wrapperFuncCheck: {
|
|
859
|
+
if (!node.body)
|
|
860
|
+
break wrapperFuncCheck;
|
|
861
|
+
// if the arrow function returns a simple identifier, use that
|
|
862
|
+
if (isIdentifier(node.body) || isPropertyAccessExpression(node.body)) {
|
|
863
|
+
const ret = this.rawMakeExportMapRecursive(node.body);
|
|
864
|
+
if (allEntries(ret).length > 0)
|
|
865
|
+
return ret;
|
|
866
|
+
}
|
|
867
|
+
if (isBlock(node.body) && node.body.statements.length === 1) {
|
|
868
|
+
const ident = findReturnIdentifier(node);
|
|
869
|
+
if (!ident)
|
|
870
|
+
break wrapperFuncCheck;
|
|
871
|
+
const ret = this.rawMakeExportMapRecursive(ident);
|
|
872
|
+
if (allEntries(ret).length > 0)
|
|
873
|
+
return ret;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (node.name)
|
|
877
|
+
return [node.name];
|
|
878
|
+
return [node];
|
|
879
|
+
}
|
|
880
|
+
else if (isCallExpression(node)) {
|
|
881
|
+
return [node];
|
|
882
|
+
}
|
|
883
|
+
else if (isIdentifier(node)) {
|
|
884
|
+
const trail = this.unwrapVariableDeclaration(node);
|
|
885
|
+
if (!trail || trail.length === 0) {
|
|
886
|
+
logger.warn("Could not find variable declaration for identifier");
|
|
887
|
+
return [];
|
|
888
|
+
}
|
|
889
|
+
const last = this.getVariableInitializer(trail.at(-1));
|
|
890
|
+
if (!last) {
|
|
891
|
+
logger.trace("[WebpackAstParser] Could not find initializer of identifier");
|
|
892
|
+
return [trail.at(-1)];
|
|
893
|
+
}
|
|
894
|
+
return this.rawMakeExportMapRecursive(last);
|
|
895
|
+
}
|
|
896
|
+
return [node];
|
|
897
|
+
}
|
|
898
|
+
rawMapToExportMap(map) {
|
|
899
|
+
if (map == null) {
|
|
900
|
+
return map;
|
|
901
|
+
}
|
|
902
|
+
if (Array.isArray(map)) {
|
|
903
|
+
return map.map((node) => {
|
|
904
|
+
if (this.isFunctionish(node) && !node.name) {
|
|
905
|
+
return this.makeRangeFromAnonFunction(node);
|
|
906
|
+
}
|
|
907
|
+
return this.makeRangeFromAstNode(node);
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
return Object.fromEntries(allEntries(map)
|
|
911
|
+
.map(([k, v]) => {
|
|
912
|
+
if (k === WebpackAstParser.SYM_HOVER) {
|
|
913
|
+
return [k, v];
|
|
914
|
+
}
|
|
915
|
+
assertNotHover(v);
|
|
916
|
+
return [k, this.rawMapToExportMap(v)];
|
|
917
|
+
}));
|
|
918
|
+
}
|
|
919
|
+
makeExportMapRecursive(node) {
|
|
920
|
+
if (!node)
|
|
921
|
+
throw new Error("node should not be undefined / falsy");
|
|
922
|
+
return this.rawMapToExportMap(this.rawMakeExportMapRecursive(node));
|
|
923
|
+
}
|
|
924
|
+
// FIXME: handle when there is more than one module.exports assignment, eg e = () => {}; e.foo = () => {};
|
|
925
|
+
/**
|
|
926
|
+
* @Cache
|
|
927
|
+
*/
|
|
928
|
+
getExportMapWreq_e() {
|
|
929
|
+
const wreqE = this.findWreq_e();
|
|
930
|
+
if (!wreqE)
|
|
931
|
+
return undefined;
|
|
932
|
+
const uses = this.vars.get(wreqE);
|
|
933
|
+
if (!uses)
|
|
934
|
+
return undefined;
|
|
935
|
+
const exportAssignment = uses.uses.find(({ location }) => {
|
|
936
|
+
const [, moduleProp] = getLeadingIdentifier(location);
|
|
937
|
+
return moduleProp?.text === "exports";
|
|
938
|
+
});
|
|
939
|
+
if (!exportAssignment)
|
|
940
|
+
return undefined;
|
|
941
|
+
const exportObject = findParent(exportAssignment.location, isBinaryExpression)?.right;
|
|
942
|
+
if (!exportObject) {
|
|
943
|
+
logger.debug("Could not find export object");
|
|
944
|
+
return undefined;
|
|
945
|
+
}
|
|
946
|
+
let exports = null;
|
|
947
|
+
// TODO: should this get extra export ranges
|
|
948
|
+
const rawClassExportMap = this.tryParseClassDeclaration(exportObject, []);
|
|
949
|
+
if (rawClassExportMap) {
|
|
950
|
+
const classExportMap = this.rawMapToExportMap(rawClassExportMap);
|
|
951
|
+
exports ??= {
|
|
952
|
+
[WebpackAstParser.SYM_CJS_DEFAULT]: classExportMap,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
exports ??= this.makeExportMapRecursive(exportObject);
|
|
956
|
+
if (Array.isArray(exports)) {
|
|
957
|
+
return {
|
|
958
|
+
[WebpackAstParser.SYM_CJS_DEFAULT]: exports,
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
return exports;
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* @Cache
|
|
965
|
+
*/
|
|
966
|
+
getExportMapWreq_d() {
|
|
967
|
+
const wreqD = this.findWreq_d();
|
|
968
|
+
if (!wreqD)
|
|
969
|
+
return;
|
|
970
|
+
const [, exports] = wreqD.arguments;
|
|
971
|
+
return Object.fromEntries(exports.properties
|
|
972
|
+
.map((x) => {
|
|
973
|
+
if (!isPropertyAssignment(x)
|
|
974
|
+
|| !(isArrowFunction(x.initializer)
|
|
975
|
+
|| isFunctionExpression(x.initializer)))
|
|
976
|
+
return false;
|
|
977
|
+
let lastNode = findReturnIdentifier(x.initializer);
|
|
978
|
+
lastNode ??= findReturnPropertyAccessExpression(x.initializer);
|
|
979
|
+
let ret;
|
|
980
|
+
ret = this.tryParseStoreForExport(lastNode, [this.makeRangeFromAstNode(x.name)]);
|
|
981
|
+
classDecl: {
|
|
982
|
+
// check for ret here instead of using ||= because we can't short-circuit
|
|
983
|
+
if (!lastNode || ret)
|
|
984
|
+
break classDecl;
|
|
985
|
+
const rawMap = this.tryParseClassDeclaration(lastNode, [x.name]);
|
|
986
|
+
if (!rawMap)
|
|
987
|
+
break classDecl;
|
|
988
|
+
ret = this.rawMapToExportMap(rawMap);
|
|
989
|
+
}
|
|
990
|
+
ret ||= this.makeExportMapRecursive(x);
|
|
991
|
+
// ensure we aren't nested
|
|
992
|
+
ret = (function nestLoop(curName, obj) {
|
|
993
|
+
if (Array.isArray(obj)) {
|
|
994
|
+
return obj;
|
|
995
|
+
}
|
|
996
|
+
const keys = allEntries(obj);
|
|
997
|
+
if (keys.length === 1 && keys[0][0] !== WebpackAstParser.SYM_HOVER) {
|
|
998
|
+
if (obj[curName]) {
|
|
999
|
+
return nestLoop(curName, obj[curName]);
|
|
1000
|
+
}
|
|
1001
|
+
const [[key]] = keys;
|
|
1002
|
+
assertNotHover(obj[key]);
|
|
1003
|
+
obj[key] = nestLoop(key, obj[key]);
|
|
1004
|
+
return obj;
|
|
1005
|
+
}
|
|
1006
|
+
for (const [k] of keys) {
|
|
1007
|
+
if (k === WebpackAstParser.SYM_HOVER) {
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
assertNotHover(obj[k]);
|
|
1011
|
+
obj[k] = nestLoop(k, obj[k]);
|
|
1012
|
+
}
|
|
1013
|
+
return obj;
|
|
1014
|
+
})(x.name.getText(), ret);
|
|
1015
|
+
return lastNode != null ? [x.name.getText(), ret] : false;
|
|
1016
|
+
})
|
|
1017
|
+
.filter((x) => x !== false));
|
|
1018
|
+
}
|
|
1019
|
+
tryParseStoreForExport(node, extraStoreLocs = []) {
|
|
1020
|
+
if (!node)
|
|
1021
|
+
return;
|
|
1022
|
+
if (!isIdentifier(node)) {
|
|
1023
|
+
logger.debug("[WebpackAstParser] Could not find identifier for store export");
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const decl = this.getVarInfoFromUse(node);
|
|
1027
|
+
if (!decl)
|
|
1028
|
+
return;
|
|
1029
|
+
const allUses = decl.uses
|
|
1030
|
+
.map(({ location }) => location)
|
|
1031
|
+
.concat(...decl.declarations);
|
|
1032
|
+
// find where it's set to the new store
|
|
1033
|
+
// there should never be more than one assignment
|
|
1034
|
+
const uses = allUses.filter((ident) => {
|
|
1035
|
+
return this.isVariableAssignmentLike(ident.parent);
|
|
1036
|
+
});
|
|
1037
|
+
if (uses.length === 0) {
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
else if (uses.length > 1) {
|
|
1041
|
+
logger.warn(`[WebpackAstParser] Found more than one store assignment in module ${this.moduleId}, this should not happen`);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
const [use] = uses;
|
|
1045
|
+
const initializer = (() => {
|
|
1046
|
+
if (isVariableDeclaration(use.parent)) {
|
|
1047
|
+
if (!use.parent.initializer) {
|
|
1048
|
+
throw new Error("[WebpackAstParser] Variable declaration has no initializer, this should be filtered out by the previous isVariableAssignmentLike check");
|
|
1049
|
+
}
|
|
1050
|
+
return use.parent.initializer;
|
|
1051
|
+
}
|
|
1052
|
+
else if (this.isAssignmentExpression(use.parent)) {
|
|
1053
|
+
return use.parent.right;
|
|
1054
|
+
}
|
|
1055
|
+
throw new Error("Unexpected type for use, this should not happen");
|
|
1056
|
+
})();
|
|
1057
|
+
if (!isNewExpression(initializer))
|
|
1058
|
+
return;
|
|
1059
|
+
const store = this.tryParseStore(initializer);
|
|
1060
|
+
if (!store) {
|
|
1061
|
+
logger.debug("[WebpackAstParser] Failed to parse store");
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const ret = {};
|
|
1065
|
+
const def = [];
|
|
1066
|
+
def.push(...extraStoreLocs);
|
|
1067
|
+
def.push(...store.store.map((x) => this.makeRangeFromAstNode(x)));
|
|
1068
|
+
ret[WebpackAstParser.SYM_CJS_DEFAULT] = def;
|
|
1069
|
+
ret[WebpackAstParser.SYM_HOVER] = store[WebpackAstParser.SYM_HOVER];
|
|
1070
|
+
for (const [name, loc] of allEntries(store.methods)) {
|
|
1071
|
+
const map = this.makeExportMapRecursive(loc);
|
|
1072
|
+
ret[name] = map;
|
|
1073
|
+
}
|
|
1074
|
+
for (const [name, loc] of allEntries(store.props)) {
|
|
1075
|
+
const map = this.makeExportMapRecursive(loc);
|
|
1076
|
+
ret[name] = map;
|
|
1077
|
+
}
|
|
1078
|
+
return ret;
|
|
1079
|
+
}
|
|
1080
|
+
// TODO: test this
|
|
1081
|
+
tryParseStore(storeInit) {
|
|
1082
|
+
const ret = {
|
|
1083
|
+
store: [],
|
|
1084
|
+
fluxEvents: {},
|
|
1085
|
+
methods: {},
|
|
1086
|
+
props: {},
|
|
1087
|
+
[WebpackAstParser.SYM_HOVER]: undefined,
|
|
1088
|
+
};
|
|
1089
|
+
const storeVar = storeInit.expression;
|
|
1090
|
+
const args = storeInit.arguments;
|
|
1091
|
+
parseArgs: {
|
|
1092
|
+
if (!args)
|
|
1093
|
+
break parseArgs;
|
|
1094
|
+
if (args.length !== 2) {
|
|
1095
|
+
logger.debug(`[WebpackAstParser] Incorrect number of arguments for a store instantiation, expected 2, found ${args?.length}`);
|
|
1096
|
+
break parseArgs;
|
|
1097
|
+
}
|
|
1098
|
+
const [, events] = args;
|
|
1099
|
+
if (!isObjectLiteralExpression(events)) {
|
|
1100
|
+
logger.warn("[WebpackAstParser] Expected the flux events to be an object literal expression");
|
|
1101
|
+
break parseArgs;
|
|
1102
|
+
}
|
|
1103
|
+
// FIXME: extract into function
|
|
1104
|
+
for (const prop of events.properties) {
|
|
1105
|
+
if (!isPropertyAssignment(prop)) {
|
|
1106
|
+
logger.debug("[WebpackAstParser] found prop that is not a property assignment, this should be handled");
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
ret.fluxEvents[prop.name.getText()] = [prop.initializer];
|
|
1110
|
+
if (isIdentifier(prop.initializer)) {
|
|
1111
|
+
const trail = this.unwrapVariableDeclaration(prop.initializer)?.toReversed();
|
|
1112
|
+
if (trail)
|
|
1113
|
+
ret.fluxEvents[prop.name.getText()].push(...trail);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (!isIdentifier(storeVar)) {
|
|
1118
|
+
// TODO: parse this
|
|
1119
|
+
logger.debug("[WebpackAstParser] anything than an identifier is not supported for store instantiations yet");
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
ret.store.push(storeVar);
|
|
1123
|
+
const storeVarInfo = this.getVarInfoFromUse(storeVar);
|
|
1124
|
+
if (!storeVarInfo || storeVarInfo.declarations.length === 0) {
|
|
1125
|
+
logger.debug("[WebpackAstParser] Could not find store declaration");
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
if (storeVarInfo.declarations.length > 1) {
|
|
1129
|
+
logger.warn("[WebpackAstParser] Found more than one store declaration, this should not happen");
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const [decl] = storeVarInfo.declarations;
|
|
1133
|
+
ret.store.push(decl);
|
|
1134
|
+
const classDecl = decl.parent;
|
|
1135
|
+
if (!isClassDeclaration(classDecl)) {
|
|
1136
|
+
logger.warn("[WebpackAstParser] Store decl is not a class");
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
// check if any of the extends clauses extend Store
|
|
1140
|
+
// this is the best we can do to ensure something is a store
|
|
1141
|
+
// TODO: make sure it does not extend a component
|
|
1142
|
+
const doesExtend = (classDecl.heritageClauses?.length ?? -1) > 0;
|
|
1143
|
+
if (!doesExtend) {
|
|
1144
|
+
logger.debug("[WebpackAstParser] Store class does not extend Store");
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
ret[WebpackAstParser.SYM_HOVER] = this.tryFindStoreDisplayName(storeVarInfo);
|
|
1148
|
+
for (const member of classDecl.members) {
|
|
1149
|
+
if (isMethodDeclaration(member)) {
|
|
1150
|
+
if (!member.body)
|
|
1151
|
+
continue;
|
|
1152
|
+
ret.methods[member.name.getText()] = member;
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
else if (isConstructorDeclaration(member)) {
|
|
1156
|
+
ret.store.push(member);
|
|
1157
|
+
}
|
|
1158
|
+
else if (isPropertyDeclaration(member)) {
|
|
1159
|
+
if (!member.initializer) {
|
|
1160
|
+
logger.warn("Property declaration has no initializer, this should not happen");
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
ret.props[member.name.getText()] = member.initializer;
|
|
1164
|
+
}
|
|
1165
|
+
else if (isAccessorDeclaration(member)) {
|
|
1166
|
+
if (!member.body)
|
|
1167
|
+
continue;
|
|
1168
|
+
ret.methods[member.name.getText()] = member;
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
logger.warn("Unhandled store member type. This should be handled");
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return ret;
|
|
1175
|
+
}
|
|
1176
|
+
tryFindStoreDisplayName(storeVar) {
|
|
1177
|
+
// Display names can be set in two ways:
|
|
1178
|
+
// 1. define(store, "displayName", "MyStore")
|
|
1179
|
+
// OR sometimes the bundler will inline the function so it will look something like this
|
|
1180
|
+
// 2. (i = "displayName")in m ? Object.defineProperty(store, i, {
|
|
1181
|
+
// value: myStoreNameVar,
|
|
1182
|
+
// enumerable: !0,
|
|
1183
|
+
// configurable: !0,
|
|
1184
|
+
// writable: !0
|
|
1185
|
+
// }) : store[i] = myStoreNameVar;
|
|
1186
|
+
const uses = storeVar.uses
|
|
1187
|
+
.map(({ location }) => {
|
|
1188
|
+
return findParentLimited(location, isCallExpression, 2);
|
|
1189
|
+
})
|
|
1190
|
+
.filter(nonNull)
|
|
1191
|
+
.filter((call) => call && call.arguments.length === 3);
|
|
1192
|
+
for (const use of uses) {
|
|
1193
|
+
const [arg1, arg2, arg3] = use.arguments;
|
|
1194
|
+
if (isStringLiteralLike(arg2) && arg2.text === "displayName" && isStringLiteralLike(arg3)) {
|
|
1195
|
+
return arg3.text;
|
|
1196
|
+
}
|
|
1197
|
+
// Object.defineProperty(store)
|
|
1198
|
+
// store must be an identifier
|
|
1199
|
+
// store must be the same as storeVar
|
|
1200
|
+
if (!isIdentifier(arg1) || this.usesToVars.get(arg1) !== storeVar) {
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
// Object.defineProperty's second arg must be an identifier
|
|
1204
|
+
if (!isIdentifier(arg2)) {
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1207
|
+
// The second arg must be "displayName"
|
|
1208
|
+
const arg2Info = this.getVarInfoFromUse(arg2);
|
|
1209
|
+
if (!arg2Info) {
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
const arg2Init = this.findSingleAssignment(arg2Info);
|
|
1213
|
+
if (!arg2Init || !isStringLiteralLike(arg2Init) || arg2Init.text !== "displayName") {
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
// the third arg of Object.defineProperty must be an object literal expression
|
|
1217
|
+
if (!isObjectLiteralExpression(arg3)) {
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
const valueProp = findObjectLiteralByKey(arg3, "value");
|
|
1221
|
+
if (!valueProp || !isPropertyAssignment(valueProp)) {
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
const valueInit = valueProp.initializer;
|
|
1225
|
+
if (!this.isIdentifier(valueInit)) {
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
const valueInfo = this.getVarInfoFromUse(valueInit);
|
|
1229
|
+
if (!valueInfo) {
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
const maybeStoreName = this.findSingleAssignment(valueInfo);
|
|
1233
|
+
if (!maybeStoreName || !isStringLiteralLike(maybeStoreName)) {
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
return maybeStoreName.text;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
*
|
|
1241
|
+
* @returns ```js
|
|
1242
|
+
* {
|
|
1243
|
+
* "<PASSED_IN_CLASS_NAME>": {
|
|
1244
|
+
* [WebpackAstParser.SYM_CJS_DEFAULT]: ["<CONSTRUCTOR>"],
|
|
1245
|
+
* ["methodName"]: ["METHOD"]
|
|
1246
|
+
* }
|
|
1247
|
+
* }
|
|
1248
|
+
* ```
|
|
1249
|
+
*/
|
|
1250
|
+
parseClassDeclaration(clazz, extraExportRanges = []) {
|
|
1251
|
+
const ret = {
|
|
1252
|
+
[WebpackAstParser.SYM_CJS_DEFAULT]: [...extraExportRanges, clazz.name ?? clazz.getChildAt(0)],
|
|
1253
|
+
};
|
|
1254
|
+
for (const member of clazz.members) {
|
|
1255
|
+
if (isMethodDeclaration(member)) {
|
|
1256
|
+
if (!member.body)
|
|
1257
|
+
continue;
|
|
1258
|
+
ret[member.name.getText()] = [member.name];
|
|
1259
|
+
}
|
|
1260
|
+
else if (isConstructorDeclaration(member)) {
|
|
1261
|
+
// the ConstructoKeyword
|
|
1262
|
+
const arr = ret[WebpackAstParser.SYM_CJS_DEFAULT];
|
|
1263
|
+
if (!Array.isArray(arr)) {
|
|
1264
|
+
logger.error("CJS default export is not an array, this should be never happen");
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
arr.push(member.getChildAt(0));
|
|
1268
|
+
}
|
|
1269
|
+
else if (isPropertyDeclaration(member)) {
|
|
1270
|
+
ret[member.name.getText()] = [member.name];
|
|
1271
|
+
}
|
|
1272
|
+
else if (isAccessorDeclaration(member)) {
|
|
1273
|
+
if (!member.body)
|
|
1274
|
+
continue;
|
|
1275
|
+
ret[member.name.getText()] = [member.name];
|
|
1276
|
+
}
|
|
1277
|
+
else if (isSemicolonClassElement(member)) {
|
|
1278
|
+
// ignore this
|
|
1279
|
+
}
|
|
1280
|
+
else {
|
|
1281
|
+
logger.warn("Unhandled class member type. This should be handled");
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
// name ?? ClassKeyword
|
|
1285
|
+
ret[WebpackAstParser.SYM_CJS_DEFAULT] ??= [clazz.name ?? clazz.getChildAt(0)];
|
|
1286
|
+
return ret;
|
|
1287
|
+
}
|
|
1288
|
+
tryParseClassDeclaration(node, extraExportRanges) {
|
|
1289
|
+
if (!isIdentifier(node)) {
|
|
1290
|
+
// FIXME: handle this
|
|
1291
|
+
logger.trace("[WebpackAstParser] trying to parse a class decl starting with a non-identifier node, this should be handled");
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
const varInfo = this.getVarInfoFromUse(node);
|
|
1295
|
+
if (!varInfo) {
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
// classes should only have one decl.
|
|
1299
|
+
// if someone proves me wrong on this (with an example in discord's code), ill support it
|
|
1300
|
+
if (varInfo.declarations.length !== 1) {
|
|
1301
|
+
if (varInfo.declarations.length > 1) {
|
|
1302
|
+
logger.error("[WebpackAstParser] Found more than one class declaration. this should not happen");
|
|
1303
|
+
}
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
const [decl] = varInfo.declarations;
|
|
1307
|
+
if (!isClassDeclaration(decl.parent)) {
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
return this.parseClassDeclaration(decl.parent, extraExportRanges);
|
|
1311
|
+
}
|
|
1312
|
+
getNestedExportFromMap(keys, map) {
|
|
1313
|
+
let i = 0;
|
|
1314
|
+
let cur = map;
|
|
1315
|
+
while ((cur = cur[keys[i++]])) {
|
|
1316
|
+
if (Array.isArray(cur)) {
|
|
1317
|
+
return cur;
|
|
1318
|
+
}
|
|
1319
|
+
else if (Array.isArray(cur[WebpackAstParser.SYM_CJS_DEFAULT])) {
|
|
1320
|
+
// @ts-expect-error i just fucking checked this typescript
|
|
1321
|
+
return cur[WebpackAstParser.SYM_CJS_DEFAULT];
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
return undefined;
|
|
1325
|
+
}
|
|
1326
|
+
findExportLocation(exportNames) {
|
|
1327
|
+
let cur = this.getExportMap();
|
|
1328
|
+
let range = zeroRange;
|
|
1329
|
+
let i = 0;
|
|
1330
|
+
while ((cur = cur[exportNames[i++]])) {
|
|
1331
|
+
if (Array.isArray(cur)) {
|
|
1332
|
+
const g = cur.at(-1);
|
|
1333
|
+
if (g)
|
|
1334
|
+
range = g;
|
|
1335
|
+
else
|
|
1336
|
+
logger.error("Empty array of exports");
|
|
1337
|
+
break;
|
|
1338
|
+
// fallback to the most appropriate thing
|
|
1339
|
+
// most of the time, it's the default export
|
|
1340
|
+
}
|
|
1341
|
+
else if (Array.isArray(cur[WebpackAstParser.SYM_CJS_DEFAULT])) {
|
|
1342
|
+
const g = cur[WebpackAstParser.SYM_CJS_DEFAULT].at(-1);
|
|
1343
|
+
if (g)
|
|
1344
|
+
range = g;
|
|
1345
|
+
else
|
|
1346
|
+
logger.error("Empty array of exports");
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
return range;
|
|
1350
|
+
}
|
|
1351
|
+
findHoverText(exportNames) {
|
|
1352
|
+
let cur = this.getExportMap();
|
|
1353
|
+
let lastHover;
|
|
1354
|
+
let i = 0;
|
|
1355
|
+
while ((cur = cur[exportNames[i++]])) {
|
|
1356
|
+
if (Array.isArray(cur) || typeof cur === "string") {
|
|
1357
|
+
break;
|
|
1358
|
+
}
|
|
1359
|
+
else if (cur[WebpackAstParser.SYM_HOVER]) {
|
|
1360
|
+
lastHover = cur[WebpackAstParser.SYM_HOVER];
|
|
1361
|
+
}
|
|
1362
|
+
else if (cur[WebpackAstParser.SYM_CJS_DEFAULT]?.[WebpackAstParser.SYM_HOVER]) {
|
|
1363
|
+
lastHover = cur[WebpackAstParser.SYM_CJS_DEFAULT]?.[WebpackAstParser.SYM_HOVER];
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
lastHover = undefined;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return lastHover;
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* @Cache
|
|
1373
|
+
*/
|
|
1374
|
+
findWreq_d() {
|
|
1375
|
+
if (this.uses) {
|
|
1376
|
+
const maybeWreqD = this.uses.uses.find((use) => getLeadingIdentifier(use.location)[1]?.text === "d")?.location.parent.parent;
|
|
1377
|
+
if (!maybeWreqD || !isCallExpression(maybeWreqD))
|
|
1378
|
+
return undefined;
|
|
1379
|
+
if (maybeWreqD.arguments.length !== 2
|
|
1380
|
+
|| !isIdentifier(maybeWreqD.arguments[0])
|
|
1381
|
+
|| !isObjectLiteralExpression(maybeWreqD.arguments[1]))
|
|
1382
|
+
return undefined;
|
|
1383
|
+
return maybeWreqD;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
tryFindExportwreq_d(exportName) {
|
|
1387
|
+
if (this.uses) {
|
|
1388
|
+
const wreq_dCall = this.findWreq_d();
|
|
1389
|
+
if (!wreq_dCall)
|
|
1390
|
+
return undefined;
|
|
1391
|
+
// the a: function(){return b;} of wreq.d
|
|
1392
|
+
const exportCallAssignment = findObjectLiteralByKey(wreq_dCall.arguments[1], exportName);
|
|
1393
|
+
if (!exportCallAssignment
|
|
1394
|
+
|| !isPropertyAssignment(exportCallAssignment)
|
|
1395
|
+
|| !(isFunctionExpression(exportCallAssignment.initializer)
|
|
1396
|
+
|| isArrowFunction(exportCallAssignment.initializer)))
|
|
1397
|
+
return undefined;
|
|
1398
|
+
const exportVar = findReturnIdentifier(exportCallAssignment.initializer);
|
|
1399
|
+
if (exportVar) {
|
|
1400
|
+
/**
|
|
1401
|
+
* This is probably bad for perf
|
|
1402
|
+
*
|
|
1403
|
+
* consider {@link this.getVarInfoFromUse}
|
|
1404
|
+
*/
|
|
1405
|
+
const [exportDec] = [...this.vars.entries()].find(([, v]) => {
|
|
1406
|
+
return v.uses.some((use) => use.location === exportVar);
|
|
1407
|
+
}) ?? [];
|
|
1408
|
+
if (!exportDec)
|
|
1409
|
+
return undefined;
|
|
1410
|
+
return this.makeRangeFromAstNode(exportDec);
|
|
1411
|
+
}
|
|
1412
|
+
const reExport = findReturnPropertyAccessExpression(exportCallAssignment.initializer);
|
|
1413
|
+
if (reExport) {
|
|
1414
|
+
return this.makeRangeFromAstNode(reExport.name);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* @Cache
|
|
1420
|
+
*/
|
|
1421
|
+
findWreq_t() {
|
|
1422
|
+
return this.findWebpackArg(1);
|
|
1423
|
+
}
|
|
1424
|
+
tryFindExportWreq_t(exportName) {
|
|
1425
|
+
const wreq_t = this.findWreq_t();
|
|
1426
|
+
if (!wreq_t)
|
|
1427
|
+
return undefined;
|
|
1428
|
+
const uses = this.vars.get(wreq_t);
|
|
1429
|
+
if (!uses)
|
|
1430
|
+
return undefined;
|
|
1431
|
+
const exports = uses.uses.find(({ location }) => {
|
|
1432
|
+
const [, exportAssignment] = getLeadingIdentifier(location);
|
|
1433
|
+
return exportAssignment?.text === exportName;
|
|
1434
|
+
});
|
|
1435
|
+
return exports ? this.makeRangeFromAstNode(exports.location) : undefined;
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* @Cache
|
|
1439
|
+
*/
|
|
1440
|
+
findWreq_e() {
|
|
1441
|
+
return this.findWebpackArg(0);
|
|
1442
|
+
}
|
|
1443
|
+
tryFindExportsWreq_e(exportName) {
|
|
1444
|
+
const wreq_e = this.findWreq_e();
|
|
1445
|
+
if (!wreq_e)
|
|
1446
|
+
return undefined;
|
|
1447
|
+
const uses = this.vars.get(wreq_e);
|
|
1448
|
+
if (!uses)
|
|
1449
|
+
return undefined;
|
|
1450
|
+
const exportAssignment = uses.uses.find(({ location }) => {
|
|
1451
|
+
const [, moduleProp] = getLeadingIdentifier(location);
|
|
1452
|
+
return moduleProp?.text === "exports";
|
|
1453
|
+
});
|
|
1454
|
+
if (!exportAssignment)
|
|
1455
|
+
return undefined;
|
|
1456
|
+
const exportObject = findParent(exportAssignment.location, isBinaryExpression)?.right;
|
|
1457
|
+
if (!exportObject || !isObjectLiteralExpression(exportObject))
|
|
1458
|
+
return undefined;
|
|
1459
|
+
const exportItem = findObjectLiteralByKey(exportObject, exportName);
|
|
1460
|
+
if (!exportItem)
|
|
1461
|
+
return undefined;
|
|
1462
|
+
return this.makeRangeFromAstNode(exportItem.name ?? exportItem);
|
|
1463
|
+
}
|
|
1464
|
+
// TODO: support lazy requires
|
|
1465
|
+
async getAllReExportsForExport(exportName) {
|
|
1466
|
+
const ret = [];
|
|
1467
|
+
const thisExports = this.getExportMapRaw();
|
|
1468
|
+
if (!thisExports[exportName]) {
|
|
1469
|
+
throw new Error(`Export ${exportName.toString()} not found in module ${this.moduleId}`);
|
|
1470
|
+
}
|
|
1471
|
+
const toSearch = this.getModulesThatRequireThisModule()
|
|
1472
|
+
?.sync
|
|
1473
|
+
?.map((mod) => [this, mod, exportName])
|
|
1474
|
+
?? [];
|
|
1475
|
+
let cur;
|
|
1476
|
+
while ((cur = toSearch.pop())) {
|
|
1477
|
+
const [thisParser, moduleId, exportName] = cur;
|
|
1478
|
+
const moduleText = await this.moduleCache.getModuleFromNum(moduleId);
|
|
1479
|
+
const otherParser = new WebpackAstParser(moduleText);
|
|
1480
|
+
if (!(thisParser.moduleId && otherParser.moduleId)) {
|
|
1481
|
+
throw new Error("Module is is not set, this should not happen");
|
|
1482
|
+
}
|
|
1483
|
+
const otherReExportName = otherParser.doesReExportFromImport(thisParser.moduleId, exportName);
|
|
1484
|
+
if (otherReExportName) {
|
|
1485
|
+
ret.push([otherParser.moduleId, [otherReExportName]]);
|
|
1486
|
+
for (const mod of otherParser.getModulesThatRequireThisModule()?.sync ?? []) {
|
|
1487
|
+
toSearch.push([otherParser, mod, otherReExportName]);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return ret;
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* @returns the string of the export if this is the flux dispatcher module, null otherwise
|
|
1495
|
+
* @Cache
|
|
1496
|
+
*/
|
|
1497
|
+
isFluxDispatcherModule() {
|
|
1498
|
+
const moduleExports = this.getExportMapRaw();
|
|
1499
|
+
// the flux dispatcher module exports a single class
|
|
1500
|
+
if (Object.keys(moduleExports).length !== 1) {
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
const [mainExport] = Object.entries(moduleExports);
|
|
1504
|
+
if ([
|
|
1505
|
+
"isDispatching",
|
|
1506
|
+
"dispatch",
|
|
1507
|
+
"dispatchForStoreTest",
|
|
1508
|
+
"flushWaitQueue",
|
|
1509
|
+
"_dispatchWithDevtools",
|
|
1510
|
+
"_dispatchWithLogging",
|
|
1511
|
+
"_dispatch",
|
|
1512
|
+
"addInterceptor",
|
|
1513
|
+
"wait",
|
|
1514
|
+
"subscribe",
|
|
1515
|
+
"unsubscribe",
|
|
1516
|
+
"register",
|
|
1517
|
+
"createToken",
|
|
1518
|
+
"addDependencies",
|
|
1519
|
+
WebpackAstParser.SYM_CJS_DEFAULT,
|
|
1520
|
+
]
|
|
1521
|
+
.some((key) => !(key in mainExport[1]))) {
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
return mainExport[0];
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* checks if this module exports a flux dispatcher
|
|
1528
|
+
*
|
|
1529
|
+
* @returns the string of the export name if this module exports a flux dispatcher
|
|
1530
|
+
*/
|
|
1531
|
+
exportsFluxDispatcherInstance() {
|
|
1532
|
+
const moduleExports = this.getExportMapRaw();
|
|
1533
|
+
// no exports, cant be flux dispatcher
|
|
1534
|
+
if (allEntries(moduleExports).length === 0) {
|
|
1535
|
+
return null;
|
|
1536
|
+
}
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
__decorate([
|
|
1541
|
+
CacheGetter()
|
|
1542
|
+
], WebpackAstParser.prototype, "wreq", null);
|
|
1543
|
+
__decorate([
|
|
1544
|
+
CacheGetter()
|
|
1545
|
+
], WebpackAstParser.prototype, "uses", null);
|
|
1546
|
+
__decorate([
|
|
1547
|
+
CacheGetter()
|
|
1548
|
+
], WebpackAstParser.prototype, "moduleId", null);
|
|
1549
|
+
__decorate([
|
|
1550
|
+
CacheGetter()
|
|
1551
|
+
], WebpackAstParser.prototype, "moduleCache", null);
|
|
1552
|
+
__decorate([
|
|
1553
|
+
CacheGetter()
|
|
1554
|
+
], WebpackAstParser.prototype, "moduleDepManager", null);
|
|
1555
|
+
__decorate([
|
|
1556
|
+
Cache()
|
|
1557
|
+
], WebpackAstParser.prototype, "getModulesThatThisModuleRequires", null);
|
|
1558
|
+
__decorate([
|
|
1559
|
+
Cache()
|
|
1560
|
+
], WebpackAstParser.prototype, "doesReExportWholeModule", null);
|
|
1561
|
+
__decorate([
|
|
1562
|
+
Cache()
|
|
1563
|
+
], WebpackAstParser.prototype, "getExportMapRaw", null);
|
|
1564
|
+
__decorate([
|
|
1565
|
+
Cache()
|
|
1566
|
+
], WebpackAstParser.prototype, "getExportMapRawWreq_d", null);
|
|
1567
|
+
__decorate([
|
|
1568
|
+
Cache()
|
|
1569
|
+
], WebpackAstParser.prototype, "getExportMapRawWreq_e", null);
|
|
1570
|
+
__decorate([
|
|
1571
|
+
Cache()
|
|
1572
|
+
], WebpackAstParser.prototype, "getExportMapRawWreq_t", null);
|
|
1573
|
+
__decorate([
|
|
1574
|
+
Cache()
|
|
1575
|
+
], WebpackAstParser.prototype, "getExportMap", null);
|
|
1576
|
+
__decorate([
|
|
1577
|
+
Cache()
|
|
1578
|
+
], WebpackAstParser.prototype, "getExportMapWreq_t", null);
|
|
1579
|
+
__decorate([
|
|
1580
|
+
Cache()
|
|
1581
|
+
], WebpackAstParser.prototype, "getExportMapWreq_e", null);
|
|
1582
|
+
__decorate([
|
|
1583
|
+
Cache()
|
|
1584
|
+
], WebpackAstParser.prototype, "getExportMapWreq_d", null);
|
|
1585
|
+
__decorate([
|
|
1586
|
+
Cache()
|
|
1587
|
+
], WebpackAstParser.prototype, "findWreq_d", null);
|
|
1588
|
+
__decorate([
|
|
1589
|
+
Cache()
|
|
1590
|
+
], WebpackAstParser.prototype, "findWreq_t", null);
|
|
1591
|
+
__decorate([
|
|
1592
|
+
Cache()
|
|
1593
|
+
], WebpackAstParser.prototype, "findWreq_e", null);
|
|
1594
|
+
__decorate([
|
|
1595
|
+
Cache()
|
|
1596
|
+
], WebpackAstParser.prototype, "isFluxDispatcherModule", null);
|
|
1597
|
+
//# sourceMappingURL=WebpackAstParser.js.map
|