@openrewrite/rewrite 8.91.3 → 8.91.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/javascript/index.d.ts +1 -1
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +2 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/parser.d.ts +3 -2
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +154 -114
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/remove-import.d.ts +0 -18
- package/dist/javascript/remove-import.d.ts.map +1 -1
- package/dist/javascript/remove-import.js +50 -438
- package/dist/javascript/remove-import.js.map +1 -1
- package/dist/javascript/scope.d.ts +6 -0
- package/dist/javascript/scope.d.ts.map +1 -1
- package/dist/javascript/scope.js +87 -7
- package/dist/javascript/scope.js.map +1 -1
- package/dist/javascript/tsconfig.d.ts +32 -0
- package/dist/javascript/tsconfig.d.ts.map +1 -0
- package/dist/javascript/tsconfig.js +205 -0
- package/dist/javascript/tsconfig.js.map +1 -0
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/index.d.ts +1 -1
- package/dist/rpc/index.d.ts.map +1 -1
- package/dist/rpc/index.js +2 -1
- package/dist/rpc/index.js.map +1 -1
- package/dist/rpc/java-recipe.d.ts +32 -22
- package/dist/rpc/java-recipe.d.ts.map +1 -1
- package/dist/rpc/java-recipe.js +91 -30
- package/dist/rpc/java-recipe.js.map +1 -1
- package/dist/rpc/request/prepare-recipe.d.ts +10 -14
- package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
- package/dist/rpc/request/prepare-recipe.js +53 -88
- package/dist/rpc/request/prepare-recipe.js.map +1 -1
- package/dist/rpc/rewrite-rpc.d.ts +5 -12
- package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
- package/dist/rpc/rewrite-rpc.js +5 -12
- package/dist/rpc/rewrite-rpc.js.map +1 -1
- package/package.json +1 -1
- package/src/javascript/index.ts +1 -1
- package/src/javascript/parser.ts +104 -58
- package/src/javascript/remove-import.ts +50 -424
- package/src/javascript/scope.ts +103 -7
- package/src/javascript/tsconfig.ts +177 -0
- package/src/rpc/index.ts +1 -1
- package/src/rpc/java-recipe.ts +107 -33
- package/src/rpc/request/prepare-recipe.ts +55 -90
- package/src/rpc/rewrite-rpc.ts +5 -12
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {JavaScriptVisitor} from "./visitor";
|
|
2
2
|
import {J} from "../java";
|
|
3
|
-
import {bindingNames} from "./scope";
|
|
3
|
+
import {bindingNames, namesReferencedWithin} from "./scope";
|
|
4
4
|
import {JS, JSX} from "./tree";
|
|
5
5
|
import {mapAsync, updateIfChanged} from "../util";
|
|
6
6
|
import {ElementRemovalFormatter} from "../java";
|
|
@@ -101,14 +101,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
override async visitJsCompilationUnit(compilationUnit: JS.CompilationUnit, p: P): Promise<J | undefined> {
|
|
104
|
-
|
|
105
|
-
const usedIdentifiers = new Set<string>();
|
|
106
|
-
const usedTypes = new Set<string>();
|
|
107
|
-
|
|
108
|
-
// Traverse the AST to collect used identifiers
|
|
109
|
-
await this.collectUsedIdentifiers(compilationUnit, usedIdentifiers, usedTypes);
|
|
110
|
-
|
|
111
|
-
// Now process imports with knowledge of what's used
|
|
104
|
+
const usedNames = collectUsedNames(compilationUnit);
|
|
112
105
|
return this.produceJavaScript(compilationUnit, p, async draft => {
|
|
113
106
|
const formatter = new ElementRemovalFormatter<J>(true); // Preserve file headers from first import
|
|
114
107
|
|
|
@@ -118,7 +111,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
118
111
|
// Handle ES6 imports
|
|
119
112
|
if (statement?.kind === JS.Kind.Import) {
|
|
120
113
|
const jsImport = statement as JS.Import;
|
|
121
|
-
const result = await this.processImport(jsImport,
|
|
114
|
+
const result = await this.processImport(jsImport, usedNames, p);
|
|
122
115
|
if (result === undefined) {
|
|
123
116
|
formatter.markRemoved(statement);
|
|
124
117
|
return undefined;
|
|
@@ -133,7 +126,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
133
126
|
// Multi-variable declarations might come as JS.ScopedVariableDeclarations
|
|
134
127
|
if (statement?.kind === J.Kind.VariableDeclarations) {
|
|
135
128
|
const varDecl = statement as J.VariableDeclarations;
|
|
136
|
-
const result = await this.processRequireFromVarDecls(varDecl,
|
|
129
|
+
const result = await this.processRequireFromVarDecls(varDecl, usedNames, p);
|
|
137
130
|
if (result === undefined) {
|
|
138
131
|
formatter.markRemoved(statement);
|
|
139
132
|
return undefined;
|
|
@@ -154,7 +147,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
154
147
|
for (const v of scopedVarDecl.variables) {
|
|
155
148
|
const varDecl = v.element;
|
|
156
149
|
if (varDecl?.kind === J.Kind.VariableDeclarations) {
|
|
157
|
-
const result = await this.processRequireFromVarDecls(varDecl as J.VariableDeclarations,
|
|
150
|
+
const result = await this.processRequireFromVarDecls(varDecl as J.VariableDeclarations, usedNames, p);
|
|
158
151
|
if (result === undefined) {
|
|
159
152
|
hasChanges = true;
|
|
160
153
|
varFormatter.markRemoved(varDecl);
|
|
@@ -197,13 +190,12 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
197
190
|
|
|
198
191
|
private async processImport(
|
|
199
192
|
jsImport: JS.Import,
|
|
200
|
-
|
|
201
|
-
usedTypes: Set<string>,
|
|
193
|
+
usedNames: Set<string>,
|
|
202
194
|
p: P
|
|
203
195
|
): Promise<JS.Import | undefined> {
|
|
204
196
|
// Handle import-equals-require syntax: import util = require("util");
|
|
205
197
|
if (jsImport.initializer) {
|
|
206
|
-
return this.processImportEqualsRequire(jsImport,
|
|
198
|
+
return this.processImportEqualsRequire(jsImport, usedNames, p);
|
|
207
199
|
}
|
|
208
200
|
|
|
209
201
|
// Check if this import is from the target module
|
|
@@ -231,10 +223,10 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
231
223
|
let shouldRemove: boolean;
|
|
232
224
|
if (this.member === 'default') {
|
|
233
225
|
// Special case: member 'default' means remove any default import from the target module if unused
|
|
234
|
-
shouldRemove = !
|
|
226
|
+
shouldRemove = !usedNames.has(name);
|
|
235
227
|
} else {
|
|
236
228
|
// Regular case: check if the import name matches the removal criteria
|
|
237
|
-
shouldRemove = this.shouldRemoveImport(name,
|
|
229
|
+
shouldRemove = this.shouldRemoveImport(name, usedNames, name);
|
|
238
230
|
}
|
|
239
231
|
|
|
240
232
|
if (shouldRemove) {
|
|
@@ -281,7 +273,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
281
273
|
if (this.member !== undefined) {
|
|
282
274
|
// We're trying to remove a specific member from this namespace
|
|
283
275
|
// Check if the namespace itself is used
|
|
284
|
-
if (!
|
|
276
|
+
if (!usedNames.has(name)) {
|
|
285
277
|
// Namespace is not used, remove the entire import
|
|
286
278
|
if (!importClause.name) {
|
|
287
279
|
return undefined;
|
|
@@ -291,7 +283,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
291
283
|
}, p);
|
|
292
284
|
}
|
|
293
285
|
// Namespace is used, we can't remove individual members from it
|
|
294
|
-
} else if (this.shouldRemoveImport(name,
|
|
286
|
+
} else if (this.shouldRemoveImport(name, usedNames, name)) {
|
|
295
287
|
// If there's no default import, remove the entire import
|
|
296
288
|
if (!importClause.name) {
|
|
297
289
|
return undefined;
|
|
@@ -311,7 +303,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
311
303
|
if (this.member !== undefined) {
|
|
312
304
|
// We're trying to remove a specific member from this namespace
|
|
313
305
|
// Check if the namespace itself is used
|
|
314
|
-
if (!
|
|
306
|
+
if (!usedNames.has(aliasName)) {
|
|
315
307
|
// Namespace is not used, remove the entire import
|
|
316
308
|
if (!importClause.name) {
|
|
317
309
|
return undefined;
|
|
@@ -321,7 +313,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
321
313
|
}, p);
|
|
322
314
|
}
|
|
323
315
|
// Namespace is used, we can't remove individual members from it
|
|
324
|
-
} else if (this.shouldRemoveImport(aliasName,
|
|
316
|
+
} else if (this.shouldRemoveImport(aliasName, usedNames, aliasName)) {
|
|
325
317
|
// If there's no default import, remove the entire import
|
|
326
318
|
if (!importClause.name) {
|
|
327
319
|
return undefined;
|
|
@@ -336,7 +328,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
336
328
|
// Handle named imports: import { a, b } from 'module'
|
|
337
329
|
if (namedBindings.kind === JS.Kind.NamedImports) {
|
|
338
330
|
const namedImports = namedBindings as JS.NamedImports;
|
|
339
|
-
const updatedImports = await this.processNamedImports(namedImports,
|
|
331
|
+
const updatedImports = await this.processNamedImports(namedImports, usedNames, p);
|
|
340
332
|
|
|
341
333
|
if (updatedImports === undefined) {
|
|
342
334
|
// All named imports were removed
|
|
@@ -366,12 +358,11 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
366
358
|
*/
|
|
367
359
|
private async processImportEqualsRequire(
|
|
368
360
|
jsImport: JS.Import,
|
|
369
|
-
|
|
370
|
-
usedTypes: Set<string>,
|
|
361
|
+
usedNames: Set<string>,
|
|
371
362
|
p: P
|
|
372
363
|
): Promise<JS.Import | undefined> {
|
|
373
364
|
const initializer = jsImport.initializer?.element;
|
|
374
|
-
if (!initializer || !
|
|
365
|
+
if (!initializer || !isRequireCall(initializer)) {
|
|
375
366
|
return jsImport;
|
|
376
367
|
}
|
|
377
368
|
|
|
@@ -392,25 +383,13 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
392
383
|
|
|
393
384
|
// For import-equals-require, we can only remove the entire import since
|
|
394
385
|
// it imports the whole module as a single identifier
|
|
395
|
-
if (this.shouldRemoveIdentifier(importedName,
|
|
386
|
+
if (this.shouldRemoveIdentifier(importedName, usedNames)) {
|
|
396
387
|
return undefined;
|
|
397
388
|
}
|
|
398
389
|
|
|
399
390
|
return jsImport;
|
|
400
391
|
}
|
|
401
392
|
|
|
402
|
-
/**
|
|
403
|
-
* Check if a node is a require() method invocation
|
|
404
|
-
*/
|
|
405
|
-
private isRequireCall(node: J): boolean {
|
|
406
|
-
if (node.kind !== J.Kind.MethodInvocation) {
|
|
407
|
-
return false;
|
|
408
|
-
}
|
|
409
|
-
const methodInv = node as J.MethodInvocation;
|
|
410
|
-
return methodInv.name?.kind === J.Kind.Identifier &&
|
|
411
|
-
(methodInv.name as J.Identifier).simpleName === 'require';
|
|
412
|
-
}
|
|
413
|
-
|
|
414
393
|
/**
|
|
415
394
|
* Check if the module name matches the target module
|
|
416
395
|
*/
|
|
@@ -421,17 +400,16 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
421
400
|
/**
|
|
422
401
|
* Check if an identifier should be removed based on usage
|
|
423
402
|
*/
|
|
424
|
-
private shouldRemoveIdentifier(name: string,
|
|
403
|
+
private shouldRemoveIdentifier(name: string, usedNames: Set<string>): boolean {
|
|
425
404
|
// For CommonJS and import-equals-require, we're removing the entire import
|
|
426
405
|
// if the identifier is not used (member is typically undefined for these cases,
|
|
427
406
|
// or we're checking if a specific binding is used)
|
|
428
|
-
return !
|
|
407
|
+
return !usedNames.has(name);
|
|
429
408
|
}
|
|
430
409
|
|
|
431
410
|
private async processNamedImports(
|
|
432
411
|
namedImports: JS.NamedImports,
|
|
433
|
-
|
|
434
|
-
usedTypes: Set<string>,
|
|
412
|
+
usedNames: Set<string>,
|
|
435
413
|
p: P
|
|
436
414
|
): Promise<JS.NamedImports | undefined> {
|
|
437
415
|
const {filtered, allRemoved} = await this.filterElementsWithPrefixPreservation(
|
|
@@ -451,12 +429,12 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
451
429
|
// We're removing a specific member - check if this matches
|
|
452
430
|
if (this.member === importName) {
|
|
453
431
|
// This is the member we want to remove - check if it's used
|
|
454
|
-
return
|
|
432
|
+
return usedNames.has(nameToCheck);
|
|
455
433
|
}
|
|
456
434
|
return true; // Keep imports that don't match the member
|
|
457
435
|
} else {
|
|
458
436
|
// We're removing based on the import name itself
|
|
459
|
-
return !this.shouldRemoveImport(importName,
|
|
437
|
+
return !this.shouldRemoveImport(importName, usedNames, importName);
|
|
460
438
|
}
|
|
461
439
|
}
|
|
462
440
|
return true; // Keep non-ImportSpecifier elements
|
|
@@ -493,7 +471,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
493
471
|
|
|
494
472
|
private async processRequireFromVarDecls(
|
|
495
473
|
varDecls: J.VariableDeclarations,
|
|
496
|
-
|
|
474
|
+
usedNames: Set<string>,
|
|
497
475
|
p: P
|
|
498
476
|
): Promise<J.VariableDeclarations | undefined> {
|
|
499
477
|
// Check if this is a require() call
|
|
@@ -507,7 +485,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
507
485
|
}
|
|
508
486
|
|
|
509
487
|
const initializer = namedVar.initializer?.element;
|
|
510
|
-
if (!initializer || !
|
|
488
|
+
if (!initializer || !isRequireCall(initializer)) {
|
|
511
489
|
return varDecls;
|
|
512
490
|
}
|
|
513
491
|
|
|
@@ -525,7 +503,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
525
503
|
|
|
526
504
|
// For require() statements, check the module name from the require call
|
|
527
505
|
const moduleName = this.getModuleNameFromRequire(methodInv);
|
|
528
|
-
if (moduleName && this.matchesTargetModule(moduleName) && !
|
|
506
|
+
if (moduleName && this.matchesTargetModule(moduleName) && !usedNames.has(varName)) {
|
|
529
507
|
return undefined; // Remove the entire require statement
|
|
530
508
|
}
|
|
531
509
|
}
|
|
@@ -533,7 +511,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
533
511
|
// Handle: const { readFile } = require('fs')
|
|
534
512
|
if (pattern.kind === JS.Kind.ObjectBindingPattern && this.member !== undefined) {
|
|
535
513
|
const objectPattern = pattern as JS.ObjectBindingPattern;
|
|
536
|
-
const updatedPattern = await this.processObjectBindingPattern(objectPattern,
|
|
514
|
+
const updatedPattern = await this.processObjectBindingPattern(objectPattern, usedNames, p);
|
|
537
515
|
|
|
538
516
|
if (updatedPattern === undefined) {
|
|
539
517
|
return undefined; // Remove entire require
|
|
@@ -572,7 +550,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
572
550
|
|
|
573
551
|
private async processObjectBindingPattern(
|
|
574
552
|
pattern: JS.ObjectBindingPattern,
|
|
575
|
-
|
|
553
|
+
usedNames: Set<string>,
|
|
576
554
|
p: P
|
|
577
555
|
): Promise<JS.ObjectBindingPattern | undefined> {
|
|
578
556
|
const {filtered, allRemoved} = await this.filterElementsWithPrefixPreservation(
|
|
@@ -581,7 +559,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
581
559
|
const bound = this.boundByPatternElement(elem);
|
|
582
560
|
// An element whose names cannot be read is left alone.
|
|
583
561
|
return bound.length === 0 ||
|
|
584
|
-
bound.some(b => !this.shouldRemoveImport(b.name,
|
|
562
|
+
bound.some(b => !this.shouldRemoveImport(b.name, usedNames, b.member));
|
|
585
563
|
},
|
|
586
564
|
async (elem: J, prefix: J.Space) => {
|
|
587
565
|
if (elem.kind === J.Kind.Identifier) {
|
|
@@ -659,8 +637,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
659
637
|
|
|
660
638
|
private shouldRemoveImport(
|
|
661
639
|
name: string,
|
|
662
|
-
|
|
663
|
-
usedTypes: Set<string>,
|
|
640
|
+
usedNames: Set<string>,
|
|
664
641
|
member: string | undefined
|
|
665
642
|
): boolean {
|
|
666
643
|
// If member is specified, we're removing a specific member from the module
|
|
@@ -675,7 +652,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
675
652
|
// So we check if this particular import is unused
|
|
676
653
|
|
|
677
654
|
// Check if it's used
|
|
678
|
-
return !
|
|
655
|
+
return !usedNames.has(name);
|
|
679
656
|
}
|
|
680
657
|
|
|
681
658
|
private isTargetModule(jsImport: JS.Import): boolean {
|
|
@@ -692,379 +669,28 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
692
669
|
return moduleName === this.module;
|
|
693
670
|
}
|
|
694
671
|
|
|
695
|
-
|
|
696
|
-
* Helper to traverse parameters from various node types
|
|
697
|
-
*/
|
|
698
|
-
private async traverseParameters(
|
|
699
|
-
params: any,
|
|
700
|
-
usedIdentifiers: Set<string>,
|
|
701
|
-
usedTypes: Set<string>
|
|
702
|
-
): Promise<void> {
|
|
703
|
-
if (!params || typeof params !== 'object') return;
|
|
704
|
-
|
|
705
|
-
if (Array.isArray(params)) {
|
|
706
|
-
for (const p of params) {
|
|
707
|
-
// Array elements might be RightPadded, so unwrap them
|
|
708
|
-
const elem = (p as any).element || p;
|
|
709
|
-
await this.collectUsedIdentifiers(elem, usedIdentifiers, usedTypes);
|
|
710
|
-
}
|
|
711
|
-
} else if (params.elements) {
|
|
712
|
-
for (const p of params.elements) {
|
|
713
|
-
if (p.element) {
|
|
714
|
-
await this.collectUsedIdentifiers(p.element, usedIdentifiers, usedTypes);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
} else if (params.parameters) {
|
|
718
|
-
for (const p of params.parameters) {
|
|
719
|
-
const elem = p.element || p;
|
|
720
|
-
await this.collectUsedIdentifiers(elem, usedIdentifiers, usedTypes);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* Helper to traverse statements from various node types
|
|
727
|
-
*/
|
|
728
|
-
private async traverseStatements(
|
|
729
|
-
statements: any,
|
|
730
|
-
usedIdentifiers: Set<string>,
|
|
731
|
-
usedTypes: Set<string>
|
|
732
|
-
): Promise<void> {
|
|
733
|
-
if (!statements) return;
|
|
734
|
-
|
|
735
|
-
if (Array.isArray(statements)) {
|
|
736
|
-
for (const stmt of statements) {
|
|
737
|
-
const element = stmt.element || stmt;
|
|
738
|
-
if (element) {
|
|
739
|
-
await this.collectUsedIdentifiers(element, usedIdentifiers, usedTypes);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
* Helper to check for type expressions and collect type usage
|
|
747
|
-
*/
|
|
748
|
-
private async checkTypeExpression(
|
|
749
|
-
node: any,
|
|
750
|
-
usedTypes: Set<string>
|
|
751
|
-
): Promise<void> {
|
|
752
|
-
if (node.typeExpression) {
|
|
753
|
-
await this.collectTypeUsage(node.typeExpression, usedTypes);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
672
|
+
}
|
|
756
673
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
} else if (node.kind === J.Kind.VariableDeclarations) {
|
|
770
|
-
const varDecls = node as J.VariableDeclarations;
|
|
771
|
-
// Check the type expression on the VariableDeclarations itself
|
|
772
|
-
await this.checkTypeExpression(varDecls, usedTypes);
|
|
773
|
-
for (const v of varDecls.variables) {
|
|
774
|
-
// Check the initializer
|
|
775
|
-
if (v.element.initializer?.element) {
|
|
776
|
-
await this.collectUsedIdentifiers(v.element.initializer.element, usedIdentifiers, usedTypes);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
} else if (node.kind === J.Kind.MethodInvocation) {
|
|
780
|
-
const methodInv = node as J.MethodInvocation;
|
|
781
|
-
|
|
782
|
-
// Check if this is a member access pattern like fs.readFileSync
|
|
783
|
-
if (methodInv.select?.element?.kind === J.Kind.FieldAccess) {
|
|
784
|
-
const fieldAccess = methodInv.select.element as J.FieldAccess;
|
|
785
|
-
if (fieldAccess.target?.kind === J.Kind.Identifier) {
|
|
786
|
-
usedIdentifiers.add((fieldAccess.target as J.Identifier).simpleName);
|
|
787
|
-
}
|
|
788
|
-
} else if (methodInv.select?.element?.kind === J.Kind.Identifier) {
|
|
789
|
-
// Direct identifier like fs in fs.method() - though this is rare
|
|
790
|
-
usedIdentifiers.add((methodInv.select.element as J.Identifier).simpleName);
|
|
791
|
-
} else if (!methodInv.select) {
|
|
792
|
-
// No select means this is a direct function call like isArray()
|
|
793
|
-
// Only in this case should we add the method name as a used identifier
|
|
794
|
-
if (methodInv.name && methodInv.name.kind === J.Kind.Identifier) {
|
|
795
|
-
usedIdentifiers.add((methodInv.name as J.Identifier).simpleName);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
// Note: We don't add method names for calls like Array.isArray() or obj.method()
|
|
799
|
-
// because those are methods on objects, not standalone imported functions
|
|
800
|
-
// Recursively check arguments
|
|
801
|
-
if (methodInv.arguments) {
|
|
802
|
-
for (const arg of methodInv.arguments.elements) {
|
|
803
|
-
if (arg.element) {
|
|
804
|
-
await this.collectUsedIdentifiers(arg.element, usedIdentifiers, usedTypes);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
// Check select (object being called on)
|
|
809
|
-
if (methodInv.select?.element) {
|
|
810
|
-
await this.collectUsedIdentifiers(methodInv.select.element, usedIdentifiers, usedTypes);
|
|
811
|
-
}
|
|
812
|
-
} else if (node.kind === J.Kind.MemberReference) {
|
|
813
|
-
const memberRef = node as J.MemberReference;
|
|
814
|
-
if (memberRef.containing && memberRef.containing.element?.kind === J.Kind.Identifier) {
|
|
815
|
-
usedIdentifiers.add((memberRef.containing.element as J.Identifier).simpleName);
|
|
816
|
-
}
|
|
817
|
-
} else if (node.kind === J.Kind.FieldAccess) {
|
|
818
|
-
// Handle field access like fs.readFileSync
|
|
819
|
-
const fieldAccess = node as J.FieldAccess;
|
|
820
|
-
if (fieldAccess.target?.kind === J.Kind.Identifier) {
|
|
821
|
-
usedIdentifiers.add((fieldAccess.target as J.Identifier).simpleName);
|
|
822
|
-
}
|
|
823
|
-
// Recursively check the target
|
|
824
|
-
if (fieldAccess.target) {
|
|
825
|
-
await this.collectUsedIdentifiers(fieldAccess.target, usedIdentifiers, usedTypes);
|
|
826
|
-
}
|
|
827
|
-
} else if (node.kind === JS.Kind.CompilationUnit) {
|
|
828
|
-
const cu = node as JS.CompilationUnit;
|
|
829
|
-
for (const stmt of cu.statements) {
|
|
830
|
-
if (stmt.element && stmt.element.kind !== JS.Kind.Import) {
|
|
831
|
-
// Skip require() statements at the top level
|
|
832
|
-
if (stmt.element.kind === J.Kind.VariableDeclarations) {
|
|
833
|
-
const varDecls = stmt.element as J.VariableDeclarations;
|
|
834
|
-
// Check if this is a require() statement
|
|
835
|
-
let isRequire = false;
|
|
836
|
-
for (const v of varDecls.variables) {
|
|
837
|
-
const namedVar = v.element;
|
|
838
|
-
if (namedVar?.initializer?.element?.kind === J.Kind.MethodInvocation) {
|
|
839
|
-
const methodInv = namedVar.initializer.element as J.MethodInvocation;
|
|
840
|
-
if (methodInv.name?.kind === J.Kind.Identifier &&
|
|
841
|
-
(methodInv.name as J.Identifier).simpleName === 'require') {
|
|
842
|
-
isRequire = true;
|
|
843
|
-
break;
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
if (!isRequire) {
|
|
848
|
-
// Not a require statement, process normally
|
|
849
|
-
await this.collectUsedIdentifiers(stmt.element, usedIdentifiers, usedTypes);
|
|
850
|
-
}
|
|
851
|
-
} else if (stmt.element.kind !== JS.Kind.ScopedVariableDeclarations) {
|
|
852
|
-
// Process other non-import, non-require statements normally
|
|
853
|
-
await this.collectUsedIdentifiers(stmt.element, usedIdentifiers, usedTypes);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
} else if (node.kind === J.Kind.Return) {
|
|
858
|
-
// Handle return statements
|
|
859
|
-
const returnStmt = node as J.Return;
|
|
860
|
-
if (returnStmt.expression) {
|
|
861
|
-
await this.collectUsedIdentifiers(returnStmt.expression, usedIdentifiers, usedTypes);
|
|
862
|
-
}
|
|
863
|
-
} else if (node.kind === J.Kind.Block) {
|
|
864
|
-
const block = node as J.Block;
|
|
865
|
-
for (const stmt of block.statements) {
|
|
866
|
-
if (stmt.element) {
|
|
867
|
-
await this.collectUsedIdentifiers(stmt.element, usedIdentifiers, usedTypes);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
} else if (node.kind === J.Kind.MethodDeclaration) {
|
|
871
|
-
const method = node as J.MethodDeclaration;
|
|
872
|
-
// Check parameters for type usage
|
|
873
|
-
if (method.parameters) {
|
|
874
|
-
for (const param of method.parameters.elements) {
|
|
875
|
-
// Parameters can be various types, handle them recursively
|
|
876
|
-
if (param.element) {
|
|
877
|
-
await this.collectUsedIdentifiers(param.element, usedIdentifiers, usedTypes);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
// Check body
|
|
882
|
-
if (method.body) {
|
|
883
|
-
await this.collectUsedIdentifiers(method.body, usedIdentifiers, usedTypes);
|
|
884
|
-
}
|
|
885
|
-
} else if (node.kind === JS.Kind.TypeOf) {
|
|
886
|
-
// Handle typeof expressions like: typeof util
|
|
887
|
-
const typeOf = node as JS.TypeOf;
|
|
888
|
-
if (typeOf.expression) {
|
|
889
|
-
await this.collectUsedIdentifiers(typeOf.expression, usedIdentifiers, usedTypes);
|
|
890
|
-
}
|
|
891
|
-
} else if (node.kind === JS.Kind.TypeQuery) {
|
|
892
|
-
// Handle typeof type queries like: const x: typeof util
|
|
893
|
-
const typeQuery = node as JS.TypeQuery;
|
|
894
|
-
if (typeQuery.typeExpression) {
|
|
895
|
-
await this.collectUsedIdentifiers(typeQuery.typeExpression, usedIdentifiers, usedTypes);
|
|
896
|
-
}
|
|
897
|
-
} else if ((node as any).typeExpression) {
|
|
898
|
-
// Handle nodes with type expressions (parameters, variables, etc.)
|
|
899
|
-
await this.checkTypeExpression(node, usedTypes);
|
|
900
|
-
// Continue traversing other parts
|
|
901
|
-
if ((node as any).name) {
|
|
902
|
-
await this.collectUsedIdentifiers((node as any).name, usedIdentifiers, usedTypes);
|
|
903
|
-
}
|
|
904
|
-
if ((node as any).initializer) {
|
|
905
|
-
await this.collectUsedIdentifiers((node as any).initializer, usedIdentifiers, usedTypes);
|
|
906
|
-
}
|
|
907
|
-
} else if (node.kind === JS.Kind.ArrowFunction || node.kind === J.Kind.Lambda) {
|
|
908
|
-
// Handle arrow functions and lambdas
|
|
909
|
-
const func = node as any;
|
|
910
|
-
if (func.parameters) {
|
|
911
|
-
await this.collectUsedIdentifiers(func.parameters, usedIdentifiers, usedTypes);
|
|
912
|
-
}
|
|
913
|
-
if (func.lambda) {
|
|
914
|
-
await this.collectUsedIdentifiers(func.lambda, usedIdentifiers, usedTypes);
|
|
915
|
-
}
|
|
916
|
-
if (func.body) {
|
|
917
|
-
await this.collectUsedIdentifiers(func.body, usedIdentifiers, usedTypes);
|
|
918
|
-
}
|
|
919
|
-
} else if (node.kind === J.Kind.Lambda) {
|
|
920
|
-
const lambda = node as J.Lambda;
|
|
921
|
-
if (lambda.parameters?.parameters) {
|
|
922
|
-
for (const param of lambda.parameters.parameters) {
|
|
923
|
-
if (param.element) {
|
|
924
|
-
await this.collectUsedIdentifiers(param.element, usedIdentifiers, usedTypes);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
if (lambda.body) {
|
|
929
|
-
await this.collectUsedIdentifiers(lambda.body, usedIdentifiers, usedTypes);
|
|
930
|
-
}
|
|
931
|
-
} else if (node.kind === JS.Kind.JsxTag) {
|
|
932
|
-
// Handle JSX tags like <div>content</div>
|
|
933
|
-
const jsxTag = node as JSX.Tag;
|
|
934
|
-
// Check attributes
|
|
935
|
-
if (jsxTag.attributes) {
|
|
936
|
-
for (const attr of jsxTag.attributes) {
|
|
937
|
-
if (attr.element) {
|
|
938
|
-
await this.collectUsedIdentifiers(attr.element, usedIdentifiers, usedTypes);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
// Check children
|
|
943
|
-
if (jsxTag.children) {
|
|
944
|
-
for (const child of jsxTag.children) {
|
|
945
|
-
if (child) {
|
|
946
|
-
await this.collectUsedIdentifiers(child, usedIdentifiers, usedTypes);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
} else if (node.kind === JS.Kind.JsxEmbeddedExpression) {
|
|
951
|
-
// Handle JSX embedded expressions like {React.version}
|
|
952
|
-
const embedded = node as JSX.EmbeddedExpression;
|
|
953
|
-
// The expression is wrapped in RightPadded, so unwrap it
|
|
954
|
-
const expr = embedded.expression?.element || embedded.expression;
|
|
955
|
-
if (expr) {
|
|
956
|
-
await this.collectUsedIdentifiers(expr, usedIdentifiers, usedTypes);
|
|
957
|
-
}
|
|
958
|
-
} else if (node.kind === JS.Kind.JsxAttribute) {
|
|
959
|
-
// Handle JSX attributes like onClick={handler}
|
|
960
|
-
const jsxAttr = node as any;
|
|
961
|
-
if (jsxAttr.value) {
|
|
962
|
-
await this.collectUsedIdentifiers(jsxAttr.value, usedIdentifiers, usedTypes);
|
|
963
|
-
}
|
|
964
|
-
} else if (node.kind === JS.Kind.TypeDeclaration) {
|
|
965
|
-
// Handle type alias declarations like: type Props = { children: React.ReactNode }
|
|
966
|
-
const typeDecl = node as JS.TypeDeclaration;
|
|
967
|
-
// The initializer contains the type expression (the right side of the =)
|
|
968
|
-
if (typeDecl.initializer?.element) {
|
|
969
|
-
await this.collectTypeUsage(typeDecl.initializer.element, usedTypes);
|
|
970
|
-
}
|
|
971
|
-
} else if ((node as any).statements) {
|
|
972
|
-
// Generic handler for nodes with statements
|
|
973
|
-
await this.traverseStatements((node as any).statements, usedIdentifiers, usedTypes);
|
|
974
|
-
} else if ((node as any).body) {
|
|
975
|
-
// Generic handler for nodes with body
|
|
976
|
-
await this.collectUsedIdentifiers((node as any).body, usedIdentifiers, usedTypes);
|
|
977
|
-
} else if ((node as any).parameters) {
|
|
978
|
-
// Handle anything with parameters (functions, methods, etc.)
|
|
979
|
-
await this.traverseParameters((node as any).parameters, usedIdentifiers, usedTypes);
|
|
980
|
-
// Continue with the body if it exists
|
|
981
|
-
if ((node as any).body) {
|
|
982
|
-
await this.collectUsedIdentifiers((node as any).body, usedIdentifiers, usedTypes);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
674
|
+
/**
|
|
675
|
+
* The names the file references, less its own imports: a specifier names what it binds, so counting
|
|
676
|
+
* one would read an import as a use of itself.
|
|
677
|
+
*/
|
|
678
|
+
function collectUsedNames(cu: JS.CompilationUnit): Set<string> {
|
|
679
|
+
const used = new Set<string>();
|
|
680
|
+
for (const statement of cu.statements) {
|
|
681
|
+
const element = statement.element;
|
|
682
|
+
if (!element || element.kind === JS.Kind.Import) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
namesReferencedWithin(element).forEach(name => used.add(name));
|
|
985
686
|
}
|
|
687
|
+
return used;
|
|
688
|
+
}
|
|
986
689
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
} else if (typeExpr.kind === J.Kind.ParameterizedType) {
|
|
991
|
-
const paramType = typeExpr as J.ParameterizedType;
|
|
992
|
-
// First, collect usage from the base type (e.g., React.Ref from React.Ref<T>)
|
|
993
|
-
if (paramType.class) {
|
|
994
|
-
await this.collectTypeUsage(paramType.class, usedTypes);
|
|
995
|
-
}
|
|
996
|
-
// Then collect usage from type parameters (e.g., HTMLButtonElement from React.Ref<HTMLButtonElement>)
|
|
997
|
-
if (paramType.typeParameters) {
|
|
998
|
-
for (const typeParam of paramType.typeParameters.elements) {
|
|
999
|
-
if (typeParam.element) {
|
|
1000
|
-
await this.collectTypeUsage(typeParam.element, usedTypes);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
} else if (typeExpr.kind === J.Kind.FieldAccess) {
|
|
1005
|
-
// Handle qualified names in type positions like React.Ref
|
|
1006
|
-
const fieldAccess = typeExpr as J.FieldAccess;
|
|
1007
|
-
if (fieldAccess.target?.kind === J.Kind.Identifier) {
|
|
1008
|
-
usedTypes.add((fieldAccess.target as J.Identifier).simpleName);
|
|
1009
|
-
} else if (fieldAccess.target) {
|
|
1010
|
-
// Recursively handle nested field accesses
|
|
1011
|
-
await this.collectTypeUsage(fieldAccess.target, usedTypes);
|
|
1012
|
-
}
|
|
1013
|
-
} else if (typeExpr.kind === JS.Kind.Intersection) {
|
|
1014
|
-
// Handle intersection types like ButtonProps & { ref?: React.Ref<HTMLButtonElement> }
|
|
1015
|
-
const intersection = typeExpr as JS.Intersection;
|
|
1016
|
-
for (const typeElem of intersection.types) {
|
|
1017
|
-
if (typeElem.element) {
|
|
1018
|
-
await this.collectTypeUsage(typeElem.element, usedTypes);
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
} else if (typeExpr.kind === JS.Kind.TypeLiteral) {
|
|
1022
|
-
// Handle type literals like { ref?: React.Ref<HTMLButtonElement> }
|
|
1023
|
-
const typeLiteral = typeExpr as JS.TypeLiteral;
|
|
1024
|
-
// TypeLiteral members are in a Block, which contains statements
|
|
1025
|
-
for (const stmt of typeLiteral.members.statements) {
|
|
1026
|
-
if (stmt.element) {
|
|
1027
|
-
// Each statement is typically a VariableDeclarations representing a property
|
|
1028
|
-
await this.collectUsedIdentifiers(stmt.element, new Set(), usedTypes);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
} else if (typeExpr.kind === JS.Kind.TypeQuery) {
|
|
1032
|
-
// Handle typeof type queries like: const x: typeof util
|
|
1033
|
-
const typeQuery = typeExpr as JS.TypeQuery;
|
|
1034
|
-
if (typeQuery.typeExpression) {
|
|
1035
|
-
await this.collectTypeUsage(typeQuery.typeExpression, usedTypes);
|
|
1036
|
-
}
|
|
1037
|
-
} else if (typeExpr.kind === JS.Kind.TypeOf) {
|
|
1038
|
-
// Handle typeof expressions in types
|
|
1039
|
-
const typeOf = typeExpr as JS.TypeOf;
|
|
1040
|
-
if (typeOf.expression) {
|
|
1041
|
-
// For typeof expressions, the expression contains the identifier
|
|
1042
|
-
// Add it to usedTypes since it's used in a type context
|
|
1043
|
-
if (typeOf.expression.kind === J.Kind.Identifier) {
|
|
1044
|
-
usedTypes.add((typeOf.expression as J.Identifier).simpleName);
|
|
1045
|
-
} else {
|
|
1046
|
-
await this.collectTypeUsage(typeOf.expression, usedTypes);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
} else if (typeExpr.kind === JS.Kind.TypeTreeExpression) {
|
|
1050
|
-
// Handle TypeTreeExpression which wraps type identifiers
|
|
1051
|
-
const typeTree = typeExpr as JS.TypeTreeExpression;
|
|
1052
|
-
if (typeTree.expression) {
|
|
1053
|
-
await this.collectTypeUsage(typeTree.expression, usedTypes);
|
|
1054
|
-
}
|
|
1055
|
-
} else if (typeExpr.kind === JS.Kind.TypeInfo) {
|
|
1056
|
-
// Handle TypeInfo which contains type identifiers
|
|
1057
|
-
const typeInfo = typeExpr as JS.TypeInfo;
|
|
1058
|
-
if (typeInfo.typeIdentifier) {
|
|
1059
|
-
await this.collectTypeUsage(typeInfo.typeIdentifier, usedTypes);
|
|
1060
|
-
}
|
|
1061
|
-
} else if ((typeExpr as any).expression) {
|
|
1062
|
-
// Generic handler for nodes with expression property
|
|
1063
|
-
await this.collectTypeUsage((typeExpr as any).expression, usedTypes);
|
|
1064
|
-
} else if ((typeExpr as any).typeIdentifier) {
|
|
1065
|
-
// Generic handler for nodes with typeIdentifier property
|
|
1066
|
-
await this.collectTypeUsage((typeExpr as any).typeIdentifier, usedTypes);
|
|
1067
|
-
}
|
|
1068
|
-
// Add more type expression handlers as needed
|
|
690
|
+
function isRequireCall(node: J): boolean {
|
|
691
|
+
if (node.kind !== J.Kind.MethodInvocation) {
|
|
692
|
+
return false;
|
|
1069
693
|
}
|
|
694
|
+
const name = (node as J.MethodInvocation).name;
|
|
695
|
+
return name?.kind === J.Kind.Identifier && (name as J.Identifier).simpleName === 'require';
|
|
1070
696
|
}
|