@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.
@@ -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