@openrewrite/rewrite 8.62.4 → 8.62.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 (110) hide show
  1. package/dist/java/tree.d.ts +12 -6
  2. package/dist/java/tree.d.ts.map +1 -1
  3. package/dist/java/tree.js +12 -5
  4. package/dist/java/tree.js.map +1 -1
  5. package/dist/java/type.d.ts +2 -0
  6. package/dist/java/type.d.ts.map +1 -1
  7. package/dist/java/type.js +12 -0
  8. package/dist/java/type.js.map +1 -1
  9. package/dist/java/visitor.d.ts +8 -0
  10. package/dist/java/visitor.d.ts.map +1 -1
  11. package/dist/java/visitor.js +23 -1
  12. package/dist/java/visitor.js.map +1 -1
  13. package/dist/javascript/index.d.ts +3 -0
  14. package/dist/javascript/index.d.ts.map +1 -1
  15. package/dist/javascript/index.js +3 -0
  16. package/dist/javascript/index.js.map +1 -1
  17. package/dist/javascript/method-matcher.d.ts +16 -0
  18. package/dist/javascript/method-matcher.d.ts.map +1 -0
  19. package/dist/javascript/method-matcher.js +222 -0
  20. package/dist/javascript/method-matcher.js.map +1 -0
  21. package/dist/javascript/parser.js +1 -1
  22. package/dist/javascript/parser.js.map +1 -1
  23. package/dist/javascript/preconditions.d.ts +6 -0
  24. package/dist/javascript/preconditions.d.ts.map +1 -0
  25. package/dist/javascript/preconditions.js +58 -0
  26. package/dist/javascript/preconditions.js.map +1 -0
  27. package/dist/javascript/remove-import.d.ts +56 -0
  28. package/dist/javascript/remove-import.d.ts.map +1 -0
  29. package/dist/javascript/remove-import.js +715 -0
  30. package/dist/javascript/remove-import.js.map +1 -0
  31. package/dist/javascript/rpc.js +2 -2
  32. package/dist/javascript/rpc.js.map +1 -1
  33. package/dist/javascript/search/index.d.ts +3 -0
  34. package/dist/javascript/search/index.d.ts.map +1 -0
  35. package/dist/javascript/search/index.js +19 -0
  36. package/dist/javascript/search/index.js.map +1 -0
  37. package/dist/javascript/search/uses-method.d.ts +8 -0
  38. package/dist/javascript/search/uses-method.d.ts.map +1 -0
  39. package/dist/javascript/search/uses-method.js +35 -0
  40. package/dist/javascript/search/uses-method.js.map +1 -0
  41. package/dist/javascript/search/uses-type.d.ts +8 -0
  42. package/dist/javascript/search/uses-type.d.ts.map +1 -0
  43. package/dist/javascript/search/uses-type.js +71 -0
  44. package/dist/javascript/search/uses-type.js.map +1 -0
  45. package/dist/javascript/templating.d.ts +1 -1
  46. package/dist/javascript/templating.d.ts.map +1 -1
  47. package/dist/javascript/templating.js +1 -1
  48. package/dist/javascript/templating.js.map +1 -1
  49. package/dist/javascript/tree.d.ts +3 -3
  50. package/dist/javascript/tree.d.ts.map +1 -1
  51. package/dist/javascript/tree.js +28 -0
  52. package/dist/javascript/tree.js.map +1 -1
  53. package/dist/javascript/type-mapping.d.ts +4 -0
  54. package/dist/javascript/type-mapping.d.ts.map +1 -1
  55. package/dist/javascript/type-mapping.js +92 -46
  56. package/dist/javascript/type-mapping.js.map +1 -1
  57. package/dist/javascript/visitor.js +1 -1
  58. package/dist/javascript/visitor.js.map +1 -1
  59. package/dist/print.d.ts +1 -0
  60. package/dist/print.d.ts.map +1 -1
  61. package/dist/print.js +6 -0
  62. package/dist/print.js.map +1 -1
  63. package/dist/rpc/rewrite-rpc.d.ts +1 -1
  64. package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
  65. package/dist/rpc/rewrite-rpc.js +0 -3
  66. package/dist/rpc/rewrite-rpc.js.map +1 -1
  67. package/dist/search/index.d.ts +2 -0
  68. package/dist/search/index.d.ts.map +1 -0
  69. package/dist/search/index.js +18 -0
  70. package/dist/search/index.js.map +1 -0
  71. package/dist/search/is-source-file.d.ts +8 -0
  72. package/dist/search/is-source-file.d.ts.map +1 -0
  73. package/dist/search/is-source-file.js +70 -0
  74. package/dist/search/is-source-file.js.map +1 -0
  75. package/dist/test/rewrite-test.d.ts.map +1 -1
  76. package/dist/test/rewrite-test.js +3 -0
  77. package/dist/test/rewrite-test.js.map +1 -1
  78. package/dist/util.d.ts +1 -0
  79. package/dist/util.d.ts.map +1 -1
  80. package/dist/util.js +13 -0
  81. package/dist/util.js.map +1 -1
  82. package/dist/version.txt +1 -1
  83. package/dist/visitor.d.ts +1 -1
  84. package/dist/visitor.d.ts.map +1 -1
  85. package/dist/visitor.js +3 -2
  86. package/dist/visitor.js.map +1 -1
  87. package/package.json +3 -1
  88. package/src/java/tree.ts +19 -11
  89. package/src/java/type.ts +14 -0
  90. package/src/java/visitor.ts +28 -8
  91. package/src/javascript/index.ts +4 -0
  92. package/src/javascript/method-matcher.ts +250 -0
  93. package/src/javascript/parser.ts +1 -1
  94. package/src/javascript/preconditions.ts +40 -0
  95. package/src/javascript/remove-import.ts +780 -0
  96. package/src/javascript/rpc.ts +2 -2
  97. package/src/javascript/search/index.ts +2 -0
  98. package/src/javascript/search/uses-method.ts +21 -0
  99. package/src/javascript/search/uses-type.ts +27 -0
  100. package/src/javascript/templating.ts +4 -3
  101. package/src/javascript/tree.ts +47 -3
  102. package/src/javascript/type-mapping.ts +113 -50
  103. package/src/javascript/visitor.ts +125 -125
  104. package/src/print.ts +9 -3
  105. package/src/rpc/rewrite-rpc.ts +1 -4
  106. package/src/search/index.ts +1 -0
  107. package/src/search/is-source-file.ts +26 -0
  108. package/src/test/rewrite-test.ts +4 -1
  109. package/src/util.ts +19 -4
  110. package/src/visitor.ts +3 -3
