@openrewrite/rewrite 8.91.4 → 8.91.6

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.
Files changed (40) hide show
  1. package/dist/javascript/add-import.js +4 -6
  2. package/dist/javascript/add-import.js.map +1 -1
  3. package/dist/javascript/index.d.ts +1 -1
  4. package/dist/javascript/index.d.ts.map +1 -1
  5. package/dist/javascript/index.js +2 -1
  6. package/dist/javascript/index.js.map +1 -1
  7. package/dist/javascript/remove-import.d.ts +0 -18
  8. package/dist/javascript/remove-import.d.ts.map +1 -1
  9. package/dist/javascript/remove-import.js +50 -438
  10. package/dist/javascript/remove-import.js.map +1 -1
  11. package/dist/javascript/scope.d.ts +6 -0
  12. package/dist/javascript/scope.d.ts.map +1 -1
  13. package/dist/javascript/scope.js +87 -7
  14. package/dist/javascript/scope.js.map +1 -1
  15. package/dist/rewrite-javascript-version.txt +1 -1
  16. package/dist/rpc/index.d.ts +1 -1
  17. package/dist/rpc/index.d.ts.map +1 -1
  18. package/dist/rpc/index.js +2 -1
  19. package/dist/rpc/index.js.map +1 -1
  20. package/dist/rpc/java-recipe.d.ts +32 -22
  21. package/dist/rpc/java-recipe.d.ts.map +1 -1
  22. package/dist/rpc/java-recipe.js +91 -30
  23. package/dist/rpc/java-recipe.js.map +1 -1
  24. package/dist/rpc/request/prepare-recipe.d.ts +10 -14
  25. package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
  26. package/dist/rpc/request/prepare-recipe.js +53 -88
  27. package/dist/rpc/request/prepare-recipe.js.map +1 -1
  28. package/dist/rpc/rewrite-rpc.d.ts +5 -12
  29. package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
  30. package/dist/rpc/rewrite-rpc.js +5 -12
  31. package/dist/rpc/rewrite-rpc.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/javascript/add-import.ts +4 -6
  34. package/src/javascript/index.ts +1 -1
  35. package/src/javascript/remove-import.ts +50 -424
  36. package/src/javascript/scope.ts +103 -7
  37. package/src/rpc/index.ts +1 -1
  38. package/src/rpc/java-recipe.ts +107 -33
  39. package/src/rpc/request/prepare-recipe.ts +55 -90
  40. 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
- // First, collect all used identifiers in the file
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, usedIdentifiers, usedTypes, p);
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, usedIdentifiers, p);
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, usedIdentifiers, p);
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
- usedIdentifiers: Set<string>,
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, usedIdentifiers, usedTypes, p);
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 = !usedIdentifiers.has(name) && !usedTypes.has(name);
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, usedIdentifiers, usedTypes, 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 (!usedIdentifiers.has(name) && !usedTypes.has(name)) {
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, usedIdentifiers, usedTypes, 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 (!usedIdentifiers.has(aliasName) && !usedTypes.has(aliasName)) {
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, usedIdentifiers, usedTypes, 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, usedIdentifiers, usedTypes, p);
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
- usedIdentifiers: Set<string>,
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 || !this.isRequireCall(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, usedIdentifiers, usedTypes)) {
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, usedIdentifiers: Set<string>, usedTypes: Set<string>): boolean {
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 !usedIdentifiers.has(name) && !usedTypes.has(name);
407
+ return !usedNames.has(name);
429
408
  }
430
409
 
431
410
  private async processNamedImports(
432
411
  namedImports: JS.NamedImports,
433
- usedIdentifiers: Set<string>,
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 usedIdentifiers.has(nameToCheck) || usedTypes.has(nameToCheck);
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, usedIdentifiers, usedTypes, 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
- usedIdentifiers: Set<string>,
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 || !this.isRequireCall(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) && !usedIdentifiers.has(varName)) {
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, usedIdentifiers, p);
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
- usedIdentifiers: Set<string>,
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, usedIdentifiers, new Set(), b.member));
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
- usedIdentifiers: Set<string>,
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 !(usedIdentifiers.has(name) || usedTypes.has(name));
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
- private async collectUsedIdentifiers(
758
- node: J,
759
- usedIdentifiers: Set<string>,
760
- usedTypes: Set<string>
761
- ): Promise<void> {
762
- // This is a simplified version - in a real implementation,
763
- // we'd need to traverse the entire AST and collect all identifier usages
764
- // For now, we'll implement a basic traversal
765
-
766
- if (node.kind === J.Kind.Identifier) {
767
- const identifier = node as J.Identifier;
768
- usedIdentifiers.add(identifier.simpleName);
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
- private async collectTypeUsage(typeExpr: J, usedTypes: Set<string>): Promise<void> {
988
- if (typeExpr.kind === J.Kind.Identifier) {
989
- usedTypes.add((typeExpr as J.Identifier).simpleName);
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
  }