@plexcord-companion/plexcord-ast-parser 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ import { type Expression, type Identifier, type Node, type ObjectLiteralExpression } from "typescript";
2
+ import { AstParser, type Import } from "@plexcord-companion/ast-parser";
3
+ import { type Logger } from "@plexcord-companion/shared/Logger";
4
+ import type { FindUse, FunctionNode, IFindType, IReplacement, PatchData, RegexNode, SourcePatch, StringNode } from "./types.js";
5
+ export declare function setLogger(newLogger: Logger): void;
6
+ export declare class PlexcordAstParser extends AstParser {
7
+ private readonly _path;
8
+ get path(): string;
9
+ /**
10
+ * @CacheGetter
11
+ */
12
+ get imports(): Map<Identifier, Import>;
13
+ constructor(content: string, path: string);
14
+ /**
15
+ * @Cache
16
+ */
17
+ private findDefinePlugin;
18
+ getPluginName(): string | null;
19
+ /**
20
+ * @Cache
21
+ */
22
+ getPatches(): SourcePatch[];
23
+ parseFind(patch: ObjectLiteralExpression): IFindType | null;
24
+ /**
25
+ * Try to parse a string literal
26
+ *
27
+ * if it is a template literal, attempt to extract the string content by inlining variables
28
+ */
29
+ tryParseStringLiteral(node: Node): string | null;
30
+ tryParseStringLiteralToStringNode(node: Node): StringNode | null;
31
+ tryParseFunction(node: Node): FunctionNode | null;
32
+ parseReplace(node: Expression): StringNode | FunctionNode | null;
33
+ parseMatch(node: Expression): StringNode | RegexNode | null;
34
+ parseReplacement(patch: ObjectLiteralExpression): IReplacement[] | null;
35
+ parsePatch(patch: ObjectLiteralExpression): PatchData | null;
36
+ /**
37
+ * @returns true if this file is the entry point (if it has a definePlugin call)
38
+ */
39
+ isRootPluginFile(): boolean;
40
+ /**
41
+ * @Cache
42
+ */
43
+ getFinds(): FindUse[];
44
+ /**
45
+ * @Cache
46
+ */
47
+ private getFindUses;
48
+ /**
49
+ * returns the import if this identifier is imported
50
+ */
51
+ private isIdentifierImported;
52
+ private listImports;
53
+ }
@@ -0,0 +1,325 @@
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 { DeclarationDomain } from "ts-api-utils";
8
+ import { createPrinter, EmitHint, isArrayLiteralExpression, isArrowFunction, isCallExpression, isFunctionExpression, isIdentifier, isNamespaceImport, isObjectLiteralExpression, isPropertyAssignment, isRegularExpressionLiteral, isStringLiteral, isStringLiteralLike, isTemplateExpression, isVariableDeclaration, ScriptTarget, transpileModule, } from "typescript";
9
+ import { AstParser, findObjectLiteralByKey, findParent, getImportName, getImportSource, isDefaultImport, isInImportStatment, } from "@plexcord-companion/ast-parser";
10
+ import { Cache, CacheGetter } from "@plexcord-companion/shared/decorators";
11
+ import { NoopLogger } from "@plexcord-companion/shared/Logger";
12
+ import { tryParseRegularExpressionLiteral } from "./util.js";
13
+ let logger = NoopLogger;
14
+ export function setLogger(newLogger) {
15
+ logger = newLogger;
16
+ }
17
+ export class PlexcordAstParser extends AstParser {
18
+ _path;
19
+ get path() {
20
+ return this._path;
21
+ }
22
+ /**
23
+ * @CacheGetter
24
+ */
25
+ get imports() {
26
+ return this.listImports();
27
+ }
28
+ constructor(content, path) {
29
+ super(content);
30
+ this._path = path;
31
+ }
32
+ /**
33
+ * @Cache
34
+ */
35
+ findDefinePlugin() {
36
+ const define = [...this.imports.values()].find((x) => {
37
+ if (!x.default)
38
+ return;
39
+ if (x.source !== "@utils/types")
40
+ return;
41
+ return true;
42
+ });
43
+ if (!define)
44
+ return;
45
+ const uses = this.vars.get(define.as)?.uses;
46
+ if (!uses)
47
+ return;
48
+ const definePlugin = uses.find(({ location }) => {
49
+ if (!isCallExpression(location.parent))
50
+ return;
51
+ return location.parent.arguments.length === 1 && isObjectLiteralExpression(location.parent.arguments[0]);
52
+ });
53
+ return (definePlugin?.location.parent).arguments[0];
54
+ }
55
+ // TODO: work on files in the plugin folder but not the root plugin file
56
+ getPluginName() {
57
+ const definePlugin = this.findDefinePlugin();
58
+ if (!definePlugin)
59
+ return null;
60
+ const nameProp = findObjectLiteralByKey(definePlugin, "name");
61
+ if (!nameProp || !isPropertyAssignment(nameProp) || !isStringLiteral(nameProp.initializer))
62
+ return null;
63
+ return nameProp.initializer.text;
64
+ }
65
+ /**
66
+ * @Cache
67
+ */
68
+ getPatches() {
69
+ const definePlugin = this.findDefinePlugin();
70
+ if (!definePlugin)
71
+ return [];
72
+ const patchesProp = findObjectLiteralByKey(definePlugin, "patches");
73
+ if (!patchesProp || !isPropertyAssignment(patchesProp) || !isArrayLiteralExpression(patchesProp.initializer))
74
+ return [];
75
+ return patchesProp.initializer.elements
76
+ .map((x, origIndex) => {
77
+ if (!isObjectLiteralExpression(x))
78
+ return null;
79
+ const res = this.parsePatch(x);
80
+ if (!res)
81
+ return null;
82
+ return {
83
+ ...res,
84
+ range: this.makeRangeFromAstNode(x.getChildAt(1)),
85
+ origIndex,
86
+ };
87
+ })
88
+ .filter((x) => x !== null);
89
+ }
90
+ parseFind(patch) {
91
+ const find = findObjectLiteralByKey(patch, "find");
92
+ if (!find || !isPropertyAssignment(find))
93
+ return null;
94
+ if (!(isStringLiteral(find.initializer) || isRegularExpressionLiteral(find.initializer)))
95
+ return null;
96
+ return {
97
+ findType: isStringLiteral(find.initializer) ? "string" : "regex",
98
+ find: find.initializer.text,
99
+ };
100
+ }
101
+ /**
102
+ * Try to parse a string literal
103
+ *
104
+ * if it is a template literal, attempt to extract the string content by inlining variables
105
+ */
106
+ tryParseStringLiteral(node) {
107
+ tryParse: if (isStringLiteralLike(node)) {
108
+ return node.text;
109
+ // resolve template literals if they are constant
110
+ }
111
+ else if (isTemplateExpression(node)) {
112
+ const resolvedSpans = [];
113
+ for (const span of node.templateSpans) {
114
+ const spanExpr = span.expression;
115
+ if (!isIdentifier(spanExpr)) {
116
+ logger.debug(`[PlexcordAstParser] Trying to parse template literal with non-identifier span: ${span.getText()}, FileName: ${span.getSourceFile().fileName}`);
117
+ break tryParse;
118
+ }
119
+ const usageInfo = this.getVarInfoFromUse(spanExpr);
120
+ if (!usageInfo) {
121
+ break tryParse;
122
+ }
123
+ else if (usageInfo.declarations.length === 0) {
124
+ logger.trace(`[PlexcordAstParser] Could not resolve identifier ${spanExpr.text} to a variable declaration. Is it a global?`);
125
+ break tryParse;
126
+ }
127
+ const isConst = this.isConstDeclared(usageInfo);
128
+ if (!isConst) {
129
+ break tryParse;
130
+ }
131
+ const decl = findParent(isConst[0], isVariableDeclaration);
132
+ if (!decl) {
133
+ break tryParse;
134
+ }
135
+ const init = decl.initializer;
136
+ if (!init) {
137
+ break tryParse;
138
+ }
139
+ const initValue = this.tryParseStringLiteral(init);
140
+ // explicitly check for null to avoid empty string
141
+ if (initValue == null) {
142
+ break tryParse;
143
+ }
144
+ resolvedSpans.push(initValue + span.literal.text);
145
+ }
146
+ return node.head.text + resolvedSpans.join("");
147
+ }
148
+ return null;
149
+ }
150
+ tryParseStringLiteralToStringNode(node) {
151
+ const str = this.tryParseStringLiteral(node);
152
+ if (str == null)
153
+ return null;
154
+ return {
155
+ type: "string",
156
+ value: str,
157
+ };
158
+ }
159
+ tryParseFunction(node) {
160
+ if (!isArrowFunction(node) && !isFunctionExpression(node))
161
+ return null;
162
+ const code = createPrinter()
163
+ .printNode(EmitHint.Expression, node, node.getSourceFile());
164
+ const res = transpileModule(code, {
165
+ compilerOptions: {
166
+ target: ScriptTarget.ESNext,
167
+ strict: true,
168
+ },
169
+ });
170
+ if (res.diagnostics && res.diagnostics.length > 0)
171
+ return null;
172
+ return {
173
+ type: "function",
174
+ value: res.outputText,
175
+ };
176
+ }
177
+ parseReplace(node) {
178
+ return this.tryParseStringLiteralToStringNode(node) ?? this.tryParseFunction(node);
179
+ }
180
+ parseMatch(node) {
181
+ return this.tryParseStringLiteralToStringNode(node) ?? tryParseRegularExpressionLiteral(node);
182
+ }
183
+ parseReplacement(patch) {
184
+ const replacementObj = findObjectLiteralByKey(patch, "replacement");
185
+ if (!replacementObj || !isPropertyAssignment(replacementObj))
186
+ return null;
187
+ const replacement = replacementObj.initializer;
188
+ const replacements = isArrayLiteralExpression(replacement) ? replacement.elements : [replacement];
189
+ if (!replacements.every(isObjectLiteralExpression))
190
+ return null;
191
+ const replacementValues = replacements.map((r) => {
192
+ const match = findObjectLiteralByKey(r, "match");
193
+ const replace = findObjectLiteralByKey(r, "replace");
194
+ if (!replace || !isPropertyAssignment(replace) || !match || !isPropertyAssignment(match))
195
+ return null;
196
+ const matchValue = this.parseMatch(match.initializer);
197
+ if (!matchValue)
198
+ return null;
199
+ const replaceValue = this.parseReplace(replace.initializer);
200
+ if (replaceValue == null)
201
+ return null;
202
+ return {
203
+ match: matchValue,
204
+ replace: replaceValue,
205
+ };
206
+ })
207
+ .filter((x) => x != null);
208
+ return replacementValues.length > 0 ? replacementValues : null;
209
+ }
210
+ parsePatch(patch) {
211
+ const find = this.parseFind(patch);
212
+ const replacement = this.parseReplacement(patch);
213
+ if (!replacement || !find)
214
+ return null;
215
+ return {
216
+ ...find,
217
+ replacement,
218
+ };
219
+ }
220
+ /**
221
+ * @returns true if this file is the entry point (if it has a definePlugin call)
222
+ */
223
+ isRootPluginFile() {
224
+ return !!this.findDefinePlugin();
225
+ }
226
+ /**
227
+ * @Cache
228
+ */
229
+ getFinds() {
230
+ return this.getFindUses()
231
+ .map((x) => {
232
+ const call = x.parent;
233
+ if (call.arguments.length === 0)
234
+ return false;
235
+ const args = call.arguments.map((x) => {
236
+ return this.tryParseStringLiteralToStringNode(x)
237
+ ?? tryParseRegularExpressionLiteral(x)
238
+ ?? this.tryParseFunction(x);
239
+ });
240
+ const range = this.makeRangeFromAstNode(call);
241
+ return {
242
+ range,
243
+ use: {
244
+ type: "testFind",
245
+ data: {
246
+ type: x.getText(),
247
+ args: args.filter((x) => x != null),
248
+ },
249
+ },
250
+ };
251
+ })
252
+ .filter((x) => x !== false);
253
+ }
254
+ /**
255
+ * @Cache
256
+ */
257
+ getFindUses() {
258
+ const imports = [...this.imports.entries()].flatMap(([k, v]) => {
259
+ if (v.source !== "@webpack")
260
+ return [];
261
+ const origName = (v.orig ?? v.as).getText();
262
+ if (!origName.startsWith("find"))
263
+ return [];
264
+ return k;
265
+ });
266
+ const toRet = [];
267
+ for (const i of imports) {
268
+ const uses = this.vars.get(i)?.uses;
269
+ if (!uses)
270
+ continue;
271
+ for (const { location } of uses) {
272
+ if (!isCallExpression(location.parent))
273
+ continue;
274
+ toRet.push(location);
275
+ }
276
+ }
277
+ return toRet;
278
+ }
279
+ /**
280
+ * returns the import if this identifier is imported
281
+ */
282
+ isIdentifierImported(i) {
283
+ const { declarations, domain } = this.vars.get(i) ?? {};
284
+ if (!declarations || declarations.length === 0)
285
+ return;
286
+ if (!(domain & DeclarationDomain.Import))
287
+ return;
288
+ const source = declarations.flatMap((x) => {
289
+ if (!isInImportStatment(x))
290
+ return [];
291
+ return x;
292
+ });
293
+ if (source.length !== 1)
294
+ return;
295
+ const [importIdent] = source;
296
+ return {
297
+ default: isDefaultImport(importIdent),
298
+ namespace: isNamespaceImport(importIdent),
299
+ ...getImportName(importIdent),
300
+ source: getImportSource(importIdent),
301
+ };
302
+ }
303
+ listImports() {
304
+ return new Map([...this.vars.entries()].flatMap(([k]) => {
305
+ const ret = this.isIdentifierImported(k);
306
+ return ret ? [[k, ret]] : [];
307
+ }));
308
+ }
309
+ }
310
+ __decorate([
311
+ CacheGetter()
312
+ ], PlexcordAstParser.prototype, "imports", null);
313
+ __decorate([
314
+ Cache()
315
+ ], PlexcordAstParser.prototype, "findDefinePlugin", null);
316
+ __decorate([
317
+ Cache()
318
+ ], PlexcordAstParser.prototype, "getPatches", null);
319
+ __decorate([
320
+ Cache()
321
+ ], PlexcordAstParser.prototype, "getFinds", null);
322
+ __decorate([
323
+ Cache()
324
+ ], PlexcordAstParser.prototype, "getFindUses", null);
325
+ //# sourceMappingURL=PlexcordAstParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlexcordAstParser.js","sourceRoot":"","sources":["../src/PlexcordAstParser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from "./PlexcordAstParser.js";
2
+ export * from "./types.js";
3
+ export * from "./util.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./PlexcordAstParser.js";
2
+ export * from "./types.js";
3
+ export * from "./util.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { Range } from "@plexcord-companion/shared/Range";
2
+ export type AnyFindType = `find${"Component" | "ByProps" | "Store" | "ByCode" | "ModuleId" | "ComponentByCode" | ""}${"Lazy" | ""}`;
3
+ export type StringNode = {
4
+ type: "string";
5
+ value: string;
6
+ };
7
+ export type RegexNode = {
8
+ type: "regex";
9
+ value: {
10
+ pattern: string;
11
+ flags: string;
12
+ };
13
+ };
14
+ export type FunctionNode = {
15
+ type: "function";
16
+ value: string;
17
+ };
18
+ export type FindNode = StringNode | RegexNode | FunctionNode;
19
+ export type FindData = {
20
+ type: AnyFindType;
21
+ args: FindNode[];
22
+ };
23
+ export type IReplacement = {
24
+ match: StringNode | RegexNode;
25
+ replace: StringNode | FunctionNode;
26
+ };
27
+ export type IFindType = {
28
+ findType: "string";
29
+ /**
30
+ * the find string
31
+ */
32
+ find: string;
33
+ } | {
34
+ findType: "regex";
35
+ /**
36
+ * stringified regex
37
+ */
38
+ find: string;
39
+ };
40
+ export type PatchData = IFindType & {
41
+ replacement: IReplacement[];
42
+ };
43
+ /**
44
+ * a parsed patch, as it appears in a source file
45
+ */
46
+ export type SourcePatch = PatchData & {
47
+ range: Range;
48
+ origIndex: number;
49
+ };
50
+ export type FindUse = {
51
+ range: Range;
52
+ use: TestFind;
53
+ };
54
+ export type TestFind = {
55
+ type: "testFind";
56
+ data: FindData;
57
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ import { Range } from "@plexcord-companion/shared/Range";
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kCAAkC,CAAC"}
package/dist/util.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { type Node } from "typescript";
2
+ import type { FunctionNode, RegexNode, StringNode } from "./types.js";
3
+ export declare function tryParseStringLiteral(node: Node): StringNode | null;
4
+ export declare function tryParseRegularExpressionLiteral(node: Node): RegexNode | null;
5
+ export declare function tryParseFunction(path: string, node: Node): FunctionNode | null;
package/dist/util.js ADDED
@@ -0,0 +1,42 @@
1
+ import { createPrinter, EmitHint, findConfigFile, isArrowFunction, isFunctionExpression, isRegularExpressionLiteral, isStringLiteral, parseJsonConfigFileContent, readConfigFile, sys, transpileModule, } from "typescript";
2
+ import { basename } from "node:path";
3
+ export function tryParseStringLiteral(node) {
4
+ if (!isStringLiteral(node))
5
+ return null;
6
+ return {
7
+ type: "string",
8
+ value: node.text,
9
+ };
10
+ }
11
+ export function tryParseRegularExpressionLiteral(node) {
12
+ if (!isRegularExpressionLiteral(node))
13
+ return null;
14
+ const m = node.text.match(/^\/(.+)\/(.*?)$/);
15
+ return m && {
16
+ type: "regex",
17
+ value: {
18
+ pattern: m[1],
19
+ flags: m[2],
20
+ },
21
+ };
22
+ }
23
+ export function tryParseFunction(path, node) {
24
+ if (!isArrowFunction(node) && !isFunctionExpression(node))
25
+ return null;
26
+ const code = createPrinter()
27
+ .printNode(EmitHint.Expression, node, node.getSourceFile());
28
+ let compilerOptions = {};
29
+ const tsConfigPath = findConfigFile(path, sys.fileExists);
30
+ if (tsConfigPath) {
31
+ const configFile = readConfigFile(tsConfigPath, sys.readFile);
32
+ compilerOptions = parseJsonConfigFileContent(configFile.config, sys, basename(tsConfigPath)).options;
33
+ }
34
+ const res = transpileModule(code, { compilerOptions });
35
+ if (res.diagnostics && res.diagnostics.length > 0)
36
+ return null;
37
+ return {
38
+ type: "function",
39
+ value: res.outputText,
40
+ };
41
+ }
42
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,aAAa,EACb,QAAQ,EACR,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,0BAA0B,EAC1B,eAAe,EAEf,0BAA0B,EAC1B,cAAc,EACd,GAAG,EACH,eAAe,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAKrC,MAAM,UAAU,qBAAqB,CAAC,IAAU;IAC5C,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;QACtB,OAAO,IAAI,CAAC;IAEhB,OAAO;QACH,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI,CAAC,IAAI;KACnB,CAAC;AACN,CAAC;AAED,MAAM,UAAU,gCAAgC,CAAC,IAAU;IACvD,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC;QACjC,OAAO,IAAI,CAAC;IAEhB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAE7C,OAAO,CAAC,IAAI;QACR,IAAI,EAAE,OAAO;QACb,KAAK,EAAE;YACH,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;YACb,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;SACd;KACJ,CAAC;AACN,CAAC;AACD,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,IAAU;IACrD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC;QACrD,OAAO,IAAI,CAAC;IAEhB,MAAM,IAAI,GAAG,aAAa,EAAE;SACvB,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IAEhE,IAAI,eAAe,GAAoB,EAAE,CAAC;IAC1C,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAE1D,IAAI,YAAY,EAAE,CAAC;QACf,MAAM,UAAU,GAAG,cAAc,CAAC,YAAY,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QAE9D,eAAe,GAAG,0BAA0B,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;IACzG,CAAC;IAED,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,EAAE,EAAE,eAAe,EAAE,CAAC,CAAC;IAEvD,IAAI,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;QAC7C,OAAO,IAAI,CAAC;IAEhB,OAAO;QACH,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,GAAG,CAAC,UAAU;KACxB,CAAC;AACN,CAAC"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@plexcord-companion/plexcord-ast-parser",
3
+ "version": "2.0.0",
4
+ "description": "",
5
+ "author": {
6
+ "name": "MutanPlex",
7
+ "url": "https://mutanplex.com"
8
+ },
9
+ "main": "index.js",
10
+ "exports": {
11
+ "./*": "./dist/*.js",
12
+ ".": "./dist/index.js"
13
+ },
14
+ "keywords": [],
15
+ "license": "LGPL-3.0-or-later",
16
+ "dependencies": {
17
+ "@plexcord-companion/ast-parser": "2.0.0",
18
+ "@plexcord-companion/shared": "2.0.0"
19
+ },
20
+ "peerDependencies": {
21
+ "ts-api-utils": "^2.1.0",
22
+ "typescript": "^5.9.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^24.6.0",
26
+ "vitest": "^3.2.4"
27
+ },
28
+ "scripts": {
29
+ "preversion": "mkdir .git || :",
30
+ "bump": "npm version",
31
+ "postversion": "rmdir .git",
32
+ "build": "tsc -b && tsc-alias -f"
33
+ }
34
+ }
@@ -0,0 +1,429 @@
1
+ import { DeclarationDomain } from "ts-api-utils";
2
+ import {
3
+ type CallExpression,
4
+ createPrinter,
5
+ EmitHint,
6
+ type Expression,
7
+ type Identifier,
8
+ isArrayLiteralExpression,
9
+ isArrowFunction,
10
+ isCallExpression,
11
+ isFunctionExpression,
12
+ isIdentifier,
13
+ isNamespaceImport,
14
+ isObjectLiteralExpression,
15
+ isPropertyAssignment,
16
+ isRegularExpressionLiteral,
17
+ isStringLiteral,
18
+ isStringLiteralLike,
19
+ isTemplateExpression,
20
+ isVariableDeclaration,
21
+ type Node,
22
+ type ObjectLiteralExpression,
23
+ ScriptTarget,
24
+ transpileModule,
25
+ } from "typescript";
26
+
27
+ import {
28
+ AstParser,
29
+ findObjectLiteralByKey,
30
+ findParent,
31
+ getImportName,
32
+ getImportSource,
33
+ type Import,
34
+ isDefaultImport,
35
+ isInImportStatment,
36
+ type WithParent,
37
+ } from "@plexcord-companion/ast-parser";
38
+ import { Cache, CacheGetter } from "@plexcord-companion/shared/decorators";
39
+ import { type Logger, NoopLogger } from "@plexcord-companion/shared/Logger";
40
+
41
+ import type { FindUse, FunctionNode, IFindType, IReplacement, PatchData, RegexNode, SourcePatch, StringNode, TestFind } from "./types";
42
+ import { tryParseRegularExpressionLiteral } from "./util";
43
+
44
+
45
+ let logger: Logger = NoopLogger;
46
+
47
+ export function setLogger(newLogger: Logger): void {
48
+ logger = newLogger;
49
+ }
50
+
51
+ export class PlexcordAstParser extends AstParser {
52
+ private readonly _path: string;
53
+
54
+ public get path(): string {
55
+ return this._path;
56
+ }
57
+
58
+ /**
59
+ * @CacheGetter
60
+ */
61
+ @CacheGetter()
62
+ public get imports(): Map<Identifier, Import> {
63
+ return this.listImports();
64
+ }
65
+
66
+ constructor(content: string, path: string) {
67
+ super(content);
68
+ this._path = path;
69
+ }
70
+
71
+ /**
72
+ * @Cache
73
+ */
74
+ @Cache()
75
+ private findDefinePlugin(): ObjectLiteralExpression | undefined {
76
+ const define = [...this.imports.values()].find((x) => {
77
+ if (!x.default)
78
+ return;
79
+ if (x.source !== "@utils/types")
80
+ return;
81
+ return true;
82
+ });
83
+
84
+ if (!define)
85
+ return;
86
+
87
+ const uses = this.vars.get(define.as)?.uses;
88
+
89
+ if (!uses)
90
+ return;
91
+
92
+ const definePlugin = uses.find(({ location }) => {
93
+ if (!isCallExpression(location.parent))
94
+ return;
95
+ return location.parent.arguments.length === 1 && isObjectLiteralExpression(location.parent.arguments[0]);
96
+ });
97
+
98
+ return (definePlugin?.location.parent as CallExpression).arguments[0] as ObjectLiteralExpression;
99
+ }
100
+
101
+ // TODO: work on files in the plugin folder but not the root plugin file
102
+ public getPluginName(): string | null {
103
+ const definePlugin = this.findDefinePlugin();
104
+
105
+ if (!definePlugin)
106
+ return null;
107
+
108
+ const nameProp = findObjectLiteralByKey(definePlugin, "name");
109
+
110
+ if (!nameProp || !isPropertyAssignment(nameProp) || !isStringLiteral(nameProp.initializer))
111
+ return null;
112
+ return nameProp.initializer.text;
113
+ }
114
+
115
+ /**
116
+ * @Cache
117
+ */
118
+ @Cache()
119
+ public getPatches(): SourcePatch[] {
120
+ const definePlugin = this.findDefinePlugin();
121
+
122
+ if (!definePlugin)
123
+ return [];
124
+
125
+ const patchesProp = findObjectLiteralByKey(definePlugin, "patches");
126
+
127
+ if (!patchesProp || !isPropertyAssignment(patchesProp) || !isArrayLiteralExpression(patchesProp.initializer))
128
+ return [];
129
+ return patchesProp.initializer.elements
130
+ .map((x, origIndex) => {
131
+ if (!isObjectLiteralExpression(x))
132
+ return null;
133
+
134
+ const res = this.parsePatch(x);
135
+
136
+ if (!res)
137
+ return null;
138
+ return {
139
+ ...res,
140
+ range: this.makeRangeFromAstNode(x.getChildAt(1)),
141
+ origIndex,
142
+ };
143
+ })
144
+ .filter((x) => x !== null);
145
+ }
146
+
147
+ parseFind(patch: ObjectLiteralExpression): IFindType | null {
148
+ const find = findObjectLiteralByKey(patch, "find");
149
+
150
+ if (!find || !isPropertyAssignment(find))
151
+ return null;
152
+ if (!(isStringLiteral(find.initializer) || isRegularExpressionLiteral(find.initializer)))
153
+ return null;
154
+
155
+ return {
156
+ findType: isStringLiteral(find.initializer) ? "string" : "regex",
157
+ find: find.initializer.text,
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Try to parse a string literal
163
+ *
164
+ * if it is a template literal, attempt to extract the string content by inlining variables
165
+ */
166
+ tryParseStringLiteral(node: Node): string | null {
167
+ tryParse: if (isStringLiteralLike(node)) {
168
+ return node.text;
169
+ // resolve template literals if they are constant
170
+ } else if (isTemplateExpression(node)) {
171
+ const resolvedSpans = [] as string[];
172
+
173
+ for (const span of node.templateSpans) {
174
+ const spanExpr = span.expression;
175
+
176
+ if (!isIdentifier(spanExpr)) {
177
+ logger.debug(`[PlexcordAstParser] Trying to parse template literal with non-identifier span: ${span.getText()}, FileName: ${span.getSourceFile().fileName}`);
178
+ break tryParse;
179
+ }
180
+
181
+ const usageInfo = this.getVarInfoFromUse(spanExpr);
182
+
183
+ if (!usageInfo) {
184
+ break tryParse;
185
+ } else if (usageInfo.declarations.length === 0) {
186
+ logger.trace(`[PlexcordAstParser] Could not resolve identifier ${spanExpr.text} to a variable declaration. Is it a global?`);
187
+ break tryParse;
188
+ }
189
+
190
+ const isConst = this.isConstDeclared(usageInfo);
191
+
192
+ if (!isConst) {
193
+ break tryParse;
194
+ }
195
+
196
+ const decl = findParent(isConst[0], isVariableDeclaration);
197
+
198
+ if (!decl) {
199
+ break tryParse;
200
+ }
201
+
202
+ const init = decl.initializer;
203
+
204
+ if (!init) {
205
+ break tryParse;
206
+ }
207
+
208
+ const initValue = this.tryParseStringLiteral(init);
209
+
210
+ // explicitly check for null to avoid empty string
211
+ if (initValue == null) {
212
+ break tryParse;
213
+ }
214
+
215
+ resolvedSpans.push(initValue + span.literal.text);
216
+ }
217
+
218
+ return node.head.text + resolvedSpans.join("");
219
+ }
220
+ return null;
221
+ }
222
+
223
+ tryParseStringLiteralToStringNode(node: Node): StringNode | null {
224
+ const str = this.tryParseStringLiteral(node);
225
+
226
+ if (str == null)
227
+ return null;
228
+
229
+ return {
230
+ type: "string",
231
+ value: str,
232
+ };
233
+ }
234
+
235
+ tryParseFunction(node: Node): FunctionNode | null {
236
+ if (!isArrowFunction(node) && !isFunctionExpression(node))
237
+ return null;
238
+
239
+ const code = createPrinter()
240
+ .printNode(EmitHint.Expression, node, node.getSourceFile());
241
+
242
+ const res = transpileModule(code, {
243
+ compilerOptions: {
244
+ target: ScriptTarget.ESNext,
245
+ strict: true,
246
+ },
247
+ });
248
+
249
+ if (res.diagnostics && res.diagnostics.length > 0)
250
+ return null;
251
+
252
+ return {
253
+ type: "function",
254
+ value: res.outputText,
255
+ };
256
+ }
257
+
258
+ parseReplace(node: Expression): StringNode | FunctionNode | null {
259
+ return this.tryParseStringLiteralToStringNode(node) ?? this.tryParseFunction(node);
260
+ }
261
+
262
+ parseMatch(node: Expression): StringNode | RegexNode | null {
263
+ return this.tryParseStringLiteralToStringNode(node) ?? tryParseRegularExpressionLiteral(node);
264
+ }
265
+
266
+ parseReplacement(patch: ObjectLiteralExpression): IReplacement[] | null {
267
+ const replacementObj = findObjectLiteralByKey(patch, "replacement");
268
+
269
+ if (!replacementObj || !isPropertyAssignment(replacementObj))
270
+ return null;
271
+
272
+ const replacement = replacementObj.initializer;
273
+ const replacements = isArrayLiteralExpression(replacement) ? replacement.elements : [replacement];
274
+
275
+ if (!replacements.every(isObjectLiteralExpression))
276
+ return null;
277
+
278
+ const replacementValues = (replacements as ObjectLiteralExpression[]).map((r: ObjectLiteralExpression) => {
279
+ const match = findObjectLiteralByKey(r, "match");
280
+ const replace = findObjectLiteralByKey(r, "replace");
281
+
282
+ if (!replace || !isPropertyAssignment(replace) || !match || !isPropertyAssignment(match))
283
+ return null;
284
+
285
+ const matchValue = this.parseMatch(match.initializer);
286
+
287
+ if (!matchValue)
288
+ return null;
289
+
290
+ const replaceValue = this.parseReplace(replace.initializer);
291
+
292
+ if (replaceValue == null)
293
+ return null;
294
+
295
+ return {
296
+ match: matchValue,
297
+ replace: replaceValue,
298
+ };
299
+ })
300
+ .filter((x) => x != null);
301
+
302
+ return replacementValues.length > 0 ? replacementValues : null;
303
+ }
304
+
305
+ parsePatch(patch: ObjectLiteralExpression): PatchData | null {
306
+ const find = this.parseFind(patch);
307
+ const replacement = this.parseReplacement(patch);
308
+
309
+ if (!replacement || !find)
310
+ return null;
311
+
312
+ return {
313
+ ...find,
314
+ replacement,
315
+ };
316
+ }
317
+
318
+
319
+ /**
320
+ * @returns true if this file is the entry point (if it has a definePlugin call)
321
+ */
322
+ public isRootPluginFile(): boolean {
323
+ return !!this.findDefinePlugin();
324
+ }
325
+
326
+ /**
327
+ * @Cache
328
+ */
329
+ @Cache()
330
+ public getFinds(): FindUse[] {
331
+ return this.getFindUses()
332
+ .map<FindUse | false>((x) => {
333
+ const call = x.parent;
334
+
335
+ if (call.arguments.length === 0)
336
+ return false;
337
+
338
+ const args = call.arguments.map((x) => {
339
+ return this.tryParseStringLiteralToStringNode(x)
340
+ ?? tryParseRegularExpressionLiteral(x)
341
+ ?? this.tryParseFunction(x);
342
+ });
343
+
344
+ const range = this.makeRangeFromAstNode(call);
345
+
346
+ return {
347
+ range,
348
+ use: {
349
+ type: "testFind",
350
+ data: {
351
+ type: x.getText() as TestFind["data"]["type"],
352
+ args: args.filter((x) => x != null),
353
+ },
354
+ },
355
+ };
356
+ })
357
+ .filter((x) => x !== false);
358
+ }
359
+
360
+ /**
361
+ * @Cache
362
+ */
363
+ @Cache()
364
+ private getFindUses(): WithParent<Identifier, CallExpression>[] {
365
+ const imports = [...this.imports.entries()].flatMap(([k, v]) => {
366
+ if (v.source !== "@webpack")
367
+ return [];
368
+
369
+ const origName = (v.orig ?? v.as).getText();
370
+
371
+ if (!origName.startsWith("find"))
372
+ return [];
373
+ return k;
374
+ });
375
+
376
+ const toRet: WithParent<Identifier, CallExpression>[] = [];
377
+
378
+ for (const i of imports) {
379
+ const uses = this.vars.get(i)?.uses;
380
+
381
+ if (!uses)
382
+ continue;
383
+ for (const { location } of uses) {
384
+ if (!isCallExpression(location.parent))
385
+ continue;
386
+ toRet.push(location as WithParent<Identifier, CallExpression>);
387
+ }
388
+ }
389
+ return toRet;
390
+ }
391
+
392
+ /**
393
+ * returns the import if this identifier is imported
394
+ */
395
+ private isIdentifierImported(i: Identifier): Import | undefined {
396
+ const { declarations, domain } = this.vars.get(i) ?? {};
397
+
398
+ if (!declarations || declarations.length === 0)
399
+ return;
400
+ if (!(domain! & DeclarationDomain.Import))
401
+ return;
402
+
403
+ const source = declarations.flatMap((x) => {
404
+ if (!isInImportStatment(x))
405
+ return [];
406
+ return x;
407
+ });
408
+
409
+ if (source.length !== 1)
410
+ return;
411
+
412
+ const [importIdent] = source;
413
+
414
+ return {
415
+ default: isDefaultImport(importIdent),
416
+ namespace: isNamespaceImport(importIdent),
417
+ ...getImportName(importIdent),
418
+ source: getImportSource(importIdent),
419
+ };
420
+ }
421
+
422
+ private listImports(): Map<Identifier, Import> {
423
+ return new Map([...this.vars.entries()].flatMap(([k]) => {
424
+ const ret = this.isIdentifierImported(k);
425
+
426
+ return ret ? [[k, ret]] : [];
427
+ }));
428
+ }
429
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./PlexcordAstParser";
2
+ export * from "./types";
3
+ export * from "./util";
package/src/types.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { Range } from "@plexcord-companion/shared/Range";
2
+ export type AnyFindType
3
+ = `find${"Component" | "ByProps" | "Store" | "ByCode" | "ModuleId" | "ComponentByCode" | ""}${"Lazy" | ""}`;
4
+
5
+ export type StringNode = {
6
+ type: "string";
7
+ value: string;
8
+ };
9
+
10
+ export type RegexNode = {
11
+ type: "regex";
12
+ value: {
13
+ pattern: string;
14
+ flags: string;
15
+ };
16
+ };
17
+
18
+ export type FunctionNode = {
19
+ type: "function";
20
+ value: string;
21
+ };
22
+
23
+ export type FindNode = StringNode | RegexNode | FunctionNode;
24
+
25
+ export type FindData = {
26
+ type: AnyFindType;
27
+ args: FindNode[];
28
+ };
29
+ export type IReplacement = {
30
+ match: StringNode | RegexNode;
31
+ replace: StringNode | FunctionNode;
32
+ };
33
+ export type IFindType
34
+ = | {
35
+ findType: "string";
36
+ /**
37
+ * the find string
38
+ */
39
+ find: string;
40
+ }
41
+ | {
42
+ findType: "regex";
43
+ /**
44
+ * stringified regex
45
+ */
46
+ find: string;
47
+ };
48
+ export type PatchData = IFindType & {
49
+ replacement: IReplacement[];
50
+ };
51
+ /**
52
+ * a parsed patch, as it appears in a source file
53
+ */
54
+ export type SourcePatch = PatchData & { range: Range;
55
+ origIndex: number; };
56
+ export type FindUse = {
57
+ range: Range;
58
+ use: TestFind;
59
+ };
60
+
61
+ export type TestFind = {
62
+ type: "testFind";
63
+ data: FindData;
64
+ };
package/src/util.ts ADDED
@@ -0,0 +1,71 @@
1
+ import {
2
+ type CompilerOptions,
3
+ createPrinter,
4
+ EmitHint,
5
+ findConfigFile,
6
+ isArrowFunction,
7
+ isFunctionExpression,
8
+ isRegularExpressionLiteral,
9
+ isStringLiteral,
10
+ type Node,
11
+ parseJsonConfigFileContent,
12
+ readConfigFile,
13
+ sys,
14
+ transpileModule,
15
+ } from "typescript";
16
+
17
+ import { basename } from "node:path";
18
+
19
+ import type { FunctionNode, RegexNode, StringNode } from "./types";
20
+
21
+
22
+ export function tryParseStringLiteral(node: Node): StringNode | null {
23
+ if (!isStringLiteral(node))
24
+ return null;
25
+
26
+ return {
27
+ type: "string",
28
+ value: node.text,
29
+ };
30
+ }
31
+
32
+ export function tryParseRegularExpressionLiteral(node: Node): RegexNode | null {
33
+ if (!isRegularExpressionLiteral(node))
34
+ return null;
35
+
36
+ const m = node.text.match(/^\/(.+)\/(.*?)$/);
37
+
38
+ return m && {
39
+ type: "regex",
40
+ value: {
41
+ pattern: m[1],
42
+ flags: m[2],
43
+ },
44
+ };
45
+ }
46
+ export function tryParseFunction(path: string, node: Node): FunctionNode | null {
47
+ if (!isArrowFunction(node) && !isFunctionExpression(node))
48
+ return null;
49
+
50
+ const code = createPrinter()
51
+ .printNode(EmitHint.Expression, node, node.getSourceFile());
52
+
53
+ let compilerOptions: CompilerOptions = {};
54
+ const tsConfigPath = findConfigFile(path, sys.fileExists);
55
+
56
+ if (tsConfigPath) {
57
+ const configFile = readConfigFile(tsConfigPath, sys.readFile);
58
+
59
+ compilerOptions = parseJsonConfigFileContent(configFile.config, sys, basename(tsConfigPath)).options;
60
+ }
61
+
62
+ const res = transpileModule(code, { compilerOptions });
63
+
64
+ if (res.diagnostics && res.diagnostics.length > 0)
65
+ return null;
66
+
67
+ return {
68
+ type: "function",
69
+ value: res.outputText,
70
+ };
71
+ }