@@ -0,0 +1,780 @@
1
+ import {JavaScriptVisitor} from "./visitor";
2
+ import {J} from "../java";
3
+ import {JS} from "./tree";
4
+ import {mapAsync} from "../util";
5
+
6
+ /**
7
+ * @param visitor The visitor to add the import removal to
8
+ * @param target Either the module name (e.g., 'fs') to remove specific members from,
9
+ * or the name of the import to remove entirely
10
+ * @param member Optionally, the specific member to remove from the import.
11
+ * If not specified, removes the import matching `target`
12
+ */
13
+ export function maybeRemoveImport(visitor: JavaScriptVisitor<any>, target: string, member?: string) {
14
+ for (const v of visitor.afterVisit || []) {
15
+ if (v instanceof RemoveImport && v.target === target && v.member === member) {
16
+ return;
17
+ }
18
+ }
19
+ visitor.afterVisit.push(new RemoveImport(target, member));
20
+ }
21
+
22
+ // Type alias for RightPadded elements to simplify type signatures
23
+ type RightPaddedElement<T extends J> = {
24
+ element?: T;
25
+ after?: J.Space;
26
+ markers?: any;
27
+ kind?: any; // Add kind to match the RightPadded type structure
28
+ }
29
+
30
+ export class RemoveImport<P> extends JavaScriptVisitor<P> {
31
+ /**
32
+ * @param target Either the module name (e.g., 'fs') to remove specific members from,
33
+ * or the name of the import to remove entirely
34
+ * @param member Optionally, the specific member to remove from the import.
35
+ * If not specified, removes the import matching `target`
36
+ */
37
+ constructor(readonly target: string,
38
+ readonly member?: string) {
39
+ super();
40
+ }
41
+
42
+ /**
43
+ * Generic helper to filter elements from a RightPadded array while preserving formatting.
44
+ * When removing elements, the prefix from the first removed element is applied to the
45
+ * first remaining element to maintain proper spacing.
46
+ */
47
+ private async filterElementsWithPrefixPreservation<T extends J>(
48
+ elements: RightPaddedElement<T>[],
49
+ shouldKeep: (elem: T) => boolean,
50
+ updatePrefix: (elem: T, prefix: J.Space) => Promise<T>,
51
+ _p: P
52
+ ): Promise<{ filtered: RightPaddedElement<T>[], allRemoved: boolean }> {
53
+ const filtered: RightPaddedElement<T>[] = [];
54
+ let removedPrefix: J.Space | undefined;
55
+
56
+ for (const elem of elements) {
57
+ if (elem.element && shouldKeep(elem.element)) {
58
+ // If we removed the previous element and this is the first kept element,
59
+ // apply the removed element's prefix to maintain formatting
60
+ if (removedPrefix && filtered.length === 0) {
61
+ const updatedElement = await updatePrefix(elem.element, removedPrefix);
62
+ filtered.push({...elem, element: updatedElement});
63
+ removedPrefix = undefined;
64
+ } else {
65
+ filtered.push(elem);
66
+ }
67
+ } else if (elem.element) {
68
+ // Store the prefix of the first removed element
69
+ if (filtered.length === 0 && !removedPrefix) {
70
+ removedPrefix = elem.element.prefix;
71
+ }
72
+ } else {
73
+ // Keep non-element entries (shouldn't happen but be safe)
74
+ filtered.push(elem);
75
+ }
76
+ }
77
+
78
+ return {
79
+ filtered,
80
+ allRemoved: filtered.length === 0
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Helper to update an import clause by removing specific bindings
86
+ */
87
+ private async updateImportClause(
88
+ jsImport: JS.Import,
89
+ importClause: JS.ImportClause,
90
+ updateFn: (draft: any) => void,
91
+ p: P
92
+ ): Promise<JS.Import> {
93
+ return this.produceJavaScript<JS.Import>(jsImport, p, async draft => {
94
+ if (draft.importClause) {
95
+ draft.importClause = await this.produceJavaScript<JS.ImportClause>(
96
+ importClause, p, async (clauseDraft: any) => updateFn(clauseDraft)
97
+ );
98
+ }
99
+ });
100
+ }
101
+
102
+ override async visitJsCompilationUnit(compilationUnit: JS.CompilationUnit, p: P): Promise<J | undefined> {
103
+ // First, collect all used identifiers in the file
104
+ const usedIdentifiers = new Set<string>();
105
+ const usedTypes = new Set<string>();
106
+
107
+ // Traverse the AST to collect used identifiers
108
+ await this.collectUsedIdentifiers(compilationUnit, usedIdentifiers, usedTypes);
109
+
110
+
111
+ // Now process imports with knowledge of what's used
112
+ return this.produceJavaScript<JS.CompilationUnit>(compilationUnit, p, async draft => {
113
+ let nextStatementPrefix: J.Space | undefined;
114
+
115
+ draft.statements = await mapAsync(compilationUnit.statements, async (stmt, index) => {
116
+ const statement = stmt.element;
117
+
118
+ // Handle ES6 imports
119
+ if (statement?.kind === JS.Kind.Import) {
120
+ const jsImport = statement as JS.Import;
121
+ const result = await this.processImport(jsImport, usedIdentifiers, usedTypes, p);
122
+ if (result === undefined) {
123
+ // Store the prefix for the next statement to avoid leaving blank lines
124
+ if (index < compilationUnit.statements.length - 1) {
125
+ const nextStmt = compilationUnit.statements[index + 1];
126
+ if (nextStmt?.element) {
127
+ nextStatementPrefix = jsImport.prefix;
128
+ }
129
+ }
130
+ // Remove the entire import
131
+ return undefined;
132
+ }
133
+ return {...stmt, element: result};
134
+ }
135
+
136
+ // Handle CommonJS require statements
137
+ // Note: const fs = require() comes as J.VariableDeclarations, not ScopedVariableDeclarations
138
+ if (statement?.kind === J.Kind.VariableDeclarations) {
139
+ const varDecl = statement as J.VariableDeclarations;
140
+ const result = await this.processRequireFromVarDecls(varDecl, usedIdentifiers, p);
141
+ if (result === undefined) {
142
+ // Store the prefix for the next statement to avoid leaving blank lines
143
+ if (index < compilationUnit.statements.length - 1) {
144
+ const nextStmt = compilationUnit.statements[index + 1];
145
+ if (nextStmt?.element) {
146
+ nextStatementPrefix = varDecl.prefix;
147
+ }
148
+ }
149
+ // Remove the entire require statement
150
+ return undefined;
151
+ }
152
+ return {...stmt, element: result};
153
+ }
154
+
155
+ // Apply stored prefix if this statement follows a removed import
156
+ if (nextStatementPrefix && statement) {
157
+ const updatedStatement = await this.visit(statement, p) as J;
158
+ if (updatedStatement) {
159
+ (updatedStatement as any).prefix = nextStatementPrefix;
160
+ nextStatementPrefix = undefined;
161
+ return {...stmt, element: updatedStatement};
162
+ }
163
+ }
164
+
165
+ return stmt;
166
+ });
167
+
168
+ // Filter out undefined (removed) statements
169
+ draft.statements = draft.statements.filter(s => s !== undefined);
170
+ draft.eof = await this.visitSpace(compilationUnit.eof, p);
171
+ });
172
+ }
173
+
174
+ private async processImport(
175
+ jsImport: JS.Import,
176
+ usedIdentifiers: Set<string>,
177
+ usedTypes: Set<string>,
178
+ p: P
179
+ ): Promise<JS.Import | undefined> {
180
+ // Check if this import is from the target module
181
+ if (!this.isTargetModule(jsImport)) {
182
+ return jsImport;
183
+ }
184
+
185
+ const importClause = jsImport.importClause;
186
+ if (!importClause) {
187
+ // Side-effect import like: import 'module'
188
+ if (this.member === '*') {
189
+ return undefined; // Remove the entire import
190
+ }
191
+ return jsImport;
192
+ }
193
+
194
+ // Process default import
195
+ if (importClause.name) {
196
+ const defaultName = importClause.name.element;
197
+ if (defaultName && defaultName.kind === J.Kind.Identifier) {
198
+ const identifier = defaultName as J.Identifier;
199
+ const name = identifier.simpleName;
200
+
201
+ if (this.shouldRemoveImport(name, usedIdentifiers, usedTypes)) {
202
+ // If there are no named imports, remove the entire import
203
+ if (!importClause.namedBindings) {
204
+ return undefined;
205
+ }
206
+ // Otherwise, just remove the default import
207
+ return this.updateImportClause(jsImport, importClause, draft => {
208
+ draft.name = undefined;
209
+ }, p);
210
+ }
211
+ }
212
+ }
213
+
214
+ // Process named imports
215
+ if (importClause.namedBindings) {
216
+ const namedBindings = importClause.namedBindings;
217
+
218
+ // Handle namespace import: import * as X from 'module'
219
+ if (namedBindings.kind === J.Kind.Identifier) {
220
+ const identifier = namedBindings as J.Identifier;
221
+ const name = identifier.simpleName;
222
+
223
+ if (this.shouldRemoveImport(name, usedIdentifiers, usedTypes)) {
224
+ // If there's no default import, remove the entire import
225
+ if (!importClause.name) {
226
+ return undefined;
227
+ }
228
+ // Otherwise, just remove the namespace import
229
+ return this.updateImportClause(jsImport, importClause, draft => {
230
+ draft.namedBindings = undefined;
231
+ }, p);
232
+ }
233
+ } else if (namedBindings.kind === JS.Kind.Alias) {
234
+ // Handle import * as X from 'module' - represented as Alias with propertyName = "*"
235
+ const alias = namedBindings as JS.Alias;
236
+ const aliasName = (alias.alias as J.Identifier).simpleName;
237
+
238
+ if (this.shouldRemoveImport(aliasName, usedIdentifiers, usedTypes)) {
239
+ // If there's no default import, remove the entire import
240
+ if (!importClause.name) {
241
+ return undefined;
242
+ }
243
+ // Otherwise, just remove the namespace import
244
+ return this.updateImportClause(jsImport, importClause, draft => {
245
+ draft.namedBindings = undefined;
246
+ }, p);
247
+ }
248
+ }
249
+
250
+ // Handle named imports: import { a, b } from 'module'
251
+ if (namedBindings.kind === JS.Kind.NamedImports) {
252
+ const namedImports = namedBindings as JS.NamedImports;
253
+ const updatedImports = await this.processNamedImports(namedImports, usedIdentifiers, usedTypes, p);
254
+
255
+ if (updatedImports === undefined) {
256
+ // All named imports were removed
257
+ if (!importClause.name) {
258
+ // No default import either, remove the entire import
259
+ return undefined;
260
+ }
261
+ // Keep the import with just the default import
262
+ return this.updateImportClause(jsImport, importClause, draft => {
263
+ draft.namedBindings = undefined;
264
+ }, p);
265
+ } else if (updatedImports !== namedImports) {
266
+ // Some named imports were removed
267
+ return this.updateImportClause(jsImport, importClause, draft => {
268
+ draft.namedBindings = updatedImports;
269
+ }, p);
270
+ }
271
+ }
272
+ }
273
+
274
+ return jsImport;
275
+ }
276
+
277
+ private async processNamedImports(
278
+ namedImports: JS.NamedImports,
279
+ usedIdentifiers: Set<string>,
280
+ usedTypes: Set<string>,
281
+ p: P
282
+ ): Promise<JS.NamedImports | undefined> {
283
+ const {filtered, allRemoved} = await this.filterElementsWithPrefixPreservation(
284
+ namedImports.elements.elements,
285
+ (elem: J) => {
286
+ if (elem.kind === JS.Kind.ImportSpecifier) {
287
+ const importName = this.getImportName(elem as JS.ImportSpecifier);
288
+ return !this.shouldRemoveImport(importName, usedIdentifiers, usedTypes);
289
+ }
290
+ return true; // Keep non-ImportSpecifier elements
291
+ },
292
+ async (elem: J, prefix: J.Space) => {
293
+ if (elem.kind === JS.Kind.ImportSpecifier) {
294
+ return this.produceJavaScript<JS.ImportSpecifier>(
295
+ elem as JS.ImportSpecifier, p, async draft => {
296
+ draft.prefix = prefix;
297
+ }
298
+ );
299
+ }
300
+ return elem;
301
+ },
302
+ p
303
+ );
304
+
305
+ if (allRemoved) {
306
+ return undefined;
307
+ }
308
+
309
+ if (filtered.length === namedImports.elements.elements.length) {
310
+ return namedImports; // No changes
311
+ }
312
+
313
+ // Create updated named imports with filtered elements
314
+ return this.produceJavaScript<JS.NamedImports>(namedImports, p, async draft => {
315
+ draft.elements = {
316
+ ...namedImports.elements,
317
+ elements: filtered as any
318
+ };
319
+ });
320
+ }
321
+
322
+ private async processRequireFromVarDecls(
323
+ varDecls: J.VariableDeclarations,
324
+ usedIdentifiers: Set<string>,
325
+ p: P
326
+ ): Promise<J.VariableDeclarations | undefined> {
327
+ // Check if this is a require() call
328
+ if (varDecls.variables.length !== 1) {
329
+ return varDecls;
330
+ }
331
+
332
+ const namedVar = varDecls.variables[0].element;
333
+ if (!namedVar) {
334
+ return varDecls;
335
+ }
336
+
337
+ const initializer = namedVar.initializer?.element;
338
+ if (!initializer || initializer.kind !== J.Kind.MethodInvocation) {
339
+ return varDecls;
340
+ }
341
+
342
+ const methodInv = initializer as J.MethodInvocation;
343
+ if (methodInv.name?.kind !== J.Kind.Identifier || (methodInv.name as J.Identifier).simpleName !== 'require') {
344
+ return varDecls;
345
+ }
346
+
347
+ // This is a require() statement
348
+ const pattern = namedVar.name;
349
+ if (!pattern) {
350
+ return varDecls;
351
+ }
352
+
353
+ // Handle: const fs = require('fs')
354
+ if (pattern.kind === J.Kind.Identifier) {
355
+ const varName = (pattern as J.Identifier).simpleName;
356
+ if (this.shouldRemoveImport(varName, usedIdentifiers, new Set())) {
357
+ return undefined; // Remove the entire require statement
358
+ }
359
+ }
360
+
361
+ // Handle: const { readFile } = require('fs')
362
+ if (pattern.kind === JS.Kind.ObjectBindingPattern && this.member !== undefined) {
363
+ const objectPattern = pattern as JS.ObjectBindingPattern;
364
+ const updatedPattern = await this.processObjectBindingPattern(objectPattern, usedIdentifiers, p);
365
+
366
+ if (updatedPattern === undefined) {
367
+ return undefined; // Remove entire require
368
+ } else if (updatedPattern !== objectPattern) {
369
+ // Update with filtered bindings
370
+ return this.produceJava<J.VariableDeclarations>(varDecls, p, async draft => {
371
+ const updatedNamedVar = await this.produceJava<J.VariableDeclarations.NamedVariable>(
372
+ namedVar, p, async namedDraft => {
373
+ namedDraft.name = updatedPattern;
374
+ }
375
+ );
376
+ draft.variables = [{...varDecls.variables[0], element: updatedNamedVar}];
377
+ });
378
+ }
379
+ }
380
+
381
+ return varDecls;
382
+ }
383
+
384
+ private async processObjectBindingPattern(
385
+ pattern: JS.ObjectBindingPattern,
386
+ usedIdentifiers: Set<string>,
387
+ p: P
388
+ ): Promise<JS.ObjectBindingPattern | undefined> {
389
+ const {filtered, allRemoved} = await this.filterElementsWithPrefixPreservation(
390
+ pattern.bindings.elements,
391
+ (elem: J) => {
392
+ if (elem.kind === JS.Kind.BindingElement) {
393
+ const name = this.getBindingElementName(elem as JS.BindingElement);
394
+ return !this.shouldRemoveImport(name, usedIdentifiers, new Set());
395
+ } else if (elem.kind === J.Kind.Identifier) {
396
+ const name = (elem as J.Identifier).simpleName;
397
+ return !this.shouldRemoveImport(name, usedIdentifiers, new Set());
398
+ }
399
+ return true; // Keep other element types
400
+ },
401
+ async (elem: J, prefix: J.Space) => {
402
+ if (elem.kind === J.Kind.Identifier) {
403
+ return this.produceJava<J.Identifier>(
404
+ elem as J.Identifier, p, async draft => {
405
+ draft.prefix = prefix;
406
+ }
407
+ );
408
+ } else if (elem.kind === JS.Kind.BindingElement) {
409
+ return this.produceJavaScript<JS.BindingElement>(
410
+ elem as JS.BindingElement, p, async draft => {
411
+ draft.prefix = prefix;
412
+ }
413
+ );
414
+ }
415
+ return elem;
416
+ },
417
+ p
418
+ );
419
+
420
+ if (allRemoved) {
421
+ return undefined;
422
+ }
423
+
424
+ if (filtered.length === pattern.bindings.elements.length) {
425
+ return pattern;
426
+ }
427
+
428
+ return this.produceJavaScript<JS.ObjectBindingPattern>(pattern, p, async draft => {
429
+ draft.bindings = {
430
+ ...pattern.bindings,
431
+ elements: filtered as any
432
+ };
433
+ });
434
+ }
435
+
436
+ private getImportName(specifier: JS.ImportSpecifier): string {
437
+ const spec = specifier.specifier;
438
+ if (spec?.kind === JS.Kind.Alias) {
439
+ // Handle aliased import: import { foo as bar }
440
+ const alias = spec as JS.Alias;
441
+ const propertyName = alias.propertyName.element;
442
+ if (propertyName?.kind === J.Kind.Identifier) {
443
+ return (propertyName as J.Identifier).simpleName;
444
+ }
445
+ } else if (spec?.kind === J.Kind.Identifier) {
446
+ // Handle regular import: import { foo }
447
+ return (spec as J.Identifier).simpleName;
448
+ }
449
+ return '';
450
+ }
451
+
452
+ private getBindingElementName(bindingElement: JS.BindingElement): string {
453
+ const name = bindingElement.name;
454
+ if (name?.kind === J.Kind.Identifier) {
455
+ return (name as J.Identifier).simpleName;
456
+ }
457
+ return '';
458
+ }
459
+
460
+ private shouldRemoveImport(
461
+ name: string,
462
+ usedIdentifiers: Set<string>,
463
+ usedTypes: Set<string>
464
+ ): boolean {
465
+ // If member is specified, we're removing a specific member from a module
466
+ if (this.member !== undefined) {
467
+ // Only remove if this is the specific member we're looking for
468
+ if (this.member !== name) {
469
+ return false;
470
+ }
471
+ } else {
472
+ // If no member specified, we're removing based on the import name itself
473
+ if (this.target !== name) {
474
+ return false;
475
+ }
476
+ }
477
+
478
+ // Check if it's used
479
+ return !(usedIdentifiers.has(name) || usedTypes.has(name));
480
+ }
481
+
482
+ private isTargetModule(jsImport: JS.Import): boolean {
483
+ // If member is specified, we're looking for imports from a specific module
484
+ if (this.member !== undefined) {
485
+ const moduleSpecifier = jsImport.moduleSpecifier?.element;
486
+ if (!moduleSpecifier || moduleSpecifier.kind !== J.Kind.Literal) {
487
+ return false;
488
+ }
489
+
490
+ const literal = moduleSpecifier as J.Literal;
491
+ const moduleName = literal.value?.toString().replace(/['"`]/g, '');
492
+
493
+ // Match the module name
494
+ return moduleName === this.target;
495
+ }
496
+
497
+ // If no member specified, we process all imports to check their names
498
+ return true;
499
+ }
500
+
501
+ /**
502
+ * Helper to traverse parameters from various node types
503
+ */
504
+ private async traverseParameters(
505
+ params: any,
506
+ usedIdentifiers: Set<string>,
507
+ usedTypes: Set<string>
508
+ ): Promise<void> {
509
+ if (!params || typeof params !== 'object') return;
510
+
511
+ if (Array.isArray(params)) {
512
+ for (const p of params) {
513
+ await this.collectUsedIdentifiers(p, usedIdentifiers, usedTypes);
514
+ }
515
+ } else if (params.elements) {
516
+ for (const p of params.elements) {
517
+ if (p.element) {
518
+ await this.collectUsedIdentifiers(p.element, usedIdentifiers, usedTypes);
519
+ }
520
+ }
521
+ } else if (params.parameters) {
522
+ for (const p of params.parameters) {
523
+ const elem = p.element || p;
524
+ await this.collectUsedIdentifiers(elem, usedIdentifiers, usedTypes);
525
+ }
526
+ }
527
+ }
528
+
529
+ /**
530
+ * Helper to traverse statements from various node types
531
+ */
532
+ private async traverseStatements(
533
+ statements: any,
534
+ usedIdentifiers: Set<string>,
535
+ usedTypes: Set<string>
536
+ ): Promise<void> {
537
+ if (!statements) return;
538
+
539
+ if (Array.isArray(statements)) {
540
+ for (const stmt of statements) {
541
+ const element = stmt.element || stmt;
542
+ if (element) {
543
+ await this.collectUsedIdentifiers(element, usedIdentifiers, usedTypes);
544
+ }
545
+ }
546
+ }
547
+ }
548
+
549
+ /**
550
+ * Helper to check for type expressions and collect type usage
551
+ */
552
+ private async checkTypeExpression(
553
+ node: any,
554
+ usedTypes: Set<string>
555
+ ): Promise<void> {
556
+ if (node.typeExpression) {
557
+ await this.collectTypeUsage(node.typeExpression, usedTypes);
558
+ }
559
+ }
560
+
561
+ private async collectUsedIdentifiers(
562
+ node: J,
563
+ usedIdentifiers: Set<string>,
564
+ usedTypes: Set<string>
565
+ ): Promise<void> {
566
+ // This is a simplified version - in a real implementation,
567
+ // we'd need to traverse the entire AST and collect all identifier usages
568
+ // For now, we'll implement a basic traversal
569
+
570
+ if (node.kind === J.Kind.Identifier) {
571
+ const identifier = node as J.Identifier;
572
+ usedIdentifiers.add(identifier.simpleName);
573
+ } else if (node.kind === J.Kind.MethodInvocation) {
574
+ const methodInv = node as J.MethodInvocation;
575
+
576
+ // Check if this is a member access pattern like fs.readFileSync
577
+ if (methodInv.select?.element?.kind === J.Kind.FieldAccess) {
578
+ const fieldAccess = methodInv.select.element as J.FieldAccess;
579
+ if (fieldAccess.target?.kind === J.Kind.Identifier) {
580
+ usedIdentifiers.add((fieldAccess.target as J.Identifier).simpleName);
581
+ }
582
+ } else if (methodInv.select?.element?.kind === J.Kind.Identifier) {
583
+ // Direct identifier like fs in fs.method() - though this is rare
584
+ usedIdentifiers.add((methodInv.select.element as J.Identifier).simpleName);
585
+ }
586
+
587
+ // Collect method name
588
+ if (methodInv.name && methodInv.name.kind === J.Kind.Identifier) {
589
+ usedIdentifiers.add((methodInv.name as J.Identifier).simpleName);
590
+ }
591
+ // Recursively check arguments
592
+ if (methodInv.arguments) {
593
+ for (const arg of methodInv.arguments.elements) {
594
+ if (arg.element) {
595
+ await this.collectUsedIdentifiers(arg.element, usedIdentifiers, usedTypes);
596
+ }
597
+ }
598
+ }
599
+ // Check select (object being called on)
600
+ if (methodInv.select?.element) {
601
+ await this.collectUsedIdentifiers(methodInv.select.element, usedIdentifiers, usedTypes);
602
+ }
603
+ } else if (node.kind === J.Kind.MemberReference) {
604
+ const memberRef = node as J.MemberReference;
605
+ if (memberRef.containing && memberRef.containing.element?.kind === J.Kind.Identifier) {
606
+ usedIdentifiers.add((memberRef.containing.element as J.Identifier).simpleName);
607
+ }
608
+ } else if (node.kind === J.Kind.FieldAccess) {
609
+ // Handle field access like fs.readFileSync
610
+ const fieldAccess = node as J.FieldAccess;
611
+ if (fieldAccess.target?.kind === J.Kind.Identifier) {
612
+ usedIdentifiers.add((fieldAccess.target as J.Identifier).simpleName);
613
+ }
614
+ // Recursively check the target
615
+ if (fieldAccess.target) {
616
+ await this.collectUsedIdentifiers(fieldAccess.target, usedIdentifiers, usedTypes);
617
+ }
618
+ } else if (node.kind === JS.Kind.CompilationUnit) {
619
+ const cu = node as JS.CompilationUnit;
620
+ for (const stmt of cu.statements) {
621
+ if (stmt.element && stmt.element.kind !== JS.Kind.Import) {
622
+ // Skip require() statements at the top level
623
+ if (stmt.element.kind === J.Kind.VariableDeclarations) {
624
+ const varDecls = stmt.element as J.VariableDeclarations;
625
+ // Check if this is a require() statement
626
+ let isRequire = false;
627
+ for (const v of varDecls.variables) {
628
+ const namedVar = v.element;
629
+ if (namedVar?.initializer?.element?.kind === J.Kind.MethodInvocation) {
630
+ const methodInv = namedVar.initializer.element as J.MethodInvocation;
631
+ if (methodInv.name?.kind === J.Kind.Identifier &&
632
+ (methodInv.name as J.Identifier).simpleName === 'require') {
633
+ isRequire = true;
634
+ break;
635
+ }
636
+ }
637
+ }
638
+ if (!isRequire) {
639
+ // Not a require statement, process normally
640
+ await this.collectUsedIdentifiers(stmt.element, usedIdentifiers, usedTypes);
641
+ }
642
+ } else if (stmt.element.kind !== JS.Kind.ScopedVariableDeclarations) {
643
+ // Process other non-import, non-require statements normally
644
+ await this.collectUsedIdentifiers(stmt.element, usedIdentifiers, usedTypes);
645
+ }
646
+ }
647
+ }
648
+ } else if (node.kind === J.Kind.Return) {
649
+ // Handle return statements
650
+ const returnStmt = node as J.Return;
651
+ if (returnStmt.expression) {
652
+ await this.collectUsedIdentifiers(returnStmt.expression, usedIdentifiers, usedTypes);
653
+ }
654
+ } else if (node.kind === J.Kind.Block) {
655
+ const block = node as J.Block;
656
+ for (const stmt of block.statements) {
657
+ if (stmt.element) {
658
+ await this.collectUsedIdentifiers(stmt.element, usedIdentifiers, usedTypes);
659
+ }
660
+ }
661
+ } else if (node.kind === J.Kind.MethodDeclaration) {
662
+ const method = node as J.MethodDeclaration;
663
+ // Check parameters for type usage
664
+ if (method.parameters) {
665
+ for (const param of method.parameters.elements) {
666
+ // Parameters can be various types, handle them recursively
667
+ if (param.element) {
668
+ await this.collectUsedIdentifiers(param.element, usedIdentifiers, usedTypes);
669
+ }
670
+ }
671
+ }
672
+ // Check body
673
+ if (method.body) {
674
+ await this.collectUsedIdentifiers(method.body, usedIdentifiers, usedTypes);
675
+ }
676
+ } else if ((node as any).typeExpression) {
677
+ // Handle nodes with type expressions (parameters, variables, etc.)
678
+ await this.checkTypeExpression(node, usedTypes);
679
+ // Continue traversing other parts
680
+ if ((node as any).name) {
681
+ await this.collectUsedIdentifiers((node as any).name, usedIdentifiers, usedTypes);
682
+ }
683
+ if ((node as any).initializer) {
684
+ await this.collectUsedIdentifiers((node as any).initializer, usedIdentifiers, usedTypes);
685
+ }
686
+ } else if (node.kind === JS.Kind.ArrowFunction || node.kind === J.Kind.Lambda) {
687
+ // Handle arrow functions and lambdas
688
+ const func = node as any;
689
+ if (func.parameters) {
690
+ await this.collectUsedIdentifiers(func.parameters, usedIdentifiers, usedTypes);
691
+ }
692
+ if (func.lambda) {
693
+ await this.collectUsedIdentifiers(func.lambda, usedIdentifiers, usedTypes);
694
+ }
695
+ if (func.body) {
696
+ await this.collectUsedIdentifiers(func.body, usedIdentifiers, usedTypes);
697
+ }
698
+ } else if (node.kind === J.Kind.Lambda) {
699
+ const lambda = node as J.Lambda;
700
+ if (lambda.parameters?.parameters) {
701
+ for (const param of lambda.parameters.parameters) {
702
+ if (param.element) {
703
+ await this.collectUsedIdentifiers(param.element, usedIdentifiers, usedTypes);
704
+ }
705
+ }
706
+ }
707
+ if (lambda.body) {
708
+ await this.collectUsedIdentifiers(lambda.body, usedIdentifiers, usedTypes);
709
+ }
710
+ } else if (node.kind === J.Kind.VariableDeclarations) {
711
+ const varDecls = node as J.VariableDeclarations;
712
+ // Check the type expression on the VariableDeclarations itself
713
+ await this.checkTypeExpression(varDecls, usedTypes);
714
+ for (const v of varDecls.variables) {
715
+ const namedVar = v.element;
716
+ if (namedVar) {
717
+ // Check type annotation on the variable
718
+ await this.checkTypeExpression(namedVar, usedTypes);
719
+ // Check the variable name
720
+ if (namedVar.name) {
721
+ await this.collectUsedIdentifiers(namedVar.name, usedIdentifiers, usedTypes);
722
+ }
723
+ // Check the initializer
724
+ if (namedVar.initializer && namedVar.initializer.element) {
725
+ await this.collectUsedIdentifiers(namedVar.initializer.element, usedIdentifiers, usedTypes);
726
+ }
727
+ }
728
+ }
729
+ } else if ((node as any).statements) {
730
+ // Generic handler for nodes with statements
731
+ await this.traverseStatements((node as any).statements, usedIdentifiers, usedTypes);
732
+ } else if ((node as any).body) {
733
+ // Generic handler for nodes with body
734
+ await this.collectUsedIdentifiers((node as any).body, usedIdentifiers, usedTypes);
735
+ } else if ((node as any).parameters) {
736
+ // Handle anything with parameters (functions, methods, etc.)
737
+ await this.traverseParameters((node as any).parameters, usedIdentifiers, usedTypes);
738
+ // Continue with the body if it exists
739
+ if ((node as any).body) {
740
+ await this.collectUsedIdentifiers((node as any).body, usedIdentifiers, usedTypes);
741
+ }
742
+ }
743
+ }
744
+
745
+ private async collectTypeUsage(typeExpr: J, usedTypes: Set<string>): Promise<void> {
746
+ if (typeExpr.kind === J.Kind.Identifier) {
747
+ usedTypes.add((typeExpr as J.Identifier).simpleName);
748
+ } else if (typeExpr.kind === J.Kind.ParameterizedType) {
749
+ const paramType = typeExpr as J.ParameterizedType;
750
+ // In TypeScript AST, ParameterizedType might have a different structure
751
+ // We'll need to handle the type parameters appropriately
752
+ if (paramType.typeParameters) {
753
+ for (const typeParam of paramType.typeParameters.elements) {
754
+ if (typeParam.element) {
755
+ await this.collectTypeUsage(typeParam.element, usedTypes);
756
+ }
757
+ }
758
+ }
759
+ } else if (typeExpr.kind === JS.Kind.TypeTreeExpression) {
760
+ // Handle TypeTreeExpression which wraps type identifiers
761
+ const typeTree = typeExpr as JS.TypeTreeExpression;
762
+ if (typeTree.expression) {
763
+ await this.collectTypeUsage(typeTree.expression, usedTypes);
764
+ }
765
+ } else if (typeExpr.kind === JS.Kind.TypeInfo) {
766
+ // Handle TypeInfo which contains type identifiers
767
+ const typeInfo = typeExpr as JS.TypeInfo;
768
+ if (typeInfo.typeIdentifier) {
769
+ await this.collectTypeUsage(typeInfo.typeIdentifier, usedTypes);
770
+ }
771
+ } else if ((typeExpr as any).expression) {
772
+ // Generic handler for nodes with expression property
773
+ await this.collectTypeUsage((typeExpr as any).expression, usedTypes);
774
+ } else if ((typeExpr as any).typeIdentifier) {
775
+ // Generic handler for nodes with typeIdentifier property
776
+ await this.collectTypeUsage((typeExpr as any).typeIdentifier, usedTypes);
777
+ }
778
+ // Add more type expression handlers as needed
779
+ }
780
+ }