deslop-js 0.6.2 → 0.7.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,2308 @@
1
+ import { parseSync } from "oxc-parser";
2
+ import { readFileSync, statSync } from "node:fs";
3
+
4
+ //#region src/constants.ts
5
+ const DEFAULT_EXTENSIONS = [
6
+ ".ts",
7
+ ".tsx",
8
+ ".js",
9
+ ".jsx",
10
+ ".mts",
11
+ ".mjs",
12
+ ".cts",
13
+ ".cjs",
14
+ ".mdx",
15
+ ".astro",
16
+ ".graphql",
17
+ ".gql",
18
+ ".css",
19
+ ".scss",
20
+ ".vue",
21
+ ".svelte"
22
+ ];
23
+ const STANDALONE_PROJECT_LOCKFILES = [
24
+ "package-lock.json",
25
+ "yarn.lock",
26
+ "pnpm-lock.yaml",
27
+ "bun.lockb"
28
+ ];
29
+ const MONOREPO_ROOT_MARKERS = [
30
+ "pnpm-workspace.yaml",
31
+ "pnpm-workspace.yml",
32
+ "lerna.json",
33
+ "nx.json",
34
+ "turbo.json",
35
+ "rush.json"
36
+ ];
37
+ const LOCKFILE_MARKERS = [
38
+ "pnpm-lock.yaml",
39
+ "yarn.lock",
40
+ "package-lock.json",
41
+ "bun.lockb",
42
+ "bun.lock"
43
+ ];
44
+ const ANALYZED_MANIFEST_FILENAMES = [...new Set([
45
+ "package.json",
46
+ "pnpm-workspace.yaml",
47
+ "lerna.json",
48
+ "app.json",
49
+ "ng-package.json",
50
+ ".gitignore",
51
+ ...STANDALONE_PROJECT_LOCKFILES,
52
+ ...MONOREPO_ROOT_MARKERS,
53
+ ...LOCKFILE_MARKERS
54
+ ])];
55
+ const OUTPUT_DIRECTORIES = [
56
+ "dist",
57
+ "build",
58
+ "out",
59
+ "esm",
60
+ "cjs"
61
+ ];
62
+ const SOURCE_EXTENSIONS = [
63
+ "ts",
64
+ "tsx",
65
+ "mts",
66
+ "cts",
67
+ "js",
68
+ "jsx",
69
+ "mjs",
70
+ "cjs"
71
+ ];
72
+ const SCRIPT_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|tsc|npx|bun|esr|esno|jiti|babel-node|zx)\s+(?:\S+\s+)*?([\w./@-]+\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs))(?:\s|$)/;
73
+ const SCRIPT_EXTENSIONLESS_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|bun|esr|esno|jiti|babel-node|zx)\s+(?:\S+\s+)*?((?:[./]|[\w@][\w@-]*\/)[\w./@-]+)(?:\s|$)/;
74
+ const SCRIPT_CONFIG_FILE_PATTERN = /--config\s+([\w./@-]+\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs))/;
75
+ const SCRIPT_ENTRY_PATTERNS = [];
76
+ const RESOLVER_EXTENSIONS = [
77
+ ...DEFAULT_EXTENSIONS,
78
+ ".d.ts",
79
+ ".d.mts",
80
+ ".d.cts",
81
+ ".json",
82
+ ".node",
83
+ ".css",
84
+ ".scss",
85
+ ".less",
86
+ ".svg",
87
+ ".png",
88
+ ".jpg",
89
+ ".graphql",
90
+ ".gql"
91
+ ];
92
+ const MAX_PARSE_FILE_SIZE_BYTES = 2e6;
93
+ const MAX_ERROR_DETAIL_LENGTH = 1e3;
94
+ const BINARY_DETECTION_SAMPLE_BYTES = 2048;
95
+ const MINIFIED_DETECTION_MIN_BYTES = 5e3;
96
+ /**
97
+ * Numeric literals below 1000 are dominated by indices, counters, small
98
+ * ranges, ports, percentages, and array sizes that coincide by accident
99
+ * (every `MAX_RETRIES = 3` is not a duplicate of every `LIMIT = 3`).
100
+ * 1000 admits real shared constants (timeouts in ms, byte sizes, polling
101
+ * intervals) without producing the noise floor that smaller magnitudes do.
102
+ * NOTE: even at 1000, the rule still produces medium-confidence false
103
+ * positives when constants share a value coincidentally with different
104
+ * names (e.g. `STEP_DELAY_MS` vs `MINIMUM_TOKENS`); the report explicitly
105
+ * downgrades those to `confidence: "medium"`.
106
+ */
107
+ const MIN_NUMERIC_LITERAL_MAGNITUDE_FOR_DUPLICATE = 1e3;
108
+
109
+ //#endregion
110
+ //#region src/errors.ts
111
+ const truncateDetail = (text) => {
112
+ if (text.length <= 1e3) return text;
113
+ return `${text.slice(0, MAX_ERROR_DETAIL_LENGTH)}… [truncated ${text.length - MAX_ERROR_DETAIL_LENGTH} chars]`;
114
+ };
115
+ const describeUnknownError = (caughtValue) => {
116
+ let rawText;
117
+ if (caughtValue instanceof Error) rawText = caughtValue.message || caughtValue.name || "unknown error";
118
+ else if (typeof caughtValue === "string") rawText = caughtValue;
119
+ else try {
120
+ rawText = JSON.stringify(caughtValue);
121
+ } catch {
122
+ rawText = String(caughtValue);
123
+ }
124
+ return truncateDetail(rawText ?? "");
125
+ };
126
+ var DeslopError = class DeslopError extends Error {
127
+ constructor(input) {
128
+ super(input.message);
129
+ this.name = "DeslopError";
130
+ this.code = input.code;
131
+ this.module = input.module;
132
+ this.severity = input.severity ?? "warning";
133
+ if (input.path !== void 0) this.path = input.path;
134
+ if (input.detail !== void 0) this.detail = input.detail;
135
+ }
136
+ toJSON() {
137
+ const payload = {
138
+ name: this.name,
139
+ code: this.code,
140
+ module: this.module,
141
+ severity: this.severity,
142
+ message: this.message
143
+ };
144
+ if (this.path !== void 0) payload.path = this.path;
145
+ if (this.detail !== void 0) payload.detail = this.detail;
146
+ return payload;
147
+ }
148
+ static fromCaught(input) {
149
+ return new DeslopError({
150
+ code: input.code,
151
+ module: input.module,
152
+ severity: input.severity,
153
+ message: input.message,
154
+ path: input.path,
155
+ detail: describeUnknownError(input.caught)
156
+ });
157
+ }
158
+ };
159
+ var FileReadError = class extends DeslopError {
160
+ constructor(input) {
161
+ super({
162
+ ...input,
163
+ module: "parse"
164
+ });
165
+ this.name = "FileReadError";
166
+ }
167
+ };
168
+ var ParseError = class extends DeslopError {
169
+ constructor(input) {
170
+ super({
171
+ ...input,
172
+ module: "parse"
173
+ });
174
+ this.name = "ParseError";
175
+ }
176
+ };
177
+
178
+ //#endregion
179
+ //#region src/utils/line-column.ts
180
+ const getLineFromOffset = (source, offset) => {
181
+ let line = 1;
182
+ for (let charIndex = 0; charIndex < offset && charIndex < source.length; charIndex++) if (source[charIndex] === "\n") line++;
183
+ return line;
184
+ };
185
+ const getColumnFromOffset = (source, offset) => {
186
+ let column = 0;
187
+ for (let charIndex = offset - 1; charIndex >= 0; charIndex--) {
188
+ if (source[charIndex] === "\n") break;
189
+ column++;
190
+ }
191
+ return column;
192
+ };
193
+
194
+ //#endregion
195
+ //#region src/utils/extract-default-export-local-name.ts
196
+ const extractIdentifierFromCallArguments = (expression) => {
197
+ if (expression.type !== "CallExpression") return void 0;
198
+ for (const argument of expression.arguments) {
199
+ if (argument.type === "Identifier" && argument.name) return argument.name;
200
+ if (argument.type === "CallExpression") {
201
+ const nestedName = extractIdentifierFromCallArguments(argument);
202
+ if (nestedName) return nestedName;
203
+ }
204
+ }
205
+ if (expression.callee.type === "CallExpression") return extractIdentifierFromCallArguments(expression.callee);
206
+ };
207
+ const extractDefaultExportLocalName = (declaration) => {
208
+ if (!declaration) return void 0;
209
+ if (declaration.type === "Identifier" && declaration.name) return declaration.name;
210
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return declaration.id?.name;
211
+ if (declaration.type === "CallExpression") return extractIdentifierFromCallArguments(declaration);
212
+ };
213
+
214
+ //#endregion
215
+ //#region src/utils/detect-redundant-type-pattern.ts
216
+ const isTypeNode = (value) => Boolean(value) && typeof value === "object" && typeof value.type === "string";
217
+ const isEmptyTypeLiteral = (node) => {
218
+ if (node.type !== "TSTypeLiteral") return false;
219
+ const members = node.members;
220
+ return Array.isArray(members) && members.length === 0;
221
+ };
222
+ const typeReferenceName = (node) => {
223
+ if (node.type !== "TSTypeReference") return void 0;
224
+ const typeName = node.typeName;
225
+ if (!typeName || typeName.type !== "Identifier") return void 0;
226
+ return typeName.name;
227
+ };
228
+ const isKeyofOfType = (candidate, expectedReferenceName) => {
229
+ if (candidate.type !== "TSTypeOperator") return false;
230
+ if (candidate.operator !== "keyof") return false;
231
+ const operand = candidate.typeAnnotation;
232
+ if (!operand) return false;
233
+ return typeReferenceName(operand) === expectedReferenceName;
234
+ };
235
+ const isNeverKeyword = (node) => node.type === "TSNeverKeyword";
236
+ const isLiterallyEqualByJson = (left, right) => {
237
+ const stripPositions = (key, value) => {
238
+ if (key === "start" || key === "end") return void 0;
239
+ return value;
240
+ };
241
+ return JSON.stringify(left, stripPositions) === JSON.stringify(right, stripPositions);
242
+ };
243
+ const detectIntersectionWithEmpty = (node) => {
244
+ if (node.type !== "TSIntersectionType") return void 0;
245
+ const operands = node.types;
246
+ if (!Array.isArray(operands) || operands.length < 2) return void 0;
247
+ if (!operands.some(isEmptyTypeLiteral)) return void 0;
248
+ return {
249
+ kind: "intersection-with-empty-object",
250
+ reason: "intersection with `{}` is a no-op; the empty object type does not constrain anything",
251
+ suggestion: "drop the `& {}` term"
252
+ };
253
+ };
254
+ const detectSelfUnion = (node) => {
255
+ if (node.type !== "TSUnionType") return void 0;
256
+ const operands = node.types;
257
+ if (!Array.isArray(operands) || operands.length < 2) return void 0;
258
+ for (let leftIndex = 0; leftIndex < operands.length; leftIndex++) for (let rightIndex = leftIndex + 1; rightIndex < operands.length; rightIndex++) if (isLiterallyEqualByJson(operands[leftIndex], operands[rightIndex])) return {
259
+ kind: "self-union",
260
+ reason: "union contains the same member twice",
261
+ suggestion: "deduplicate the union members"
262
+ };
263
+ };
264
+ const detectSelfIntersection = (node) => {
265
+ if (node.type !== "TSIntersectionType") return void 0;
266
+ const operands = node.types;
267
+ if (!Array.isArray(operands) || operands.length < 2) return void 0;
268
+ for (let leftIndex = 0; leftIndex < operands.length; leftIndex++) for (let rightIndex = leftIndex + 1; rightIndex < operands.length; rightIndex++) if (isLiterallyEqualByJson(operands[leftIndex], operands[rightIndex])) return {
269
+ kind: "self-intersection",
270
+ reason: "intersection contains the same operand twice",
271
+ suggestion: "deduplicate the intersection operands"
272
+ };
273
+ };
274
+ const detectNestedUtility = (node, utilityName, kind) => {
275
+ if (node.type !== "TSTypeReference") return void 0;
276
+ if (typeReferenceName(node) !== utilityName) return void 0;
277
+ const typeArguments = node.typeArguments;
278
+ if (!typeArguments) return void 0;
279
+ const params = typeArguments.params;
280
+ if (!Array.isArray(params) || params.length === 0) return void 0;
281
+ const firstArg = params[0];
282
+ if (firstArg.type !== "TSTypeReference") return void 0;
283
+ if (typeReferenceName(firstArg) !== utilityName) return void 0;
284
+ return {
285
+ kind,
286
+ reason: `${utilityName}<${utilityName}<T>> collapses to ${utilityName}<T>`,
287
+ suggestion: `flatten the nested ${utilityName}<...>`
288
+ };
289
+ };
290
+ const detectPickAllKeys = (node) => {
291
+ if (node.type !== "TSTypeReference") return void 0;
292
+ if (typeReferenceName(node) !== "Pick") return void 0;
293
+ const typeArguments = node.typeArguments;
294
+ if (!typeArguments) return void 0;
295
+ const params = typeArguments.params;
296
+ if (!Array.isArray(params) || params.length !== 2) return void 0;
297
+ const targetType = params[0];
298
+ const keys = params[1];
299
+ const targetName = typeReferenceName(targetType);
300
+ if (!targetName) return void 0;
301
+ if (!isKeyofOfType(keys, targetName)) return void 0;
302
+ return {
303
+ kind: "pick-all-keys",
304
+ reason: `Pick<${targetName}, keyof ${targetName}> is equivalent to ${targetName} itself`,
305
+ suggestion: `replace with ${targetName}`
306
+ };
307
+ };
308
+ const detectOmitNoKeys = (node) => {
309
+ if (node.type !== "TSTypeReference") return void 0;
310
+ if (typeReferenceName(node) !== "Omit") return void 0;
311
+ const typeArguments = node.typeArguments;
312
+ if (!typeArguments) return void 0;
313
+ const params = typeArguments.params;
314
+ if (!Array.isArray(params) || params.length !== 2) return void 0;
315
+ const targetType = params[0];
316
+ const keys = params[1];
317
+ const targetName = typeReferenceName(targetType);
318
+ if (!targetName) return void 0;
319
+ if (!isNeverKeyword(keys)) return void 0;
320
+ return {
321
+ kind: "omit-no-keys",
322
+ reason: `Omit<${targetName}, never> is equivalent to ${targetName} itself`,
323
+ suggestion: `replace with ${targetName}`
324
+ };
325
+ };
326
+ const isZodInferDeclarationMergingExtension = (parentExpression) => {
327
+ if (!parentExpression || parentExpression.type !== "MemberExpression") return false;
328
+ const propertyNode = parentExpression.property;
329
+ if (!propertyNode || propertyNode.type !== "Identifier") return false;
330
+ return propertyNode.name === "infer";
331
+ };
332
+ const isRadixStylePropsAliasExtension = (parentExpression) => {
333
+ if (!parentExpression || parentExpression.type !== "MemberExpression") return false;
334
+ const propertyNode = parentExpression.property;
335
+ if (!propertyNode || propertyNode.type !== "Identifier") return false;
336
+ return propertyNode.name === "Props";
337
+ };
338
+ const detectEmptyInterfaceExtendsOne = (declarationNode) => {
339
+ if (declarationNode.type !== "TSInterfaceDeclaration") return void 0;
340
+ const body = declarationNode.body;
341
+ if (!body || !Array.isArray(body.body) || body.body.length !== 0) return void 0;
342
+ const extendsClauses = declarationNode.extends;
343
+ if (!Array.isArray(extendsClauses) || extendsClauses.length !== 1) return void 0;
344
+ const declarationName = declarationNode.id?.name;
345
+ const parentExpression = extendsClauses[0]?.expression;
346
+ if (isZodInferDeclarationMergingExtension(parentExpression)) return void 0;
347
+ if (isRadixStylePropsAliasExtension(parentExpression)) return void 0;
348
+ const parentName = parentExpression && parentExpression.type === "Identifier" ? parentExpression.name : void 0;
349
+ return {
350
+ kind: "empty-interface-extends-one",
351
+ reason: `interface ${declarationName ?? "<anon>"} extends ${parentName ?? "<base>"} with no new members`,
352
+ suggestion: `replace with \`type ${declarationName ?? "X"} = ${parentName ?? "Base"}\``
353
+ };
354
+ };
355
+ const detectRedundantTypePatternForTypeAnnotation = (typeAnnotation) => {
356
+ if (!isTypeNode(typeAnnotation)) return void 0;
357
+ return detectIntersectionWithEmpty(typeAnnotation) ?? detectSelfUnion(typeAnnotation) ?? detectSelfIntersection(typeAnnotation) ?? detectNestedUtility(typeAnnotation, "Partial", "nested-partial") ?? detectNestedUtility(typeAnnotation, "Readonly", "nested-readonly") ?? detectNestedUtility(typeAnnotation, "Required", "nested-required") ?? detectPickAllKeys(typeAnnotation) ?? detectOmitNoKeys(typeAnnotation);
358
+ };
359
+ const detectRedundantInterfaceDeclaration = (declarationNode) => {
360
+ if (!isTypeNode(declarationNode)) return void 0;
361
+ return detectEmptyInterfaceExtendsOne(declarationNode);
362
+ };
363
+
364
+ //#endregion
365
+ //#region src/utils/oxc-ast-node.ts
366
+ const isOxcAstNode = (value) => Boolean(value) && typeof value === "object" && typeof value.type === "string";
367
+ const getNodeStringField = (node, key) => {
368
+ const value = node[key];
369
+ return typeof value === "string" ? value : void 0;
370
+ };
371
+ const getIdentifierName = (node) => {
372
+ if (!isOxcAstNode(node)) return void 0;
373
+ if (node.type !== "Identifier") return void 0;
374
+ return getNodeStringField(node, "name");
375
+ };
376
+
377
+ //#endregion
378
+ //#region src/utils/detect-identity-wrapper.ts
379
+ const getCalleeText = (calleeNode) => {
380
+ if (calleeNode.type === "Identifier") return getIdentifierName(calleeNode);
381
+ if (calleeNode.type === "MemberExpression") {
382
+ if (calleeNode.computed) return void 0;
383
+ const objectNode = calleeNode.object;
384
+ const propertyNode = calleeNode.property;
385
+ if (!objectNode || !propertyNode) return void 0;
386
+ const objectText = getCalleeText(objectNode);
387
+ const propertyText = propertyNode.type === "Identifier" ? propertyNode.name : void 0;
388
+ if (!objectText || !propertyText) return void 0;
389
+ return `${objectText}.${propertyText}`;
390
+ }
391
+ };
392
+ const collectParameterNames = (parameters) => {
393
+ const names = [];
394
+ let hasRest = false;
395
+ let hasDefault = false;
396
+ let restName;
397
+ for (const parameter of parameters) {
398
+ if (!isOxcAstNode(parameter)) return {
399
+ names,
400
+ hasRest: true,
401
+ hasDefault,
402
+ restName
403
+ };
404
+ if (parameter.type === "RestElement") {
405
+ const restArgument = parameter.argument;
406
+ if (restArgument && restArgument.type === "Identifier") {
407
+ hasRest = true;
408
+ restName = restArgument.name;
409
+ continue;
410
+ }
411
+ return {
412
+ names,
413
+ hasRest: true,
414
+ hasDefault,
415
+ restName
416
+ };
417
+ }
418
+ if (parameter.type === "AssignmentPattern") {
419
+ hasDefault = true;
420
+ return {
421
+ names,
422
+ hasRest,
423
+ hasDefault,
424
+ restName
425
+ };
426
+ }
427
+ if (parameter.type === "Identifier") {
428
+ names.push(parameter.name);
429
+ continue;
430
+ }
431
+ return {
432
+ names: [],
433
+ hasRest,
434
+ hasDefault,
435
+ restName
436
+ };
437
+ }
438
+ return {
439
+ names,
440
+ hasRest,
441
+ hasDefault,
442
+ restName
443
+ };
444
+ };
445
+ const argumentsMatchParameters = (callArguments, parameterNames, restName) => {
446
+ if (restName !== void 0) {
447
+ if (callArguments.length !== 1) return false;
448
+ const onlyArgument = callArguments[0];
449
+ if (!isOxcAstNode(onlyArgument)) return false;
450
+ if (onlyArgument.type !== "SpreadElement") return false;
451
+ const spreadArgumentNode = onlyArgument.argument;
452
+ return Boolean(spreadArgumentNode && spreadArgumentNode.type === "Identifier" && spreadArgumentNode.name === restName);
453
+ }
454
+ if (callArguments.length !== parameterNames.length) return false;
455
+ for (let argumentIndex = 0; argumentIndex < callArguments.length; argumentIndex++) {
456
+ const argumentNode = callArguments[argumentIndex];
457
+ if (!isOxcAstNode(argumentNode)) return false;
458
+ if (argumentNode.type !== "Identifier") return false;
459
+ if (argumentNode.name !== parameterNames[argumentIndex]) return false;
460
+ }
461
+ return true;
462
+ };
463
+ const extractCallExpressionFromBody = (bodyNode) => {
464
+ if (bodyNode.type === "CallExpression") return bodyNode;
465
+ if (bodyNode.type === "BlockStatement") {
466
+ const blockBody = bodyNode.body;
467
+ if (!Array.isArray(blockBody) || blockBody.length !== 1) return void 0;
468
+ const onlyStatement = blockBody[0];
469
+ if (!isOxcAstNode(onlyStatement)) return void 0;
470
+ if (onlyStatement.type !== "ReturnStatement") return void 0;
471
+ const returnedExpression = onlyStatement.argument;
472
+ if (!returnedExpression) return void 0;
473
+ if (returnedExpression.type !== "CallExpression") return void 0;
474
+ return returnedExpression;
475
+ }
476
+ };
477
+ const detectIdentityWrapperFromInitializer = (initializerNode, wrapperName) => {
478
+ if (!isOxcAstNode(initializerNode)) return void 0;
479
+ if (initializerNode.type !== "ArrowFunctionExpression" && initializerNode.type !== "FunctionExpression") return;
480
+ if (initializerNode.async) return void 0;
481
+ if (initializerNode.generator) return void 0;
482
+ const { names: parameterNames, hasRest, hasDefault, restName } = collectParameterNames(initializerNode.params ?? []);
483
+ if (hasDefault) return void 0;
484
+ const bodyNode = initializerNode.body;
485
+ if (!bodyNode) return void 0;
486
+ const callExpression = extractCallExpressionFromBody(bodyNode);
487
+ if (!callExpression) return void 0;
488
+ const calleeNode = callExpression.callee;
489
+ if (!calleeNode) return void 0;
490
+ const calleeText = getCalleeText(calleeNode);
491
+ if (!calleeText) return void 0;
492
+ if (calleeText === wrapperName) return void 0;
493
+ if (!argumentsMatchParameters(callExpression.arguments ?? [], parameterNames, hasRest ? restName : void 0)) return;
494
+ return { wrappedExpression: calleeText };
495
+ };
496
+
497
+ //#endregion
498
+ //#region src/utils/normalize-type-hash.ts
499
+ const POSITION_KEYS = new Set([
500
+ "start",
501
+ "end",
502
+ "loc",
503
+ "range"
504
+ ]);
505
+ const NOISY_KEYS = new Set([
506
+ "decorators",
507
+ "leadingComments",
508
+ "trailingComments",
509
+ "innerComments",
510
+ "directive",
511
+ "optional",
512
+ "computed",
513
+ "static",
514
+ "accessibility",
515
+ "declare",
516
+ "readonly"
517
+ ]);
518
+ const NAME_KEYS_TO_STRIP = new Set(["id"]);
519
+ const sanitizeNode = (input) => {
520
+ if (input === null || input === void 0) return input;
521
+ if (Array.isArray(input)) return input.map((element) => sanitizeNode(element));
522
+ if (typeof input !== "object") return input;
523
+ const record = input;
524
+ const cleaned = {};
525
+ for (const key of Object.keys(record)) {
526
+ if (POSITION_KEYS.has(key)) continue;
527
+ if (NOISY_KEYS.has(key)) continue;
528
+ if (NAME_KEYS_TO_STRIP.has(key)) continue;
529
+ cleaned[key] = sanitizeNode(record[key]);
530
+ }
531
+ if (cleaned.type === "TSTypeLiteral" && Array.isArray(cleaned.members)) cleaned.members = sortMembersByKey(cleaned.members);
532
+ if (cleaned.type === "TSInterfaceBody" && Array.isArray(cleaned.body)) cleaned.body = sortMembersByKey(cleaned.body);
533
+ return cleaned;
534
+ };
535
+ const extractMemberKey = (member) => {
536
+ if (!member || typeof member !== "object") return "";
537
+ const record = member;
538
+ if (record.key) {
539
+ const candidate = record.key.name ?? record.key.value;
540
+ if (candidate === void 0 || candidate === null) return "";
541
+ return String(candidate);
542
+ }
543
+ return `__${record.type ?? ""}__`;
544
+ };
545
+ const sortMembersByKey = (members) => {
546
+ const tagged = members.map((member) => ({
547
+ key: extractMemberKey(member),
548
+ member
549
+ }));
550
+ tagged.sort((leftEntry, rightEntry) => {
551
+ if (leftEntry.key < rightEntry.key) return -1;
552
+ if (leftEntry.key > rightEntry.key) return 1;
553
+ return 0;
554
+ });
555
+ return tagged.map((entry) => entry.member);
556
+ };
557
+ const normalizeTypeAstHash = (typeAnnotation) => {
558
+ const sanitized = sanitizeNode(typeAnnotation);
559
+ return JSON.stringify(sanitized);
560
+ };
561
+
562
+ //#endregion
563
+ //#region src/utils/collect-inline-type-literals.ts
564
+ const isTypeLiteralNode = (node) => node.type === "TSTypeLiteral";
565
+ const buildPreview = (typeLiteralNode) => {
566
+ const members = typeLiteralNode.members ?? [];
567
+ const propertyKeys = [];
568
+ for (const memberCandidate of members) {
569
+ if (!isOxcAstNode(memberCandidate)) continue;
570
+ if (memberCandidate.type !== "TSPropertySignature") continue;
571
+ const keyNode = memberCandidate.key;
572
+ const keyName = keyNode?.name ?? keyNode?.value;
573
+ if (keyName) propertyKeys.push(String(keyName));
574
+ }
575
+ propertyKeys.sort();
576
+ const truncatedKeys = propertyKeys.slice(0, 4);
577
+ const suffix = propertyKeys.length > 4 ? `, +${propertyKeys.length - 4} more` : "";
578
+ return `{ ${truncatedKeys.join(", ")}${suffix} }`;
579
+ };
580
+ const countPropertySignatures = (typeLiteralNode) => {
581
+ const members = typeLiteralNode.members ?? [];
582
+ let signatureCount = 0;
583
+ for (const memberCandidate of members) {
584
+ if (!isOxcAstNode(memberCandidate)) continue;
585
+ if (memberCandidate.type === "TSPropertySignature") signatureCount++;
586
+ }
587
+ return signatureCount;
588
+ };
589
+ const captureIfTypeLiteral = (candidateNode, captures, context, nearestName) => {
590
+ if (!isOxcAstNode(candidateNode)) return;
591
+ if (!isTypeLiteralNode(candidateNode)) return;
592
+ const memberCount = countPropertySignatures(candidateNode);
593
+ if (memberCount < 3) return;
594
+ captures.push({
595
+ structuralHash: `inline:${normalizeTypeAstHash(candidateNode)}`,
596
+ memberCount,
597
+ preview: buildPreview(candidateNode),
598
+ context,
599
+ nearestName,
600
+ startOffset: candidateNode.start ?? 0
601
+ });
602
+ };
603
+ const GENERIC_WRAPPERS_TO_RECURSE = new Set([
604
+ "Array",
605
+ "ReadonlyArray",
606
+ "Promise",
607
+ "Set",
608
+ "ReadonlySet",
609
+ "Map",
610
+ "ReadonlyMap",
611
+ "Record",
612
+ "Partial",
613
+ "Required",
614
+ "Readonly",
615
+ "NonNullable",
616
+ "Awaited"
617
+ ]);
618
+ const inspectAnyTypeNode = (candidateNode, captures, context, nearestName, recursionDepth) => {
619
+ if (!isOxcAstNode(candidateNode)) return;
620
+ if (recursionDepth > 6) return;
621
+ if (isTypeLiteralNode(candidateNode)) {
622
+ captureIfTypeLiteral(candidateNode, captures, context, nearestName);
623
+ const members = candidateNode.members ?? [];
624
+ for (const memberCandidate of members) {
625
+ if (!isOxcAstNode(memberCandidate)) continue;
626
+ if (memberCandidate.type !== "TSPropertySignature") continue;
627
+ const memberKey = memberCandidate.key?.name;
628
+ const nested = memberCandidate.typeAnnotation;
629
+ inspectAnyTypeNode(nested, captures, "interface-property", memberKey ?? nearestName, recursionDepth + 1);
630
+ }
631
+ return;
632
+ }
633
+ if (candidateNode.type === "TSTypeAnnotation") {
634
+ inspectAnyTypeNode(candidateNode.typeAnnotation, captures, context, nearestName, recursionDepth + 1);
635
+ return;
636
+ }
637
+ if (candidateNode.type === "TSArrayType") {
638
+ inspectAnyTypeNode(candidateNode.elementType, captures, context, nearestName, recursionDepth + 1);
639
+ return;
640
+ }
641
+ if (candidateNode.type === "TSUnionType" || candidateNode.type === "TSIntersectionType") {
642
+ const operands = candidateNode.types ?? [];
643
+ for (const operand of operands) inspectAnyTypeNode(operand, captures, context, nearestName, recursionDepth + 1);
644
+ return;
645
+ }
646
+ if (candidateNode.type === "TSTupleType") {
647
+ const elements = candidateNode.elementTypes ?? [];
648
+ for (const element of elements) inspectAnyTypeNode(element, captures, context, nearestName, recursionDepth + 1);
649
+ return;
650
+ }
651
+ if (candidateNode.type === "TSTypeReference") {
652
+ const referenceTypeName = candidateNode.typeName?.name;
653
+ const typeArguments = candidateNode.typeArguments;
654
+ if (referenceTypeName && typeArguments?.params && GENERIC_WRAPPERS_TO_RECURSE.has(referenceTypeName)) for (const param of typeArguments.params) inspectAnyTypeNode(param, captures, context, nearestName, recursionDepth + 1);
655
+ }
656
+ };
657
+ const inspectTypeAnnotation = (typeAnnotationNode, captures, context, nearestName) => {
658
+ inspectAnyTypeNode(typeAnnotationNode, captures, context, nearestName, 0);
659
+ };
660
+ const visitFunctionParameters = (parameters, captures, functionName) => {
661
+ if (!parameters) return;
662
+ for (const parameter of parameters) {
663
+ if (!isOxcAstNode(parameter)) continue;
664
+ const parameterIdentifierName = getIdentifierName(parameter);
665
+ inspectTypeAnnotation(parameter.typeAnnotation, captures, "function-parameter", functionName ? `${functionName}(${parameterIdentifierName ?? "?"})` : parameterIdentifierName);
666
+ }
667
+ };
668
+ const visitFunctionLike = (functionNode, captures, functionName) => {
669
+ const parameters = functionNode.params;
670
+ visitFunctionParameters(parameters, captures, functionName);
671
+ const returnTypeNode = functionNode.returnType;
672
+ if (returnTypeNode) inspectTypeAnnotation(returnTypeNode, captures, "function-return", functionName);
673
+ const bodyNode = functionNode.body;
674
+ if (bodyNode) walkBodyForInlineTypes(bodyNode, captures, functionName);
675
+ };
676
+ const visitVariableDeclaration = (declarationNode, captures, enclosingName) => {
677
+ const declarators = declarationNode.declarations ?? [];
678
+ for (const declarator of declarators) {
679
+ if (!isOxcAstNode(declarator)) continue;
680
+ const declarationName = getIdentifierName(declarator.id);
681
+ inspectTypeAnnotation(declarator.typeAnnotation ?? (declarator.id && isOxcAstNode(declarator.id) ? declarator.id.typeAnnotation : void 0), captures, "variable-annotation", declarationName);
682
+ const initializerNode = declarator.init;
683
+ if (isOxcAstNode(initializerNode)) if (initializerNode.type === "ArrowFunctionExpression" || initializerNode.type === "FunctionExpression") visitFunctionLike(initializerNode, captures, declarationName ?? enclosingName);
684
+ else walkExpressionForInlineTypes(initializerNode, captures, declarationName ?? enclosingName);
685
+ }
686
+ };
687
+ const walkBodyForInlineTypes = (bodyNode, captures, enclosingName, recursionDepth = 0) => {
688
+ if (recursionDepth > 200) return;
689
+ if (!isOxcAstNode(bodyNode)) return;
690
+ const statements = bodyNode.body ?? [];
691
+ if (!Array.isArray(statements)) return;
692
+ for (const statement of statements) {
693
+ if (!isOxcAstNode(statement)) continue;
694
+ if (statement.type === "VariableDeclaration") visitVariableDeclaration(statement, captures, enclosingName);
695
+ else if (statement.type === "FunctionDeclaration") visitFunctionLike(statement, captures, getIdentifierName(statement.id) ?? enclosingName);
696
+ else if (statement.type === "TSTypeAliasDeclaration") {
697
+ const typeAliasName = getIdentifierName(statement.id);
698
+ captureIfTypeLiteral(statement.typeAnnotation, captures, "local-type-alias", typeAliasName);
699
+ } else if (statement.type === "ReturnStatement") walkExpressionForInlineTypes(statement.argument, captures, enclosingName, recursionDepth + 1);
700
+ else if (statement.type === "BlockStatement") walkBodyForInlineTypes(statement, captures, enclosingName, recursionDepth + 1);
701
+ else if (statement.type === "ExpressionStatement") walkExpressionForInlineTypes(statement.expression, captures, enclosingName, recursionDepth + 1);
702
+ }
703
+ };
704
+ const walkExpressionForInlineTypes = (expressionNode, captures, enclosingName, recursionDepth = 0) => {
705
+ if (recursionDepth > 200) return;
706
+ if (!isOxcAstNode(expressionNode)) return;
707
+ if (expressionNode.type === "ArrowFunctionExpression" || expressionNode.type === "FunctionExpression") {
708
+ visitFunctionLike(expressionNode, captures, enclosingName);
709
+ return;
710
+ }
711
+ for (const value of Object.values(expressionNode)) if (Array.isArray(value)) for (const element of value) walkExpressionForInlineTypes(element, captures, enclosingName, recursionDepth + 1);
712
+ else if (isOxcAstNode(value)) walkExpressionForInlineTypes(value, captures, enclosingName, recursionDepth + 1);
713
+ };
714
+ const visitTopLevelStatement = (statementNode, captures) => {
715
+ if (!isOxcAstNode(statementNode)) return;
716
+ const innerNode = statementNode.type === "ExportNamedDeclaration" || statementNode.type === "ExportDefaultDeclaration" ? statementNode.declaration ?? statementNode : statementNode;
717
+ const targetNode = isOxcAstNode(innerNode) ? innerNode : statementNode;
718
+ if (targetNode.type === "FunctionDeclaration") {
719
+ visitFunctionLike(targetNode, captures, getIdentifierName(targetNode.id));
720
+ return;
721
+ }
722
+ if (targetNode.type === "VariableDeclaration") {
723
+ visitVariableDeclaration(targetNode, captures, void 0);
724
+ return;
725
+ }
726
+ if (targetNode.type === "ClassDeclaration") {
727
+ const className = getIdentifierName(targetNode.id);
728
+ const members = targetNode.body?.body ?? [];
729
+ for (const memberCandidate of members) {
730
+ if (!isOxcAstNode(memberCandidate)) continue;
731
+ const memberKeyName = getIdentifierName(memberCandidate.key);
732
+ const qualifiedName = className && memberKeyName ? `${className}.${memberKeyName}` : memberKeyName;
733
+ if (memberCandidate.type === "PropertyDefinition") {
734
+ inspectTypeAnnotation(memberCandidate.typeAnnotation, captures, "class-property", qualifiedName);
735
+ continue;
736
+ }
737
+ if (memberCandidate.type === "MethodDefinition" || memberCandidate.type === "TSAbstractMethodDefinition") {
738
+ const methodValue = memberCandidate.value;
739
+ if (isOxcAstNode(methodValue)) visitFunctionLike(methodValue, captures, qualifiedName);
740
+ }
741
+ }
742
+ return;
743
+ }
744
+ if (targetNode.type === "TSInterfaceDeclaration") {
745
+ const interfaceName = getIdentifierName(targetNode.id);
746
+ const interfaceMembers = targetNode.body?.body ?? [];
747
+ for (const memberCandidate of interfaceMembers) {
748
+ if (!isOxcAstNode(memberCandidate)) continue;
749
+ if (memberCandidate.type !== "TSPropertySignature") continue;
750
+ const memberKeyName = getIdentifierName(memberCandidate.key);
751
+ const qualifiedName = interfaceName && memberKeyName ? `${interfaceName}.${memberKeyName}` : memberKeyName;
752
+ inspectTypeAnnotation(memberCandidate.typeAnnotation, captures, "interface-property", qualifiedName);
753
+ }
754
+ }
755
+ };
756
+ const collectInlineTypeLiterals = (programBody) => {
757
+ const captures = [];
758
+ for (const statement of programBody) visitTopLevelStatement(statement, captures);
759
+ return captures;
760
+ };
761
+
762
+ //#endregion
763
+ //#region src/utils/detect-simplifiable-function.ts
764
+ const containsAwaitExpression = (node, recursionDepth = 0) => {
765
+ if (recursionDepth > 30) return false;
766
+ if (!isOxcAstNode(node)) return false;
767
+ if (node.type === "AwaitExpression") return true;
768
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") return false;
769
+ for (const value of Object.values(node)) if (Array.isArray(value)) {
770
+ for (const element of value) if (containsAwaitExpression(element, recursionDepth + 1)) return true;
771
+ } else if (isOxcAstNode(value)) {
772
+ if (containsAwaitExpression(value, recursionDepth + 1)) return true;
773
+ }
774
+ return false;
775
+ };
776
+ const containsCallOrPromiseSurface = (node, recursionDepth = 0) => {
777
+ if (recursionDepth > 30) return false;
778
+ if (!isOxcAstNode(node)) return false;
779
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") return false;
780
+ if (node.type === "CallExpression" || node.type === "NewExpression" || node.type === "TaggedTemplateExpression" || node.type === "ThrowStatement" || node.type === "YieldExpression") return true;
781
+ if (node.type === "MemberExpression") {
782
+ const objectNode = node.object;
783
+ if (objectNode && isOxcAstNode(objectNode) && objectNode.type === "Identifier") {
784
+ if (objectNode.name === "Promise") return true;
785
+ }
786
+ }
787
+ for (const value of Object.values(node)) if (Array.isArray(value)) {
788
+ for (const element of value) if (containsCallOrPromiseSurface(element, recursionDepth + 1)) return true;
789
+ } else if (isOxcAstNode(value)) {
790
+ if (containsCallOrPromiseSurface(value, recursionDepth + 1)) return true;
791
+ }
792
+ return false;
793
+ };
794
+ const unwrapParenthesizedExpression = (node) => {
795
+ let current = node;
796
+ while (current.type === "ParenthesizedExpression") {
797
+ const inner = current.expression;
798
+ if (!inner || !isOxcAstNode(inner)) return current;
799
+ current = inner;
800
+ }
801
+ return current;
802
+ };
803
+ const isSimpleReturnArgument = (argumentNode) => {
804
+ if (!isOxcAstNode(argumentNode)) return false;
805
+ const unwrapped = unwrapParenthesizedExpression(argumentNode);
806
+ if (unwrapped.type === "BlockStatement") return false;
807
+ if (unwrapped.type === "ObjectExpression") return false;
808
+ if (unwrapped.type === "JSXElement") return false;
809
+ if (unwrapped.type === "JSXFragment") return false;
810
+ return true;
811
+ };
812
+ const detectBlockArrowSingleReturn = (functionNode) => {
813
+ if (functionNode.type !== "ArrowFunctionExpression") return void 0;
814
+ if (functionNode.async) return void 0;
815
+ const bodyNode = functionNode.body;
816
+ if (!bodyNode || bodyNode.type !== "BlockStatement") return void 0;
817
+ const statements = bodyNode.body ?? [];
818
+ if (statements.length !== 1) return void 0;
819
+ const onlyStatement = statements[0];
820
+ if (!isOxcAstNode(onlyStatement)) return void 0;
821
+ if (onlyStatement.type !== "ReturnStatement") return void 0;
822
+ const returnArgument = onlyStatement.argument;
823
+ if (!returnArgument) return void 0;
824
+ if (!isSimpleReturnArgument(returnArgument)) return void 0;
825
+ return {
826
+ kind: "block-arrow-single-return",
827
+ startOffset: functionNode.start ?? 0,
828
+ reason: "arrow body is a single `return` statement; the block can be replaced by the expression directly",
829
+ suggestion: "rewrite as `() => expression` without `{}`"
830
+ };
831
+ };
832
+ const detectRedundantAwaitReturn = (functionNode) => {
833
+ const bodyNode = functionNode.body;
834
+ if (!bodyNode || bodyNode.type !== "BlockStatement") return void 0;
835
+ const statements = bodyNode.body ?? [];
836
+ if (statements.length < 2) return void 0;
837
+ const penultimate = statements[statements.length - 2];
838
+ const last = statements[statements.length - 1];
839
+ if (!isOxcAstNode(penultimate) || !isOxcAstNode(last)) return void 0;
840
+ if (penultimate.type !== "VariableDeclaration") return void 0;
841
+ if (last.type !== "ReturnStatement") return void 0;
842
+ const declarators = penultimate.declarations ?? [];
843
+ if (declarators.length !== 1) return void 0;
844
+ const declarator = declarators[0];
845
+ if (!isOxcAstNode(declarator)) return void 0;
846
+ const declaredIdentifier = declarator.id;
847
+ const initializer = declarator.init;
848
+ if (!declaredIdentifier?.name) return void 0;
849
+ if (!isOxcAstNode(initializer)) return void 0;
850
+ if (initializer.type !== "AwaitExpression") return void 0;
851
+ const returnedArgument = last.argument;
852
+ if (!isOxcAstNode(returnedArgument)) return void 0;
853
+ if (returnedArgument.type !== "Identifier") return void 0;
854
+ if (returnedArgument.name !== declaredIdentifier.name) return void 0;
855
+ return {
856
+ kind: "redundant-await-return",
857
+ startOffset: penultimate.start ?? 0,
858
+ reason: `\`const ${declaredIdentifier.name} = await …; return ${declaredIdentifier.name};\` can be \`return …;\` (the await is preserved by the implicit promise chain)`,
859
+ suggestion: `replace the await/assign/return sequence with a single \`return await …\` or \`return …\` if no try/catch wraps it`
860
+ };
861
+ };
862
+ const isAsyncFunction = (functionNode) => Boolean(functionNode.async);
863
+ const containsPromiseTypeReference = (node, recursionDepth = 0) => {
864
+ if (recursionDepth > 30) return false;
865
+ if (!isOxcAstNode(node)) return false;
866
+ if (node.type === "TSTypeReference") {
867
+ const typeName = node.typeName;
868
+ if (typeName?.name === "Promise") return true;
869
+ if (typeName?.right?.name === "Promise") return true;
870
+ }
871
+ for (const value of Object.values(node)) if (Array.isArray(value)) {
872
+ for (const element of value) if (containsPromiseTypeReference(element, recursionDepth + 1)) return true;
873
+ } else if (isOxcAstNode(value)) {
874
+ if (containsPromiseTypeReference(value, recursionDepth + 1)) return true;
875
+ }
876
+ return false;
877
+ };
878
+ const hasExplicitPromiseReturnType = (functionNode) => {
879
+ const returnType = functionNode.returnType;
880
+ if (!returnType || !isOxcAstNode(returnType)) return false;
881
+ const annotation = returnType.typeAnnotation;
882
+ if (!annotation || !isOxcAstNode(annotation)) return false;
883
+ return containsPromiseTypeReference(annotation);
884
+ };
885
+ const detectUselessAsync = (functionNode, context) => {
886
+ if (!isAsyncFunction(functionNode)) return void 0;
887
+ if (functionNode.type === "ClassDeclaration" || functionNode.type === "MethodDefinition") return;
888
+ if (context.isMethodContext) return void 0;
889
+ if (context.isInlineCallback) return void 0;
890
+ if (hasExplicitPromiseReturnType(functionNode)) return void 0;
891
+ const bodyNode = functionNode.body;
892
+ if (!isOxcAstNode(bodyNode)) return void 0;
893
+ if (containsAwaitExpression(bodyNode)) return void 0;
894
+ if (containsCallOrPromiseSurface(bodyNode)) return void 0;
895
+ return {
896
+ kind: "useless-async-no-await",
897
+ startOffset: functionNode.start ?? 0,
898
+ reason: "async function body contains no `await`, no function calls, and no Promise surface — the implicit Promise wrap is purely decorative",
899
+ suggestion: "drop `async` (caller's existing `await` keeps the type identical) or add an explicit return type"
900
+ };
901
+ };
902
+ const detectSimplifiableFunctionPatterns = (functionNode, context = {}) => {
903
+ if (!isOxcAstNode(functionNode)) return [];
904
+ const findings = [];
905
+ const blockArrow = detectBlockArrowSingleReturn(functionNode);
906
+ if (blockArrow) findings.push(blockArrow);
907
+ const awaitReturn = detectRedundantAwaitReturn(functionNode);
908
+ if (awaitReturn) findings.push(awaitReturn);
909
+ const uselessAsync = detectUselessAsync(functionNode, context);
910
+ if (uselessAsync) findings.push(uselessAsync);
911
+ return findings;
912
+ };
913
+
914
+ //#endregion
915
+ //#region src/utils/collect-simplifiable-functions.ts
916
+ const looksLikeFunction = (node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
917
+ const inferFunctionName = (functionNode, parentContext) => {
918
+ const declaredId = functionNode.id;
919
+ if (declaredId?.name) return declaredId.name;
920
+ return parentContext;
921
+ };
922
+ const visitFunctionAndDescend = (functionNode, captures, contextName, recursionDepth, isMethodContext, isInlineCallback) => {
923
+ const functionName = inferFunctionName(functionNode, contextName);
924
+ const detections = detectSimplifiableFunctionPatterns(functionNode, {
925
+ isMethodContext,
926
+ isInlineCallback
927
+ });
928
+ for (const detection of detections) captures.push({
929
+ kind: detection.kind,
930
+ functionName,
931
+ startOffset: detection.startOffset,
932
+ reason: detection.reason,
933
+ suggestion: detection.suggestion
934
+ });
935
+ const bodyNode = functionNode.body;
936
+ if (isOxcAstNode(bodyNode)) walkForFunctions(bodyNode, captures, functionName, recursionDepth + 1);
937
+ const parameters = functionNode.params ?? [];
938
+ for (const parameter of parameters) if (isOxcAstNode(parameter)) walkForFunctions(parameter, captures, functionName, recursionDepth + 1);
939
+ };
940
+ const isObjectMethodShorthand = (node) => (node.type === "Property" || node.type === "ObjectProperty") && node.method === true;
941
+ const isObjectPropertyAssignment = (node) => (node.type === "Property" || node.type === "ObjectProperty") && node.method !== true;
942
+ const isCallOrNewExpression = (node) => node.type === "CallExpression" || node.type === "NewExpression";
943
+ const walkForFunctions = (node, captures, contextName, recursionDepth = 0) => {
944
+ if (recursionDepth > 200) return;
945
+ if (looksLikeFunction(node)) {
946
+ visitFunctionAndDescend(node, captures, contextName, recursionDepth, false, false);
947
+ return;
948
+ }
949
+ let nextContext = contextName;
950
+ if (node.type === "VariableDeclarator") {
951
+ const declaredName = getIdentifierName(node.id);
952
+ if (declaredName) nextContext = declaredName;
953
+ }
954
+ if (node.type === "MethodDefinition" || node.type === "PropertyDefinition") {
955
+ const propertyKeyName = getIdentifierName(node.key);
956
+ if (propertyKeyName) nextContext = propertyKeyName;
957
+ }
958
+ if (node.type === "ClassDeclaration") {
959
+ const className = getIdentifierName(node.id);
960
+ if (className) nextContext = className;
961
+ }
962
+ if (node.type === "MethodDefinition" || isObjectMethodShorthand(node)) {
963
+ const methodValue = node.value;
964
+ if (methodValue && isOxcAstNode(methodValue) && looksLikeFunction(methodValue)) {
965
+ visitFunctionAndDescend(methodValue, captures, getIdentifierName(node.key) ?? nextContext, recursionDepth + 1, true, false);
966
+ const keyNode = node.key;
967
+ if (keyNode && isOxcAstNode(keyNode) && node.computed) walkForFunctions(keyNode, captures, nextContext, recursionDepth + 1);
968
+ return;
969
+ }
970
+ }
971
+ if (isObjectPropertyAssignment(node)) {
972
+ const propertyValue = node.value;
973
+ if (propertyValue && isOxcAstNode(propertyValue) && looksLikeFunction(propertyValue)) {
974
+ visitFunctionAndDescend(propertyValue, captures, getIdentifierName(node.key) ?? nextContext, recursionDepth + 1, false, true);
975
+ const keyNode = node.key;
976
+ if (keyNode && isOxcAstNode(keyNode) && node.computed) walkForFunctions(keyNode, captures, nextContext, recursionDepth + 1);
977
+ return;
978
+ }
979
+ }
980
+ if (isCallOrNewExpression(node)) {
981
+ const callee = node.callee;
982
+ if (callee && isOxcAstNode(callee)) walkForFunctions(callee, captures, nextContext, recursionDepth + 1);
983
+ const callArguments = node.arguments ?? [];
984
+ for (const argument of callArguments) {
985
+ if (!isOxcAstNode(argument)) continue;
986
+ if (looksLikeFunction(argument)) visitFunctionAndDescend(argument, captures, nextContext, recursionDepth + 1, false, true);
987
+ else walkForFunctions(argument, captures, nextContext, recursionDepth + 1);
988
+ }
989
+ return;
990
+ }
991
+ for (const value of Object.values(node)) if (Array.isArray(value)) {
992
+ for (const element of value) if (isOxcAstNode(element)) walkForFunctions(element, captures, nextContext, recursionDepth + 1);
993
+ } else if (isOxcAstNode(value)) walkForFunctions(value, captures, nextContext, recursionDepth + 1);
994
+ };
995
+ const collectSimplifiableFunctions = (programBody) => {
996
+ const captures = [];
997
+ for (const statement of programBody) if (isOxcAstNode(statement)) walkForFunctions(statement, captures, void 0, 0);
998
+ return captures;
999
+ };
1000
+
1001
+ //#endregion
1002
+ //#region src/utils/collect-simplifiable-expressions.ts
1003
+ const memberAccessText = (node, depth = 0) => {
1004
+ if (depth > 6) return void 0;
1005
+ if (node.type === "Identifier") return node.name;
1006
+ if (node.type === "ThisExpression") return "this";
1007
+ if (node.type === "MemberExpression") {
1008
+ if (node.computed) return void 0;
1009
+ const objectNode = node.object;
1010
+ const propertyNode = node.property;
1011
+ if (!objectNode || !propertyNode) return void 0;
1012
+ const objectText = memberAccessText(objectNode, depth + 1);
1013
+ const propertyText = propertyNode.type === "Identifier" ? propertyNode.name : void 0;
1014
+ if (!objectText || !propertyText) return void 0;
1015
+ return `${objectText}.${propertyText}`;
1016
+ }
1017
+ };
1018
+ const isBooleanLiteral = (node, expected) => {
1019
+ if (node.type !== "Literal") return false;
1020
+ return node.value === expected;
1021
+ };
1022
+ const detectSelfFallbackTernary = (conditionalNode) => {
1023
+ if (conditionalNode.type !== "ConditionalExpression") return void 0;
1024
+ const testNode = conditionalNode.test;
1025
+ const consequentNode = conditionalNode.consequent;
1026
+ if (!testNode || !consequentNode) return void 0;
1027
+ const testText = memberAccessText(testNode);
1028
+ const consequentText = memberAccessText(consequentNode);
1029
+ if (!testText || !consequentText) return void 0;
1030
+ if (testText !== consequentText) return void 0;
1031
+ return {
1032
+ kind: "self-fallback-ternary",
1033
+ snippet: `${testText} ? ${consequentText} : ...`,
1034
+ startOffset: conditionalNode.start ?? 0,
1035
+ reason: `\`${testText} ? ${testText} : x\` is a self-fallback ternary`,
1036
+ suggestion: `use \`${testText} ?? x\` (nullish-only) or \`${testText} || x\` (falsy fallback) depending on intent`
1037
+ };
1038
+ };
1039
+ const detectTernaryReturnsBoolean = (conditionalNode) => {
1040
+ if (conditionalNode.type !== "ConditionalExpression") return void 0;
1041
+ const consequentNode = conditionalNode.consequent;
1042
+ const alternateNode = conditionalNode.alternate;
1043
+ if (!consequentNode || !alternateNode) return void 0;
1044
+ const isTrueFalse = isBooleanLiteral(consequentNode, true) && isBooleanLiteral(alternateNode, false);
1045
+ const isFalseTrue = isBooleanLiteral(consequentNode, false) && isBooleanLiteral(alternateNode, true);
1046
+ if (!isTrueFalse && !isFalseTrue) return void 0;
1047
+ return {
1048
+ kind: "ternary-returns-boolean",
1049
+ snippet: isTrueFalse ? "cond ? true : false" : "cond ? false : true",
1050
+ startOffset: conditionalNode.start ?? 0,
1051
+ reason: isTrueFalse ? "`cond ? true : false` collapses to `Boolean(cond)`" : "`cond ? false : true` collapses to `!cond`",
1052
+ suggestion: isTrueFalse ? "replace with `Boolean(cond)` or just `cond` when types match" : "replace with `!cond`"
1053
+ };
1054
+ };
1055
+ const isNullLiteral = (node) => node.type === "Literal" && node.value === null;
1056
+ const isUndefinedIdentifier = (node) => node.type === "Identifier" && node.name === "undefined";
1057
+ const detectNullishCoalescingWithNullish = (logicalNode) => {
1058
+ if (logicalNode.type !== "LogicalExpression") return void 0;
1059
+ if (logicalNode.operator !== "??") return void 0;
1060
+ const rightNode = logicalNode.right;
1061
+ if (!rightNode) return void 0;
1062
+ if (!(isNullLiteral(rightNode) || isUndefinedIdentifier(rightNode))) return void 0;
1063
+ const leftNode = logicalNode.left;
1064
+ const leftText = leftNode ? memberAccessText(leftNode) ?? "expr" : "expr";
1065
+ const rightLabel = isNullLiteral(rightNode) ? "null" : "undefined";
1066
+ return {
1067
+ kind: "nullish-coalescing-with-nullish",
1068
+ snippet: `${leftText} ?? ${rightLabel}`,
1069
+ startOffset: logicalNode.start ?? 0,
1070
+ reason: `\`x ?? ${rightLabel}\` looks like a no-op — but may be intentional when a caller's signature requires \`${rightLabel}\` (PropTypes, form-control onChange, etc.)`,
1071
+ suggestion: `if \`x\` is already \`T | ${rightLabel}\`, drop the \`?? ${rightLabel}\`; otherwise keep — the coercion changes the resolved type`
1072
+ };
1073
+ };
1074
+ const detectRedundantNullAndUndefinedCheck = (logicalNode) => {
1075
+ if (logicalNode.type !== "LogicalExpression") return void 0;
1076
+ if (logicalNode.operator !== "&&") return void 0;
1077
+ const leftNode = logicalNode.left;
1078
+ const rightNode = logicalNode.right;
1079
+ if (!leftNode || !rightNode) return void 0;
1080
+ if (leftNode.type !== "BinaryExpression" || rightNode.type !== "BinaryExpression") return void 0;
1081
+ const leftOp = leftNode.operator;
1082
+ const rightOp = rightNode.operator;
1083
+ if (leftOp !== "!==" || rightOp !== "!==") return void 0;
1084
+ const leftLeft = leftNode.left;
1085
+ const leftRight = leftNode.right;
1086
+ const rightLeft = rightNode.left;
1087
+ const rightRight = rightNode.right;
1088
+ if (!leftLeft || !leftRight || !rightLeft || !rightRight) return void 0;
1089
+ const leftLeftText = memberAccessText(leftLeft);
1090
+ const rightLeftText = memberAccessText(rightLeft);
1091
+ if (!leftLeftText || leftLeftText !== rightLeftText) return void 0;
1092
+ const leftRhsIsNull = isNullLiteral(leftRight);
1093
+ const leftRhsIsUndefined = isUndefinedIdentifier(leftRight);
1094
+ const rightRhsIsNull = isNullLiteral(rightRight);
1095
+ const rightRhsIsUndefined = isUndefinedIdentifier(rightRight);
1096
+ if (!(leftRhsIsNull && rightRhsIsUndefined || leftRhsIsUndefined && rightRhsIsNull)) return void 0;
1097
+ return {
1098
+ kind: "redundant-null-and-undefined-check",
1099
+ snippet: `${leftLeftText} !== null && ${leftLeftText} !== undefined`,
1100
+ startOffset: logicalNode.start ?? 0,
1101
+ reason: `\`x !== null && x !== undefined\` is equivalent to \`x != null\` (loose comparison checks both)`,
1102
+ suggestion: `replace with \`${leftLeftText} != null\``
1103
+ };
1104
+ };
1105
+ const detectDoubleBangBoolean = (unaryNode) => {
1106
+ if (unaryNode.type !== "UnaryExpression") return void 0;
1107
+ if (unaryNode.operator !== "!") return void 0;
1108
+ const inner = unaryNode.argument;
1109
+ if (!inner || inner.type !== "UnaryExpression") return void 0;
1110
+ if (inner.operator !== "!") return void 0;
1111
+ const coerced = inner.argument;
1112
+ if (!coerced) return void 0;
1113
+ const coercedText = memberAccessText(coerced) ?? "expr";
1114
+ return {
1115
+ kind: "double-bang-boolean",
1116
+ snippet: `!!${coercedText}`,
1117
+ startOffset: unaryNode.start ?? 0,
1118
+ reason: "`!!x` is a double-negation boolean coercion",
1119
+ suggestion: `replace with \`Boolean(${coercedText})\``
1120
+ };
1121
+ };
1122
+ const visit = (node, captures, depth) => {
1123
+ if (depth > 100) return;
1124
+ const conditionalCapture = detectSelfFallbackTernary(node) ?? detectTernaryReturnsBoolean(node);
1125
+ if (conditionalCapture) captures.push(conditionalCapture);
1126
+ const doubleBangCapture = detectDoubleBangBoolean(node);
1127
+ if (doubleBangCapture) captures.push(doubleBangCapture);
1128
+ const logicalCapture = detectNullishCoalescingWithNullish(node) ?? detectRedundantNullAndUndefinedCheck(node);
1129
+ if (logicalCapture) captures.push(logicalCapture);
1130
+ for (const value of Object.values(node)) if (Array.isArray(value)) {
1131
+ for (const element of value) if (isOxcAstNode(element)) visit(element, captures, depth + 1);
1132
+ } else if (isOxcAstNode(value)) visit(value, captures, depth + 1);
1133
+ };
1134
+ const collectSimplifiableExpressions = (programBody) => {
1135
+ const captures = [];
1136
+ for (const statement of programBody) if (isOxcAstNode(statement)) visit(statement, captures, 0);
1137
+ return captures;
1138
+ };
1139
+
1140
+ //#endregion
1141
+ //#region src/utils/collect-duplicate-constants.ts
1142
+ const FRAMEWORK_RESERVED_CONSTANT_NAMES = new Set([
1143
+ "dynamic",
1144
+ "dynamicParams",
1145
+ "revalidate",
1146
+ "runtime",
1147
+ "fetchCache",
1148
+ "preferredRegion",
1149
+ "maxDuration",
1150
+ "metadata",
1151
+ "viewport",
1152
+ "generateStaticParams",
1153
+ "generateMetadata",
1154
+ "config",
1155
+ "loader",
1156
+ "action",
1157
+ "links",
1158
+ "meta",
1159
+ "headers",
1160
+ "handle",
1161
+ "shouldRevalidate",
1162
+ "ErrorBoundary",
1163
+ "HydrateFallback",
1164
+ "Layout"
1165
+ ]);
1166
+ const isLiteralCandidate = (node) => {
1167
+ if (node.type === "Literal") {
1168
+ const value = node.value;
1169
+ if (typeof value === "string") {
1170
+ if (value.length < 8) return false;
1171
+ return true;
1172
+ }
1173
+ if (typeof value === "number") {
1174
+ if (!Number.isFinite(value)) return false;
1175
+ if (Math.abs(value) < 1e3) return false;
1176
+ return true;
1177
+ }
1178
+ return false;
1179
+ }
1180
+ if (node.type === "TemplateLiteral") {
1181
+ const expressions = node.expressions;
1182
+ if (Array.isArray(expressions) && expressions.length > 0) return false;
1183
+ const quasis = node.quasis;
1184
+ if (!Array.isArray(quasis) || quasis.length === 0) return false;
1185
+ return (quasis[0].value?.cooked ?? "").length >= 8;
1186
+ }
1187
+ if (node.type === "ArrayExpression") {
1188
+ const elements = node.elements ?? [];
1189
+ if (elements.length === 0) return false;
1190
+ for (const element of elements) {
1191
+ if (!isOxcAstNode(element)) return false;
1192
+ if (element.type !== "Literal") return false;
1193
+ }
1194
+ return true;
1195
+ }
1196
+ return false;
1197
+ };
1198
+ const hashLiteralNode = (node) => {
1199
+ if (node.type === "Literal") return `lit:${typeof node.value}:${JSON.stringify(node.value)}`;
1200
+ if (node.type === "TemplateLiteral") {
1201
+ const quasis = node.quasis ?? [];
1202
+ return `tpl:${JSON.stringify(quasis[0]?.value?.cooked ?? "")}`;
1203
+ }
1204
+ if (node.type === "ArrayExpression") return `arr:[${(node.elements ?? []).map((element) => {
1205
+ if (!isOxcAstNode(element)) return "?";
1206
+ if (element.type !== "Literal") return "?";
1207
+ return JSON.stringify(element.value);
1208
+ }).join(",")}]`;
1209
+ return "?";
1210
+ };
1211
+ const previewLiteralNode = (node) => {
1212
+ if (node.type === "Literal") {
1213
+ const value = node.value;
1214
+ if (typeof value === "string") return `"${value.length > 60 ? value.slice(0, 57) + "..." : value}"`;
1215
+ return String(value);
1216
+ }
1217
+ if (node.type === "TemplateLiteral") {
1218
+ const cooked = (node.quasis ?? [])[0]?.value?.cooked ?? "";
1219
+ return `\`${cooked.length > 60 ? cooked.slice(0, 57) + "..." : cooked}\``;
1220
+ }
1221
+ if (node.type === "ArrayExpression") {
1222
+ const elements = node.elements ?? [];
1223
+ return `[${elements.slice(0, 3).map((element) => isOxcAstNode(element) && element.type === "Literal" ? JSON.stringify(element.value) : "?").join(", ")}${elements.length > 3 ? `, +${elements.length - 3} more` : ""}]`;
1224
+ }
1225
+ return "<literal>";
1226
+ };
1227
+ const visitForConstants = (statementNode, candidates) => {
1228
+ if (!isOxcAstNode(statementNode)) return;
1229
+ const inner = (statementNode.type === "ExportNamedDeclaration" || statementNode.type === "ExportDefaultDeclaration") && statementNode.declaration ? statementNode.declaration : statementNode;
1230
+ if (!isOxcAstNode(inner)) return;
1231
+ if (inner.type !== "VariableDeclaration") return;
1232
+ if (inner.kind !== "const") return;
1233
+ const declarators = inner.declarations ?? [];
1234
+ for (const declarator of declarators) {
1235
+ if (!isOxcAstNode(declarator)) continue;
1236
+ const idNode = declarator.id;
1237
+ const initializerNode = declarator.init;
1238
+ if (!idNode || !initializerNode) continue;
1239
+ if (idNode.type !== "Identifier") continue;
1240
+ const constantName = idNode.name;
1241
+ if (!constantName) continue;
1242
+ if (FRAMEWORK_RESERVED_CONSTANT_NAMES.has(constantName)) continue;
1243
+ if (!isLiteralCandidate(initializerNode)) continue;
1244
+ candidates.push({
1245
+ constantName,
1246
+ literalHash: hashLiteralNode(initializerNode),
1247
+ literalPreview: previewLiteralNode(initializerNode),
1248
+ startOffset: declarator.start ?? inner.start ?? 0
1249
+ });
1250
+ }
1251
+ };
1252
+ const collectDuplicateConstantCandidates = (programBody) => {
1253
+ const candidates = [];
1254
+ for (const statement of programBody) visitForConstants(statement, candidates);
1255
+ return candidates;
1256
+ };
1257
+
1258
+ //#endregion
1259
+ //#region src/collect/parse.ts
1260
+ const extractMdxImportsExports = (sourceText) => {
1261
+ const statements = [];
1262
+ let isInMultiline = false;
1263
+ let braceDepth = 0;
1264
+ for (const line of sourceText.split("\n")) {
1265
+ const trimmedLine = line.trim();
1266
+ if (isInMultiline) {
1267
+ statements.push(line);
1268
+ for (const character of trimmedLine) {
1269
+ if (character === "{") braceDepth++;
1270
+ if (character === "}") braceDepth--;
1271
+ }
1272
+ const hasFromClause = trimmedLine.includes(" from ") || trimmedLine.includes(" from'") || trimmedLine.includes(" from\"");
1273
+ if (braceDepth <= 0 || trimmedLine.endsWith(";") || hasFromClause) {
1274
+ isInMultiline = false;
1275
+ braceDepth = 0;
1276
+ }
1277
+ } else if (trimmedLine.startsWith("import ") || trimmedLine.startsWith("import{") || trimmedLine.startsWith("export ") || trimmedLine.startsWith("export{")) {
1278
+ statements.push(line);
1279
+ for (const character of trimmedLine) {
1280
+ if (character === "{") braceDepth++;
1281
+ if (character === "}") braceDepth--;
1282
+ }
1283
+ if (braceDepth > 0 && !trimmedLine.includes(" from ")) isInMultiline = true;
1284
+ }
1285
+ }
1286
+ return statements.join("\n");
1287
+ };
1288
+ const ASTRO_FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---/;
1289
+ const ASTRO_SCRIPT_TAG_PATTERN = /<script\b([^>]*?)\/>|<script\b([^>]*)>([\s\S]*?)<\/script\b[^>]*>/gi;
1290
+ const ASTRO_SCRIPT_SRC_ATTRIBUTE_PATTERN = /\bsrc\s*=\s*["']([^"']+)["']/i;
1291
+ const extractAstroSources = (sourceText) => {
1292
+ const sections = [];
1293
+ const frontmatterMatch = sourceText.match(ASTRO_FRONTMATTER_PATTERN);
1294
+ if (frontmatterMatch) sections.push(frontmatterMatch[1]);
1295
+ ASTRO_SCRIPT_TAG_PATTERN.lastIndex = 0;
1296
+ let scriptMatch;
1297
+ while ((scriptMatch = ASTRO_SCRIPT_TAG_PATTERN.exec(sourceText)) !== null) {
1298
+ const selfClosingAttributes = scriptMatch[1];
1299
+ const pairedAttributes = scriptMatch[2];
1300
+ const attributes = selfClosingAttributes ?? pairedAttributes ?? "";
1301
+ const body = selfClosingAttributes === void 0 ? scriptMatch[3] ?? "" : "";
1302
+ const srcMatch = attributes.match(ASTRO_SCRIPT_SRC_ATTRIBUTE_PATTERN);
1303
+ if (srcMatch) sections.push(`import ${JSON.stringify(srcMatch[1])};`);
1304
+ if (body) sections.push(body);
1305
+ }
1306
+ return sections.join("\n");
1307
+ };
1308
+ const VUE_SCRIPT_PATTERN = /<script[^>]*(?:lang=["'](?:ts|tsx)["'][^>]*)?>([\s\S]*?)<\/script\b[^>]*>/gi;
1309
+ const extractVueScriptContent = (sourceText) => {
1310
+ const scriptBlocks = [];
1311
+ let scriptMatch;
1312
+ VUE_SCRIPT_PATTERN.lastIndex = 0;
1313
+ while ((scriptMatch = VUE_SCRIPT_PATTERN.exec(sourceText)) !== null) if (scriptMatch[1]) scriptBlocks.push(scriptMatch[1]);
1314
+ return scriptBlocks.join("\n");
1315
+ };
1316
+ const SVELTE_SCRIPT_PATTERN = /<script[^>]*>([\s\S]*?)<\/script\b[^>]*>/gi;
1317
+ const extractSvelteScriptContent = (sourceText) => {
1318
+ const scriptBlocks = [];
1319
+ let scriptMatch;
1320
+ SVELTE_SCRIPT_PATTERN.lastIndex = 0;
1321
+ while ((scriptMatch = SVELTE_SCRIPT_PATTERN.exec(sourceText)) !== null) if (scriptMatch[1]) scriptBlocks.push(scriptMatch[1]);
1322
+ return scriptBlocks.join("\n");
1323
+ };
1324
+ const getModuleExportNameValue = (exportName) => {
1325
+ if (exportName.type === "Identifier") return exportName.name;
1326
+ if (exportName.type === "Literal") return exportName.value;
1327
+ return "default";
1328
+ };
1329
+ const CSS_EXTENSIONS = [
1330
+ ".css",
1331
+ ".scss",
1332
+ ".less",
1333
+ ".sass"
1334
+ ];
1335
+ const CSS_IMPORT_PATTERN = /@import\s+(?:url\()?['"]([^'"]+)['"]\)?/g;
1336
+ const SCSS_USE_FORWARD_PATTERN = /@(?:use|forward)\s+['"]([^'"]+)['"]/g;
1337
+ const TAILWIND_PLUGIN_REFERENCE_PATTERN = /@(?:plugin|reference|config)\s+['"]([^'"]+)['"]/g;
1338
+ const parseCssImports = (filePath) => {
1339
+ const sourceText = readFileSync(filePath, "utf-8");
1340
+ const imports = [];
1341
+ const patterns = [
1342
+ CSS_IMPORT_PATTERN,
1343
+ SCSS_USE_FORWARD_PATTERN,
1344
+ TAILWIND_PLUGIN_REFERENCE_PATTERN
1345
+ ];
1346
+ for (const pattern of patterns) {
1347
+ let match;
1348
+ pattern.lastIndex = 0;
1349
+ while ((match = pattern.exec(sourceText)) !== null) {
1350
+ const specifier = match[1];
1351
+ if (specifier && !specifier.startsWith("http")) imports.push({
1352
+ specifier,
1353
+ importedNames: [],
1354
+ isTypeOnly: false,
1355
+ isDynamic: false,
1356
+ isSideEffect: true,
1357
+ line: sourceText.substring(0, match.index).split("\n").length,
1358
+ column: 0
1359
+ });
1360
+ }
1361
+ }
1362
+ return {
1363
+ imports,
1364
+ exports: [],
1365
+ memberAccesses: [],
1366
+ wholeObjectUses: [],
1367
+ localIdentifierReferences: [],
1368
+ referencedFilenames: [],
1369
+ redundantTypePatterns: [],
1370
+ identityWrappers: [],
1371
+ typeDefinitionHashes: [],
1372
+ inlineTypeLiterals: [],
1373
+ simplifiableFunctions: [],
1374
+ simplifiableExpressions: [],
1375
+ duplicateConstantCandidates: [],
1376
+ errors: []
1377
+ };
1378
+ };
1379
+ const NON_JS_EXTENSIONS = [".graphql", ".gql"];
1380
+ const collectLocalIdentifierReferences = (statements) => {
1381
+ const references = [];
1382
+ const seenNames = /* @__PURE__ */ new Set();
1383
+ const visitNode = (node) => {
1384
+ if (!node || typeof node !== "object") return;
1385
+ const record = node;
1386
+ if (record.type === "Identifier" && typeof record.name === "string") {
1387
+ if (!seenNames.has(record.name)) {
1388
+ seenNames.add(record.name);
1389
+ references.push(record.name);
1390
+ }
1391
+ return;
1392
+ }
1393
+ for (const value of Object.values(record)) if (Array.isArray(value)) for (const innerValue of value) visitNode(innerValue);
1394
+ else if (value && typeof value === "object") visitNode(value);
1395
+ };
1396
+ for (const statement of statements) {
1397
+ if (statement.type === "ImportDeclaration" || statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" || statement.type === "ExportAllDeclaration") continue;
1398
+ visitNode(statement);
1399
+ }
1400
+ return references;
1401
+ };
1402
+ const createEmptyParsedSource = () => ({
1403
+ imports: [],
1404
+ exports: [],
1405
+ memberAccesses: [],
1406
+ wholeObjectUses: [],
1407
+ localIdentifierReferences: [],
1408
+ referencedFilenames: [],
1409
+ redundantTypePatterns: [],
1410
+ identityWrappers: [],
1411
+ typeDefinitionHashes: [],
1412
+ inlineTypeLiterals: [],
1413
+ simplifiableFunctions: [],
1414
+ simplifiableExpressions: [],
1415
+ duplicateConstantCandidates: [],
1416
+ errors: []
1417
+ });
1418
+ const stripByteOrderMark = (sourceText) => {
1419
+ if (sourceText.charCodeAt(0) === 65279) return sourceText.slice(1);
1420
+ return sourceText;
1421
+ };
1422
+ const looksLikeBinaryContent = (sourceText) => {
1423
+ const sampleLength = Math.min(sourceText.length, BINARY_DETECTION_SAMPLE_BYTES);
1424
+ let nullByteCount = 0;
1425
+ for (let scanIndex = 0; scanIndex < sampleLength; scanIndex++) {
1426
+ if (sourceText.charCodeAt(scanIndex) === 0) nullByteCount++;
1427
+ if (nullByteCount > 4) return true;
1428
+ }
1429
+ return false;
1430
+ };
1431
+ const looksLikeMinifiedSource = (sourceText) => {
1432
+ if (sourceText.length < 5e3) return false;
1433
+ let newlineCount = 0;
1434
+ for (let scanIndex = 0; scanIndex < sourceText.length; scanIndex++) if (sourceText.charCodeAt(scanIndex) === 10) newlineCount++;
1435
+ return sourceText.length / (newlineCount + 1) > 500;
1436
+ };
1437
+ const safeReadSourceFile = (filePath, errors) => {
1438
+ try {
1439
+ const stats = statSync(filePath);
1440
+ if (stats.size === 0) {
1441
+ errors.push(new FileReadError({
1442
+ code: "file-empty",
1443
+ severity: "info",
1444
+ message: "file is empty — nothing to analyze",
1445
+ path: filePath
1446
+ }));
1447
+ return;
1448
+ }
1449
+ if (stats.size > 2e6) {
1450
+ errors.push(new FileReadError({
1451
+ code: "file-too-large",
1452
+ message: `file size ${stats.size}B exceeds MAX_PARSE_FILE_SIZE_BYTES (${MAX_PARSE_FILE_SIZE_BYTES})`,
1453
+ path: filePath
1454
+ }));
1455
+ return;
1456
+ }
1457
+ } catch (statError) {
1458
+ errors.push(new FileReadError({
1459
+ code: "file-read-failed",
1460
+ message: "could not stat source file",
1461
+ path: filePath,
1462
+ detail: describeUnknownError(statError)
1463
+ }));
1464
+ return;
1465
+ }
1466
+ try {
1467
+ const sourceText = stripByteOrderMark(readFileSync(filePath, "utf-8"));
1468
+ if (looksLikeBinaryContent(sourceText)) {
1469
+ errors.push(new FileReadError({
1470
+ code: "file-binary",
1471
+ severity: "info",
1472
+ message: "file appears to be binary — skipping",
1473
+ path: filePath
1474
+ }));
1475
+ return;
1476
+ }
1477
+ if (looksLikeMinifiedSource(sourceText)) {
1478
+ errors.push(new FileReadError({
1479
+ code: "file-minified",
1480
+ severity: "info",
1481
+ message: "file appears to be a minified/bundled artifact — skipping redundancy analysis",
1482
+ path: filePath
1483
+ }));
1484
+ return;
1485
+ }
1486
+ return sourceText;
1487
+ } catch (readError) {
1488
+ errors.push(new FileReadError({
1489
+ code: "file-read-failed",
1490
+ message: "could not read source file",
1491
+ path: filePath,
1492
+ detail: describeUnknownError(readError)
1493
+ }));
1494
+ return;
1495
+ }
1496
+ };
1497
+ const parseSourceFile = (filePath) => {
1498
+ if (CSS_EXTENSIONS.some((ext) => filePath.endsWith(ext))) try {
1499
+ return parseCssImports(filePath);
1500
+ } catch (cssError) {
1501
+ return {
1502
+ ...createEmptyParsedSource(),
1503
+ errors: [new ParseError({
1504
+ code: "parse-failed",
1505
+ message: "CSS import parsing crashed",
1506
+ path: filePath,
1507
+ detail: describeUnknownError(cssError)
1508
+ })]
1509
+ };
1510
+ }
1511
+ if (NON_JS_EXTENSIONS.some((ext) => filePath.endsWith(ext))) return createEmptyParsedSource();
1512
+ const earlyErrors = [];
1513
+ const sourceText = safeReadSourceFile(filePath, earlyErrors);
1514
+ if (sourceText === void 0) return {
1515
+ ...createEmptyParsedSource(),
1516
+ errors: earlyErrors
1517
+ };
1518
+ const imports = [];
1519
+ const exports = [];
1520
+ const isMdx = filePath.endsWith(".mdx");
1521
+ const isAstro = filePath.endsWith(".astro");
1522
+ const isVue = filePath.endsWith(".vue");
1523
+ const isSvelte = filePath.endsWith(".svelte");
1524
+ const isPreprocessed = isMdx || isAstro || isVue || isSvelte;
1525
+ const textToParse = isMdx ? extractMdxImportsExports(sourceText) : isAstro ? extractAstroSources(sourceText) : isVue ? extractVueScriptContent(sourceText) : isSvelte ? extractSvelteScriptContent(sourceText) : sourceText;
1526
+ const parseFileName = isMdx || isAstro || isVue || isSvelte ? filePath.replace(/\.(mdx|astro|vue|svelte)$/, ".tsx") : filePath;
1527
+ let result;
1528
+ try {
1529
+ result = parseSync(parseFileName, textToParse);
1530
+ } catch (parseError) {
1531
+ return {
1532
+ ...createEmptyParsedSource(),
1533
+ errors: [...earlyErrors, new ParseError({
1534
+ code: "parse-failed",
1535
+ message: "oxc-parser threw during initial parse",
1536
+ path: filePath,
1537
+ detail: describeUnknownError(parseError)
1538
+ })]
1539
+ };
1540
+ }
1541
+ if ((parseFileName.endsWith(".js") || parseFileName.endsWith(".mjs") || parseFileName.endsWith(".cjs")) && result.errors.length > 0) try {
1542
+ const jsxResult = parseSync(parseFileName.replace(/\.(m?js|cjs)$/, ".jsx"), textToParse);
1543
+ if (jsxResult.errors.length === 0) result = jsxResult;
1544
+ else {
1545
+ const tsxResult = parseSync(parseFileName.replace(/\.(m?js|cjs)$/, ".tsx"), textToParse);
1546
+ if (tsxResult.errors.length === 0) result = tsxResult;
1547
+ }
1548
+ } catch {}
1549
+ if (result.errors.length > 0 && !isPreprocessed) return {
1550
+ ...createEmptyParsedSource(),
1551
+ imports,
1552
+ exports,
1553
+ referencedFilenames: extractReferencedFilenames(sourceText),
1554
+ errors: [...earlyErrors, new ParseError({
1555
+ code: "parse-recovered",
1556
+ severity: "info",
1557
+ message: `oxc-parser reported ${result.errors.length} syntax issue(s); skipping deep analysis for this file`,
1558
+ path: filePath
1559
+ })]
1560
+ };
1561
+ if (result.errors.length > 0) earlyErrors.push(new ParseError({
1562
+ code: "parse-recovered-partial",
1563
+ severity: "info",
1564
+ message: `oxc-parser reported ${result.errors.length} syntax issue(s) in extracted ${isAstro ? "Astro" : isVue ? "Vue" : isSvelte ? "Svelte" : "MDX"} sources; continuing with partial AST`,
1565
+ path: filePath
1566
+ }));
1567
+ const program = result.program;
1568
+ if (!program?.body) return {
1569
+ ...createEmptyParsedSource(),
1570
+ imports,
1571
+ exports,
1572
+ referencedFilenames: extractReferencedFilenames(sourceText),
1573
+ errors: [...earlyErrors, new ParseError({
1574
+ code: "parse-failed",
1575
+ message: "oxc-parser returned no program body",
1576
+ path: filePath
1577
+ })]
1578
+ };
1579
+ const detectorErrors = [];
1580
+ const safeWalk = (walkerName, walker, fallback) => {
1581
+ try {
1582
+ return walker();
1583
+ } catch (walkError) {
1584
+ detectorErrors.push(new ParseError({
1585
+ code: "ast-walk-failed",
1586
+ message: `${walkerName} threw during AST traversal`,
1587
+ path: filePath,
1588
+ detail: describeUnknownError(walkError)
1589
+ }));
1590
+ return fallback;
1591
+ }
1592
+ };
1593
+ safeWalk("extractImportsAndExports", () => {
1594
+ for (const node of program.body) switch (node.type) {
1595
+ case "ImportDeclaration":
1596
+ extractImportDeclaration(node, sourceText, imports);
1597
+ break;
1598
+ case "ExportNamedDeclaration":
1599
+ extractNamedExportDeclaration(node, sourceText, exports);
1600
+ break;
1601
+ case "ExportDefaultDeclaration":
1602
+ extractDefaultExportDeclaration(node, sourceText, exports);
1603
+ break;
1604
+ case "ExportAllDeclaration":
1605
+ extractExportAllDeclaration(node, sourceText, exports);
1606
+ break;
1607
+ }
1608
+ }, void 0);
1609
+ safeWalk("collectDynamicImports", () => {
1610
+ collectDynamicImports(program.body, sourceText, imports);
1611
+ }, void 0);
1612
+ const namespaceLocalNames = collectNamespaceLocalNames(imports);
1613
+ const memberAccesses = [];
1614
+ const wholeObjectUses = [];
1615
+ if (namespaceLocalNames.size > 0) safeWalk("collectMemberAccesses", () => {
1616
+ collectMemberAccesses(program.body, namespaceLocalNames, memberAccesses, wholeObjectUses);
1617
+ }, void 0);
1618
+ const localIdentifierReferences = safeWalk("collectLocalIdentifierReferences", () => collectLocalIdentifierReferences(program.body), []);
1619
+ const redundantTypePatterns = [];
1620
+ const identityWrappers = [];
1621
+ const typeDefinitionHashes = [];
1622
+ safeWalk("collectDryPatterns", () => {
1623
+ collectDryPatterns(program.body, sourceText, redundantTypePatterns, identityWrappers, typeDefinitionHashes);
1624
+ }, void 0);
1625
+ const inlineTypeLiterals = safeWalk("collectInlineTypeLiterals", () => collectInlineTypeLiterals(program.body), []).map((capture) => ({
1626
+ structuralHash: capture.structuralHash,
1627
+ memberCount: capture.memberCount,
1628
+ preview: capture.preview,
1629
+ context: capture.context,
1630
+ nearestName: capture.nearestName,
1631
+ line: getLineFromOffset(sourceText, capture.startOffset),
1632
+ column: getColumnFromOffset(sourceText, capture.startOffset)
1633
+ }));
1634
+ const simplifiableFunctions = safeWalk("collectSimplifiableFunctions", () => collectSimplifiableFunctions(program.body), []).map((capture) => ({
1635
+ kind: capture.kind,
1636
+ functionName: capture.functionName,
1637
+ line: getLineFromOffset(sourceText, capture.startOffset),
1638
+ column: getColumnFromOffset(sourceText, capture.startOffset),
1639
+ reason: capture.reason,
1640
+ suggestion: capture.suggestion
1641
+ }));
1642
+ const simplifiableExpressions = safeWalk("collectSimplifiableExpressions", () => collectSimplifiableExpressions(program.body), []).map((capture) => ({
1643
+ kind: capture.kind,
1644
+ snippet: capture.snippet,
1645
+ line: getLineFromOffset(sourceText, capture.startOffset),
1646
+ column: getColumnFromOffset(sourceText, capture.startOffset),
1647
+ reason: capture.reason,
1648
+ suggestion: capture.suggestion
1649
+ }));
1650
+ const duplicateConstantCandidates = safeWalk("collectDuplicateConstantCandidates", () => collectDuplicateConstantCandidates(program.body), []).map((capture) => ({
1651
+ constantName: capture.constantName,
1652
+ literalHash: capture.literalHash,
1653
+ literalPreview: capture.literalPreview,
1654
+ line: getLineFromOffset(sourceText, capture.startOffset),
1655
+ column: getColumnFromOffset(sourceText, capture.startOffset)
1656
+ }));
1657
+ return {
1658
+ imports,
1659
+ exports,
1660
+ memberAccesses,
1661
+ wholeObjectUses,
1662
+ localIdentifierReferences,
1663
+ referencedFilenames: extractReferencedFilenames(sourceText),
1664
+ redundantTypePatterns,
1665
+ identityWrappers,
1666
+ typeDefinitionHashes,
1667
+ inlineTypeLiterals,
1668
+ simplifiableFunctions,
1669
+ simplifiableExpressions,
1670
+ duplicateConstantCandidates,
1671
+ errors: [...earlyErrors, ...detectorErrors]
1672
+ };
1673
+ };
1674
+ const REFERENCED_FILENAME_LITERAL_PATTERN = /(?<![./@\w-])(?:["'`])([a-z][\w-]*\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs))(?:["'`])/g;
1675
+ const extractReferencedFilenames = (sourceText) => {
1676
+ const captured = /* @__PURE__ */ new Set();
1677
+ REFERENCED_FILENAME_LITERAL_PATTERN.lastIndex = 0;
1678
+ let match;
1679
+ while ((match = REFERENCED_FILENAME_LITERAL_PATTERN.exec(sourceText)) !== null) captured.add(match[1]);
1680
+ return [...captured];
1681
+ };
1682
+ const collectDryPatterns = (bodyNodes, sourceText, redundantTypePatterns, identityWrappers, typeDefinitionHashes) => {
1683
+ for (const statement of bodyNodes) inspectStatement(statement, sourceText, redundantTypePatterns, identityWrappers, typeDefinitionHashes);
1684
+ };
1685
+ const inspectStatement = (statementNode, sourceText, redundantTypePatterns, identityWrappers, typeDefinitionHashes) => {
1686
+ let declarationOfInterest = statementNode;
1687
+ if (statementNode.type === "ExportNamedDeclaration" && statementNode.declaration) declarationOfInterest = statementNode.declaration;
1688
+ if (declarationOfInterest && typeof declarationOfInterest === "object") {
1689
+ const declarationNode = declarationOfInterest;
1690
+ if (declarationNode.type === "TSTypeAliasDeclaration") {
1691
+ const typeAliasName = declarationNode.id?.name;
1692
+ const typeAnnotation = declarationNode.typeAnnotation;
1693
+ const startOffset = declarationNode.start ?? 0;
1694
+ if (typeAliasName && typeAnnotation) {
1695
+ const redundantPattern = detectRedundantTypePatternForTypeAnnotation(typeAnnotation);
1696
+ if (redundantPattern) redundantTypePatterns.push({
1697
+ typeName: typeAliasName,
1698
+ kind: redundantPattern.kind,
1699
+ line: getLineFromOffset(sourceText, startOffset),
1700
+ column: getColumnFromOffset(sourceText, startOffset),
1701
+ reason: redundantPattern.reason,
1702
+ suggestion: redundantPattern.suggestion
1703
+ });
1704
+ typeDefinitionHashes.push({
1705
+ typeName: typeAliasName,
1706
+ structuralHash: `alias:${normalizeTypeAstHash(typeAnnotation)}`,
1707
+ line: getLineFromOffset(sourceText, startOffset),
1708
+ column: getColumnFromOffset(sourceText, startOffset)
1709
+ });
1710
+ }
1711
+ } else if (declarationNode.type === "TSInterfaceDeclaration") {
1712
+ const interfaceName = declarationNode.id?.name;
1713
+ const startOffset = declarationNode.start ?? 0;
1714
+ if (interfaceName) {
1715
+ const redundantPattern = detectRedundantInterfaceDeclaration(declarationNode);
1716
+ if (redundantPattern) redundantTypePatterns.push({
1717
+ typeName: interfaceName,
1718
+ kind: redundantPattern.kind,
1719
+ line: getLineFromOffset(sourceText, startOffset),
1720
+ column: getColumnFromOffset(sourceText, startOffset),
1721
+ reason: redundantPattern.reason,
1722
+ suggestion: redundantPattern.suggestion
1723
+ });
1724
+ const declarationCopy = {
1725
+ ...declarationNode,
1726
+ id: void 0
1727
+ };
1728
+ typeDefinitionHashes.push({
1729
+ typeName: interfaceName,
1730
+ structuralHash: `interface:${normalizeTypeAstHash(declarationCopy)}`,
1731
+ line: getLineFromOffset(sourceText, startOffset),
1732
+ column: getColumnFromOffset(sourceText, startOffset)
1733
+ });
1734
+ }
1735
+ } else if (declarationNode.type === "VariableDeclaration") for (const declarator of declarationNode.declarations ?? []) {
1736
+ const wrapperName = declarator.id?.name;
1737
+ const initializerNode = declarator.init;
1738
+ const startOffset = declarator.start ?? declarationNode.start ?? 0;
1739
+ if (!wrapperName || !initializerNode) continue;
1740
+ const wrapperDetection = detectIdentityWrapperFromInitializer(initializerNode, wrapperName);
1741
+ if (wrapperDetection) identityWrappers.push({
1742
+ wrapperName,
1743
+ wrappedExpression: wrapperDetection.wrappedExpression,
1744
+ line: getLineFromOffset(sourceText, startOffset),
1745
+ column: getColumnFromOffset(sourceText, startOffset)
1746
+ });
1747
+ }
1748
+ }
1749
+ };
1750
+ const WHOLE_OBJECT_FUNCTION_NAMES = new Set([
1751
+ "keys",
1752
+ "values",
1753
+ "entries",
1754
+ "assign",
1755
+ "freeze",
1756
+ "getOwnPropertyNames",
1757
+ "getOwnPropertyDescriptors"
1758
+ ]);
1759
+ const collectNamespaceLocalNames = (imports) => {
1760
+ const namespaceNames = /* @__PURE__ */ new Set();
1761
+ for (const importInfo of imports) for (const importedName of importInfo.importedNames) if (importedName.isNamespace && importedName.alias) namespaceNames.add(importedName.alias);
1762
+ return namespaceNames;
1763
+ };
1764
+ const collectMemberAccesses = (bodyNodes, namespaceLocalNames, memberAccesses, wholeObjectUses) => {
1765
+ const walkForMemberAccesses = (node) => {
1766
+ if (node.type === "MemberExpression" && !node.computed) {
1767
+ const memberExpression = node;
1768
+ if (memberExpression.object.type === "Identifier" && namespaceLocalNames.has(memberExpression.object.name)) {
1769
+ const objectName = memberExpression.object.name;
1770
+ const memberName = memberExpression.property.name;
1771
+ if (memberName) memberAccesses.push({
1772
+ objectName,
1773
+ memberName
1774
+ });
1775
+ }
1776
+ }
1777
+ if (node.type === "MemberExpression" && Boolean(node.computed)) {
1778
+ const computedExpression = node;
1779
+ if (computedExpression.object.type === "Identifier" && namespaceLocalNames.has(computedExpression.object.name)) {
1780
+ const objectName = computedExpression.object.name;
1781
+ const expressionNode = node.expression;
1782
+ if (expressionNode?.type === "Literal") {
1783
+ const literalValue = expressionNode.value;
1784
+ if (typeof literalValue === "string") memberAccesses.push({
1785
+ objectName,
1786
+ memberName: literalValue
1787
+ });
1788
+ else wholeObjectUses.push(objectName);
1789
+ } else wholeObjectUses.push(objectName);
1790
+ }
1791
+ }
1792
+ if (node.type === "JSXMemberExpression") {
1793
+ const jsxMember = node;
1794
+ if (jsxMember.object.type === "JSXIdentifier" && jsxMember.object.name !== void 0 && namespaceLocalNames.has(jsxMember.object.name) && jsxMember.property.name !== void 0) memberAccesses.push({
1795
+ objectName: jsxMember.object.name,
1796
+ memberName: jsxMember.property.name
1797
+ });
1798
+ }
1799
+ if (node.type === "SpreadElement") {
1800
+ const spreadArgument = node.argument;
1801
+ if (spreadArgument?.type === "Identifier" && namespaceLocalNames.has(spreadArgument.name)) wholeObjectUses.push(spreadArgument.name);
1802
+ }
1803
+ if (node.type === "ForInStatement") {
1804
+ const forInRight = node.right;
1805
+ if (forInRight?.type === "Identifier" && namespaceLocalNames.has(forInRight.name)) wholeObjectUses.push(forInRight.name);
1806
+ }
1807
+ if (node.type === "CallExpression") {
1808
+ const callExpression = node;
1809
+ if (callExpression.callee.type === "MemberExpression" && !callExpression.callee.computed) {
1810
+ const calleeMember = callExpression.callee;
1811
+ if (calleeMember.object.type === "Identifier" && calleeMember.object.name === "Object" && WHOLE_OBJECT_FUNCTION_NAMES.has(calleeMember.property.name)) {
1812
+ const firstArgument = callExpression.arguments[0];
1813
+ if (firstArgument && firstArgument.type !== "SpreadElement" && firstArgument.type === "Identifier" && namespaceLocalNames.has(firstArgument.name)) wholeObjectUses.push(firstArgument.name);
1814
+ }
1815
+ }
1816
+ }
1817
+ for (const value of Object.values(node)) if (Array.isArray(value)) {
1818
+ for (const element of value) if (isWalkableNode(element)) walkForMemberAccesses(element);
1819
+ } else if (isWalkableNode(value)) walkForMemberAccesses(value);
1820
+ };
1821
+ for (const topLevelNode of bodyNodes) if (isWalkableNode(topLevelNode)) walkForMemberAccesses(topLevelNode);
1822
+ };
1823
+ const extractImportDeclaration = (node, sourceText, imports) => {
1824
+ const specifier = node.source.value;
1825
+ if (!specifier) return;
1826
+ const isTypeOnly = node.importKind === "type";
1827
+ const importedNames = [];
1828
+ for (const specifierNode of node.specifiers) switch (specifierNode.type) {
1829
+ case "ImportDefaultSpecifier":
1830
+ importedNames.push({
1831
+ name: "default",
1832
+ alias: specifierNode.local.name,
1833
+ isNamespace: false,
1834
+ isDefault: true,
1835
+ isTypeOnly
1836
+ });
1837
+ break;
1838
+ case "ImportNamespaceSpecifier":
1839
+ importedNames.push({
1840
+ name: "*",
1841
+ alias: specifierNode.local.name,
1842
+ isNamespace: true,
1843
+ isDefault: false,
1844
+ isTypeOnly
1845
+ });
1846
+ break;
1847
+ case "ImportSpecifier": {
1848
+ const importedName = getModuleExportNameValue(specifierNode.imported);
1849
+ const localName = specifierNode.local.name;
1850
+ const isSelfAlias = localName === importedName && specifierNode.imported.type === "Identifier" && specifierNode.imported.start !== specifierNode.local.start;
1851
+ importedNames.push({
1852
+ name: importedName,
1853
+ alias: localName !== importedName ? localName : void 0,
1854
+ isNamespace: false,
1855
+ isDefault: importedName === "default",
1856
+ isTypeOnly: isTypeOnly || specifierNode.importKind === "type",
1857
+ isRedundantAlias: isSelfAlias || void 0
1858
+ });
1859
+ break;
1860
+ }
1861
+ }
1862
+ const isSideEffectImport = importedNames.length === 0;
1863
+ if (isSideEffectImport) importedNames.push({
1864
+ name: "*",
1865
+ alias: void 0,
1866
+ isNamespace: false,
1867
+ isDefault: false,
1868
+ isTypeOnly: false
1869
+ });
1870
+ imports.push({
1871
+ specifier,
1872
+ importedNames,
1873
+ isTypeOnly,
1874
+ isDynamic: false,
1875
+ isSideEffect: isSideEffectImport,
1876
+ line: getLineFromOffset(sourceText, node.start),
1877
+ column: getColumnFromOffset(sourceText, node.start)
1878
+ });
1879
+ };
1880
+ const extractNamedExportDeclaration = (node, sourceText, exports) => {
1881
+ const isTypeOnly = node.exportKind === "type";
1882
+ const reExportSource = node.source?.value;
1883
+ if (node.declaration) extractDeclarationNames(node.declaration, isTypeOnly, sourceText, exports, node.start);
1884
+ for (const specifierNode of node.specifiers) {
1885
+ const exportedName = getModuleExportNameValue(specifierNode.exported);
1886
+ const localName = getModuleExportNameValue(specifierNode.local);
1887
+ const isSelfAlias = exportedName === localName && specifierNode.exported.type === "Identifier" && specifierNode.local.type === "Identifier" && specifierNode.exported.start !== specifierNode.local.start;
1888
+ exports.push({
1889
+ name: exportedName,
1890
+ isDefault: exportedName === "default",
1891
+ isTypeOnly: isTypeOnly || specifierNode.exportKind === "type",
1892
+ isReExport: reExportSource !== void 0,
1893
+ isSynthetic: false,
1894
+ reExportSource,
1895
+ reExportOriginalName: reExportSource !== void 0 ? localName : void 0,
1896
+ isNamespaceReExport: false,
1897
+ line: getLineFromOffset(sourceText, specifierNode.start ?? node.start),
1898
+ column: getColumnFromOffset(sourceText, specifierNode.start ?? node.start),
1899
+ isRedundantAlias: isSelfAlias || void 0
1900
+ });
1901
+ }
1902
+ };
1903
+ const extractDefaultExportDeclaration = (node, sourceText, exports) => {
1904
+ const defaultExportLocalName = extractDefaultExportLocalName(node.declaration);
1905
+ exports.push({
1906
+ name: "default",
1907
+ isDefault: true,
1908
+ isTypeOnly: false,
1909
+ isReExport: false,
1910
+ isSynthetic: false,
1911
+ reExportSource: void 0,
1912
+ reExportOriginalName: void 0,
1913
+ isNamespaceReExport: false,
1914
+ line: getLineFromOffset(sourceText, node.start),
1915
+ column: getColumnFromOffset(sourceText, node.start),
1916
+ defaultExportLocalName
1917
+ });
1918
+ };
1919
+ const extractExportAllDeclaration = (node, sourceText, exports) => {
1920
+ const reExportSource = node.source.value;
1921
+ if (!reExportSource) return;
1922
+ const exportedName = node.exported ? getModuleExportNameValue(node.exported) : void 0;
1923
+ exports.push({
1924
+ name: exportedName ?? "*",
1925
+ isDefault: false,
1926
+ isTypeOnly: node.exportKind === "type",
1927
+ isReExport: true,
1928
+ isSynthetic: false,
1929
+ reExportSource,
1930
+ reExportOriginalName: "*",
1931
+ isNamespaceReExport: !exportedName,
1932
+ line: getLineFromOffset(sourceText, node.start),
1933
+ column: getColumnFromOffset(sourceText, node.start)
1934
+ });
1935
+ };
1936
+ const extractDeclarationNames = (declaration, isTypeOnly, sourceText, exports, fallbackStart) => {
1937
+ const declarationType = declaration.type;
1938
+ if (declarationType === "FunctionDeclaration" || declarationType === "ClassDeclaration" || declarationType === "TSEnumDeclaration") {
1939
+ const declarationName = declaration.id?.name;
1940
+ if (declarationName) exports.push({
1941
+ name: declarationName,
1942
+ isDefault: false,
1943
+ isTypeOnly,
1944
+ isReExport: false,
1945
+ isSynthetic: false,
1946
+ reExportSource: void 0,
1947
+ reExportOriginalName: void 0,
1948
+ isNamespaceReExport: false,
1949
+ line: getLineFromOffset(sourceText, declaration.start ?? fallbackStart),
1950
+ column: getColumnFromOffset(sourceText, declaration.start ?? fallbackStart)
1951
+ });
1952
+ return;
1953
+ }
1954
+ if (declarationType === "TSTypeAliasDeclaration" || declarationType === "TSInterfaceDeclaration") {
1955
+ const declarationName = declaration.id.name;
1956
+ if (declarationName) exports.push({
1957
+ name: declarationName,
1958
+ isDefault: false,
1959
+ isTypeOnly: true,
1960
+ isReExport: false,
1961
+ isSynthetic: false,
1962
+ reExportSource: void 0,
1963
+ reExportOriginalName: void 0,
1964
+ isNamespaceReExport: false,
1965
+ line: getLineFromOffset(sourceText, declaration.start ?? fallbackStart),
1966
+ column: getColumnFromOffset(sourceText, declaration.start ?? fallbackStart)
1967
+ });
1968
+ return;
1969
+ }
1970
+ if (declarationType === "VariableDeclaration") {
1971
+ const variableDeclaration = declaration;
1972
+ for (const declarator of variableDeclaration.declarations) {
1973
+ const bindingNames = extractBindingPatternNames(declarator.id);
1974
+ for (const bindingName of bindingNames) exports.push({
1975
+ name: bindingName,
1976
+ isDefault: false,
1977
+ isTypeOnly,
1978
+ isReExport: false,
1979
+ isSynthetic: false,
1980
+ reExportSource: void 0,
1981
+ reExportOriginalName: void 0,
1982
+ isNamespaceReExport: false,
1983
+ line: getLineFromOffset(sourceText, declarator.start ?? fallbackStart),
1984
+ column: getColumnFromOffset(sourceText, declarator.start ?? fallbackStart)
1985
+ });
1986
+ }
1987
+ }
1988
+ };
1989
+ const extractBindingPatternNames = (pattern) => {
1990
+ if (!pattern) return [];
1991
+ if (pattern.type === "Identifier") return pattern.name ? [pattern.name] : [];
1992
+ if (pattern.type === "ObjectPattern") {
1993
+ const names = [];
1994
+ for (const property of pattern.properties) if (property.type === "RestElement") names.push(...extractBindingPatternNames(property.argument));
1995
+ else names.push(...extractBindingPatternNames(property.value));
1996
+ return names;
1997
+ }
1998
+ if (pattern.type === "ArrayPattern") {
1999
+ const names = [];
2000
+ for (const element of pattern.elements) {
2001
+ if (!element) continue;
2002
+ if (element.type === "RestElement") names.push(...extractBindingPatternNames(element.argument));
2003
+ else names.push(...extractBindingPatternNames(element));
2004
+ }
2005
+ return names;
2006
+ }
2007
+ if (pattern.type === "AssignmentPattern") return extractBindingPatternNames(pattern.left);
2008
+ return [];
2009
+ };
2010
+ const createNamespaceImportBinding = () => ({
2011
+ name: "*",
2012
+ alias: void 0,
2013
+ isNamespace: true,
2014
+ isDefault: false,
2015
+ isTypeOnly: false
2016
+ });
2017
+ const isWalkableNode = (value) => Boolean(value) && typeof value === "object" && typeof value.type === "string";
2018
+ const extractStringLiteralFromArgument = (callArguments) => {
2019
+ const firstArgument = callArguments[0];
2020
+ if (!firstArgument) return void 0;
2021
+ if (firstArgument.type === "SpreadElement") return void 0;
2022
+ if (firstArgument.type !== "Literal") return void 0;
2023
+ const literalValue = firstArgument.value;
2024
+ return typeof literalValue === "string" ? literalValue : void 0;
2025
+ };
2026
+ const extractGlobPatterns = (callArguments) => {
2027
+ const firstArgument = callArguments[0];
2028
+ if (!firstArgument || firstArgument.type === "SpreadElement") return [];
2029
+ if (firstArgument.type === "Literal") {
2030
+ const literalValue = firstArgument.value;
2031
+ if (typeof literalValue === "string" && (literalValue.startsWith("./") || literalValue.startsWith("../"))) return [literalValue];
2032
+ return [];
2033
+ }
2034
+ if (firstArgument.type === "ArrayExpression") return firstArgument.elements.filter((element) => element.type === "Literal" && typeof element.value === "string" && (element.value.startsWith("./") || element.value.startsWith("../"))).map((element) => element.value);
2035
+ return [];
2036
+ };
2037
+ const extractRegexGlobSuffix = (callArguments) => {
2038
+ const thirdArgument = callArguments[2];
2039
+ if (!thirdArgument || thirdArgument.type === "SpreadElement") return void 0;
2040
+ if (thirdArgument.type !== "Literal") return void 0;
2041
+ const regExpValue = thirdArgument.regex;
2042
+ if (!regExpValue) return void 0;
2043
+ const extensionMatch = regExpValue.pattern.match(/^\\\.([\w|]+)\$$/);
2044
+ if (extensionMatch) {
2045
+ const extensions = extensionMatch[1].split("|");
2046
+ if (extensions.length === 1) return `*.${extensions[0]}`;
2047
+ return `*.{${extensions.join(",")}}`;
2048
+ }
2049
+ };
2050
+ const hasMockFactoryArgument = (callExpression) => {
2051
+ const secondArgument = callExpression.arguments[1];
2052
+ if (!secondArgument) return false;
2053
+ if (secondArgument.type === "SpreadElement") return false;
2054
+ return secondArgument.type === "ArrowFunctionExpression" || secondArgument.type === "FunctionExpression";
2055
+ };
2056
+ const synthesizeAutoMockSibling = (mockSource) => {
2057
+ if (!mockSource || mockSource.includes("://") || mockSource.startsWith("data:") || mockSource.split("/").some((segment) => segment === "__mocks__")) return;
2058
+ const lastSlashIndex = mockSource.lastIndexOf("/");
2059
+ if (lastSlashIndex === -1) return void 0;
2060
+ const directory = mockSource.slice(0, lastSlashIndex);
2061
+ const fileName = mockSource.slice(lastSlashIndex + 1);
2062
+ if (!fileName) return void 0;
2063
+ return `${directory}/__mocks__/${fileName}`;
2064
+ };
2065
+ const collectDynamicImports = (bodyNodes, sourceText, imports) => {
2066
+ const walkNode = (node) => {
2067
+ if (node.type === "ImportExpression") {
2068
+ const importExpression = node;
2069
+ const sourceExpression = importExpression.source;
2070
+ if (sourceExpression.type === "Literal") {
2071
+ const specifierValue = sourceExpression.value;
2072
+ if (specifierValue) imports.push({
2073
+ specifier: specifierValue,
2074
+ importedNames: [createNamespaceImportBinding()],
2075
+ isTypeOnly: false,
2076
+ isDynamic: true,
2077
+ isSideEffect: false,
2078
+ line: getLineFromOffset(sourceText, importExpression.start),
2079
+ column: getColumnFromOffset(sourceText, importExpression.start)
2080
+ });
2081
+ } else if (sourceExpression.type === "TemplateLiteral") {
2082
+ const templateLiteral = sourceExpression;
2083
+ if (templateLiteral.quasis.length >= 2) {
2084
+ const globPattern = templateLiteral.quasis.map((quasi) => quasi.value.cooked).join("*");
2085
+ if (globPattern.startsWith("./") || globPattern.startsWith("../")) imports.push({
2086
+ specifier: globPattern,
2087
+ importedNames: [createNamespaceImportBinding()],
2088
+ isTypeOnly: false,
2089
+ isDynamic: true,
2090
+ isSideEffect: false,
2091
+ isGlob: true,
2092
+ line: getLineFromOffset(sourceText, importExpression.start),
2093
+ column: getColumnFromOffset(sourceText, importExpression.start)
2094
+ });
2095
+ }
2096
+ }
2097
+ return;
2098
+ }
2099
+ if (node.type === "CallExpression") {
2100
+ const callExpression = node;
2101
+ if (callExpression.callee.type === "Identifier" && callExpression.callee.name === "require") {
2102
+ const requireSpecifier = extractStringLiteralFromArgument(callExpression.arguments);
2103
+ if (requireSpecifier) imports.push({
2104
+ specifier: requireSpecifier,
2105
+ importedNames: [createNamespaceImportBinding()],
2106
+ isTypeOnly: false,
2107
+ isDynamic: true,
2108
+ isSideEffect: false,
2109
+ line: getLineFromOffset(sourceText, callExpression.start),
2110
+ column: getColumnFromOffset(sourceText, callExpression.start)
2111
+ });
2112
+ }
2113
+ if (callExpression.callee.type === "MemberExpression" && !callExpression.callee.computed) {
2114
+ const memberExpression = callExpression.callee;
2115
+ if (memberExpression.object.type === "Identifier" && memberExpression.object.name === "require" && memberExpression.property.name === "resolve") {
2116
+ const resolveSpecifier = extractStringLiteralFromArgument(callExpression.arguments);
2117
+ if (resolveSpecifier) imports.push({
2118
+ specifier: resolveSpecifier,
2119
+ importedNames: [createNamespaceImportBinding()],
2120
+ isTypeOnly: false,
2121
+ isDynamic: true,
2122
+ isSideEffect: false,
2123
+ line: getLineFromOffset(sourceText, callExpression.start),
2124
+ column: getColumnFromOffset(sourceText, callExpression.start)
2125
+ });
2126
+ }
2127
+ if (memberExpression.object.type === "Identifier" && (memberExpression.object.name === "vi" || memberExpression.object.name === "jest") && memberExpression.property.name === "mock") {
2128
+ const mockSpecifier = extractStringLiteralFromArgument(callExpression.arguments);
2129
+ if (mockSpecifier) {
2130
+ imports.push({
2131
+ specifier: mockSpecifier,
2132
+ importedNames: [createNamespaceImportBinding()],
2133
+ isTypeOnly: false,
2134
+ isDynamic: true,
2135
+ isSideEffect: true,
2136
+ line: getLineFromOffset(sourceText, callExpression.start),
2137
+ column: getColumnFromOffset(sourceText, callExpression.start)
2138
+ });
2139
+ const hasFactoryArgument = hasMockFactoryArgument(callExpression);
2140
+ const autoMockSibling = synthesizeAutoMockSibling(mockSpecifier);
2141
+ if (!hasFactoryArgument && autoMockSibling) imports.push({
2142
+ specifier: autoMockSibling,
2143
+ importedNames: [createNamespaceImportBinding()],
2144
+ isTypeOnly: false,
2145
+ isDynamic: true,
2146
+ isSideEffect: true,
2147
+ line: getLineFromOffset(sourceText, callExpression.start),
2148
+ column: getColumnFromOffset(sourceText, callExpression.start)
2149
+ });
2150
+ }
2151
+ }
2152
+ if (memberExpression.object.type === "MetaProperty" && memberExpression.property.name === "glob") {
2153
+ const globPatterns = extractGlobPatterns(callExpression.arguments);
2154
+ for (const globPattern of globPatterns) imports.push({
2155
+ specifier: globPattern,
2156
+ importedNames: [createNamespaceImportBinding()],
2157
+ isTypeOnly: false,
2158
+ isDynamic: true,
2159
+ isSideEffect: false,
2160
+ isGlob: true,
2161
+ line: getLineFromOffset(sourceText, callExpression.start),
2162
+ column: getColumnFromOffset(sourceText, callExpression.start)
2163
+ });
2164
+ }
2165
+ if (memberExpression.object.type === "Identifier" && memberExpression.object.name === "require" && memberExpression.property.name === "context") {
2166
+ const directoryArgument = extractStringLiteralFromArgument(callExpression.arguments);
2167
+ if (directoryArgument && (directoryArgument.startsWith("./") || directoryArgument.startsWith("../"))) {
2168
+ const hasRegexArgument = callExpression.arguments.length >= 3 && callExpression.arguments[2].type !== "SpreadElement";
2169
+ const regexSuffix = extractRegexGlobSuffix(callExpression.arguments);
2170
+ if (!hasRegexArgument || Boolean(regexSuffix)) {
2171
+ const contextGlobPrefix = callExpression.arguments[1]?.type === "Literal" && callExpression.arguments[1].value === true ? `${directoryArgument}/**/` : `${directoryArgument}/`;
2172
+ const contextGlobPattern = regexSuffix ? `${contextGlobPrefix}${regexSuffix}` : `${contextGlobPrefix}*`;
2173
+ imports.push({
2174
+ specifier: contextGlobPattern,
2175
+ importedNames: [createNamespaceImportBinding()],
2176
+ isTypeOnly: false,
2177
+ isDynamic: true,
2178
+ isSideEffect: false,
2179
+ isGlob: true,
2180
+ line: getLineFromOffset(sourceText, callExpression.start),
2181
+ column: getColumnFromOffset(sourceText, callExpression.start)
2182
+ });
2183
+ }
2184
+ }
2185
+ }
2186
+ }
2187
+ }
2188
+ if (node.type === "NewExpression") {
2189
+ const newExpression = node;
2190
+ if (newExpression.callee.type === "Identifier" && newExpression.callee.name === "URL" && newExpression.arguments.length >= 2) {
2191
+ const secondArgument = newExpression.arguments[1];
2192
+ if (secondArgument.type === "MemberExpression" && secondArgument.object.type === "MetaProperty" && secondArgument.property.name === "url") {
2193
+ const urlSpecifier = extractStringLiteralFromArgument(newExpression.arguments);
2194
+ if (urlSpecifier) imports.push({
2195
+ specifier: urlSpecifier,
2196
+ importedNames: [createNamespaceImportBinding()],
2197
+ isTypeOnly: false,
2198
+ isDynamic: true,
2199
+ isSideEffect: true,
2200
+ line: getLineFromOffset(sourceText, newExpression.start),
2201
+ column: getColumnFromOffset(sourceText, newExpression.start)
2202
+ });
2203
+ }
2204
+ }
2205
+ }
2206
+ if (node.type === "Decorator") {
2207
+ const expression = node.expression;
2208
+ if (expression?.type === "CallExpression") {
2209
+ const callNode = expression;
2210
+ const callee = callNode.callee;
2211
+ if (callee.type === "Identifier" && callee.name === "Component") {
2212
+ const objectArgument = callNode.arguments[0];
2213
+ if (objectArgument?.type === "ObjectExpression") {
2214
+ const objectProperties = objectArgument.properties;
2215
+ for (const property of objectProperties) {
2216
+ if (property.type !== "ObjectProperty" && property.type !== "Property") continue;
2217
+ const propertyKey = property.key;
2218
+ const propertyName = propertyKey?.name ?? propertyKey?.value;
2219
+ const propertyValue = property.value;
2220
+ if (propertyName === "templateUrl" && propertyValue?.type === "Literal") {
2221
+ const templatePath = propertyValue.value;
2222
+ if (templatePath) imports.push({
2223
+ specifier: templatePath.startsWith(".") ? templatePath : `./${templatePath}`,
2224
+ importedNames: [],
2225
+ isTypeOnly: false,
2226
+ isDynamic: false,
2227
+ isSideEffect: true,
2228
+ line: getLineFromOffset(sourceText, property.start),
2229
+ column: getColumnFromOffset(sourceText, property.start)
2230
+ });
2231
+ }
2232
+ if ((propertyName === "styleUrl" || propertyName === "styleUrls") && propertyValue) {
2233
+ const styleUrlValues = [];
2234
+ if (propertyValue.type === "Literal") {
2235
+ const singleValue = propertyValue.value;
2236
+ if (singleValue) styleUrlValues.push(singleValue);
2237
+ } else if (propertyValue.type === "ArrayExpression") {
2238
+ const arrayElements = propertyValue.elements;
2239
+ for (const element of arrayElements) if (element?.type === "Literal") {
2240
+ const elementValue = element.value;
2241
+ if (elementValue) styleUrlValues.push(elementValue);
2242
+ }
2243
+ }
2244
+ for (const styleUrl of styleUrlValues) imports.push({
2245
+ specifier: styleUrl.startsWith(".") ? styleUrl : `./${styleUrl}`,
2246
+ importedNames: [],
2247
+ isTypeOnly: false,
2248
+ isDynamic: false,
2249
+ isSideEffect: true,
2250
+ line: getLineFromOffset(sourceText, property.start),
2251
+ column: getColumnFromOffset(sourceText, property.start)
2252
+ });
2253
+ }
2254
+ }
2255
+ }
2256
+ }
2257
+ }
2258
+ }
2259
+ for (const value of Object.values(node)) if (Array.isArray(value)) {
2260
+ for (const element of value) if (isWalkableNode(element)) walkNode(element);
2261
+ } else if (isWalkableNode(value)) walkNode(value);
2262
+ };
2263
+ for (const topLevelNode of bodyNodes) if (isWalkableNode(topLevelNode)) walkNode(topLevelNode);
2264
+ };
2265
+ const ROUTE_CALL_FILE_ARG_INDEX = {
2266
+ route: 1,
2267
+ layout: 0,
2268
+ index: 0
2269
+ };
2270
+ const extractStringFromExpression = (expression) => {
2271
+ if (expression.type === "Literal") {
2272
+ const literalValue = expression.value;
2273
+ return typeof literalValue === "string" ? literalValue : void 0;
2274
+ }
2275
+ if (expression.type === "TemplateLiteral") {
2276
+ const templateLiteral = expression;
2277
+ if (templateLiteral.expressions.length === 0 && templateLiteral.quasis.length === 1) return templateLiteral.quasis[0]?.value.cooked;
2278
+ }
2279
+ };
2280
+ const extractReactRouterRouteModuleEntries = (routesFilePath) => {
2281
+ const result = parseSync(routesFilePath, readFileSync(routesFilePath, "utf-8"));
2282
+ if (result.errors.length > 0 || !result.program?.body) return [];
2283
+ const modulePaths = [];
2284
+ const walkForRouteCalls = (node) => {
2285
+ if (node.type === "CallExpression") {
2286
+ const callExpression = node;
2287
+ const callee = callExpression.callee;
2288
+ if (callee.type === "Identifier") {
2289
+ const fileArgumentIndex = ROUTE_CALL_FILE_ARG_INDEX[callee.name];
2290
+ if (fileArgumentIndex !== void 0) {
2291
+ const fileArgument = callExpression.arguments[fileArgumentIndex];
2292
+ if (fileArgument && fileArgument.type !== "SpreadElement") {
2293
+ const filePath = extractStringFromExpression(fileArgument);
2294
+ if (filePath) modulePaths.push(filePath);
2295
+ }
2296
+ }
2297
+ }
2298
+ }
2299
+ for (const value of Object.values(node)) if (Array.isArray(value)) {
2300
+ for (const element of value) if (isWalkableNode(element)) walkForRouteCalls(element);
2301
+ } else if (isWalkableNode(value)) walkForRouteCalls(value);
2302
+ };
2303
+ for (const topLevelNode of result.program.body) if (isWalkableNode(topLevelNode)) walkForRouteCalls(topLevelNode);
2304
+ return modulePaths;
2305
+ };
2306
+
2307
+ //#endregion
2308
+ export { OUTPUT_DIRECTORIES as a, SCRIPT_ENTRY_PATTERNS as c, SOURCE_EXTENSIONS as d, STANDALONE_PROJECT_LOCKFILES as f, MONOREPO_ROOT_MARKERS as i, SCRIPT_EXTENSIONLESS_FILE_PATTERN as l, parseSourceFile as n, RESOLVER_EXTENSIONS as o, LOCKFILE_MARKERS as r, SCRIPT_CONFIG_FILE_PATTERN as s, extractReactRouterRouteModuleEntries as t, SCRIPT_FILE_PATTERN as u };