@openrewrite/rewrite 8.91.0 → 8.91.2

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 (153) hide show
  1. package/dist/java/formatting-utils.d.ts +8 -5
  2. package/dist/java/formatting-utils.d.ts.map +1 -1
  3. package/dist/java/formatting-utils.js +14 -10
  4. package/dist/java/formatting-utils.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 +5 -0
  8. package/dist/java/type.js.map +1 -1
  9. package/dist/javascript/add-import.d.ts +96 -43
  10. package/dist/javascript/add-import.d.ts.map +1 -1
  11. package/dist/javascript/add-import.js +668 -236
  12. package/dist/javascript/add-import.js.map +1 -1
  13. package/dist/javascript/amd.d.ts +138 -0
  14. package/dist/javascript/amd.d.ts.map +1 -0
  15. package/dist/javascript/amd.js +768 -0
  16. package/dist/javascript/amd.js.map +1 -0
  17. package/dist/javascript/assertions.d.ts +7 -0
  18. package/dist/javascript/assertions.d.ts.map +1 -1
  19. package/dist/javascript/assertions.js +31 -5
  20. package/dist/javascript/assertions.js.map +1 -1
  21. package/dist/javascript/binding.d.ts +94 -0
  22. package/dist/javascript/binding.d.ts.map +1 -0
  23. package/dist/javascript/binding.js +335 -0
  24. package/dist/javascript/binding.js.map +1 -0
  25. package/dist/javascript/comparator.d.ts +5 -0
  26. package/dist/javascript/comparator.d.ts.map +1 -1
  27. package/dist/javascript/comparator.js +34 -9
  28. package/dist/javascript/comparator.js.map +1 -1
  29. package/dist/javascript/format/format.d.ts.map +1 -1
  30. package/dist/javascript/format/format.js +8 -9
  31. package/dist/javascript/format/format.js.map +1 -1
  32. package/dist/javascript/format/tabs-and-indents-visitor.d.ts +2 -0
  33. package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -1
  34. package/dist/javascript/format/tabs-and-indents-visitor.js +17 -11
  35. package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
  36. package/dist/javascript/index.d.ts +8 -1
  37. package/dist/javascript/index.d.ts.map +1 -1
  38. package/dist/javascript/index.js +26 -1
  39. package/dist/javascript/index.js.map +1 -1
  40. package/dist/javascript/node-resolution-result.d.ts +19 -1
  41. package/dist/javascript/node-resolution-result.d.ts.map +1 -1
  42. package/dist/javascript/node-resolution-result.js +37 -2
  43. package/dist/javascript/node-resolution-result.js.map +1 -1
  44. package/dist/javascript/package-exported-types.d.ts.map +1 -1
  45. package/dist/javascript/package-exported-types.js +56 -72
  46. package/dist/javascript/package-exported-types.js.map +1 -1
  47. package/dist/javascript/parser-utils.d.ts.map +1 -1
  48. package/dist/javascript/parser-utils.js +2 -0
  49. package/dist/javascript/parser-utils.js.map +1 -1
  50. package/dist/javascript/parser.d.ts +7 -1
  51. package/dist/javascript/parser.d.ts.map +1 -1
  52. package/dist/javascript/parser.js +17 -4
  53. package/dist/javascript/parser.js.map +1 -1
  54. package/dist/javascript/recipes/change-import.d.ts +2 -4
  55. package/dist/javascript/recipes/change-import.d.ts.map +1 -1
  56. package/dist/javascript/recipes/change-import.js +8 -244
  57. package/dist/javascript/recipes/change-import.js.map +1 -1
  58. package/dist/javascript/remove-import.d.ts +2 -28
  59. package/dist/javascript/remove-import.d.ts.map +1 -1
  60. package/dist/javascript/remove-import.js +19 -57
  61. package/dist/javascript/remove-import.js.map +1 -1
  62. package/dist/javascript/scope.d.ts +49 -0
  63. package/dist/javascript/scope.d.ts.map +1 -0
  64. package/dist/javascript/scope.js +324 -0
  65. package/dist/javascript/scope.js.map +1 -0
  66. package/dist/javascript/style.d.ts.map +1 -1
  67. package/dist/javascript/style.js +3 -10
  68. package/dist/javascript/style.js.map +1 -1
  69. package/dist/javascript/templating/bindings.d.ts +8 -0
  70. package/dist/javascript/templating/bindings.d.ts.map +1 -0
  71. package/dist/javascript/templating/bindings.js +92 -0
  72. package/dist/javascript/templating/bindings.js.map +1 -0
  73. package/dist/javascript/templating/comparator.d.ts +10 -0
  74. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  75. package/dist/javascript/templating/comparator.js +48 -20
  76. package/dist/javascript/templating/comparator.js.map +1 -1
  77. package/dist/javascript/templating/engine.d.ts +34 -10
  78. package/dist/javascript/templating/engine.d.ts.map +1 -1
  79. package/dist/javascript/templating/engine.js +109 -55
  80. package/dist/javascript/templating/engine.js.map +1 -1
  81. package/dist/javascript/templating/index.d.ts +1 -1
  82. package/dist/javascript/templating/index.d.ts.map +1 -1
  83. package/dist/javascript/templating/index.js.map +1 -1
  84. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  85. package/dist/javascript/templating/pattern.js +13 -10
  86. package/dist/javascript/templating/pattern.js.map +1 -1
  87. package/dist/javascript/templating/placeholder-replacement.d.ts +4 -0
  88. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  89. package/dist/javascript/templating/placeholder-replacement.js +24 -10
  90. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  91. package/dist/javascript/templating/precedence.d.ts +12 -4
  92. package/dist/javascript/templating/precedence.d.ts.map +1 -1
  93. package/dist/javascript/templating/precedence.js +18 -11
  94. package/dist/javascript/templating/precedence.js.map +1 -1
  95. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  96. package/dist/javascript/templating/rewrite.js +19 -17
  97. package/dist/javascript/templating/rewrite.js.map +1 -1
  98. package/dist/javascript/templating/template.d.ts +14 -0
  99. package/dist/javascript/templating/template.d.ts.map +1 -1
  100. package/dist/javascript/templating/template.js +63 -3
  101. package/dist/javascript/templating/template.js.map +1 -1
  102. package/dist/javascript/templating/types.d.ts +39 -1
  103. package/dist/javascript/templating/types.d.ts.map +1 -1
  104. package/dist/javascript/templating/utils.d.ts +6 -1
  105. package/dist/javascript/templating/utils.d.ts.map +1 -1
  106. package/dist/javascript/templating/utils.js +20 -2
  107. package/dist/javascript/templating/utils.js.map +1 -1
  108. package/dist/javascript/type-mapping.d.ts +1 -0
  109. package/dist/javascript/type-mapping.d.ts.map +1 -1
  110. package/dist/javascript/type-mapping.js +52 -2
  111. package/dist/javascript/type-mapping.js.map +1 -1
  112. package/dist/rewrite-javascript-version.txt +1 -1
  113. package/dist/style.d.ts +2 -0
  114. package/dist/style.d.ts.map +1 -1
  115. package/dist/style.js +5 -0
  116. package/dist/style.js.map +1 -1
  117. package/dist/test/rewrite-test.d.ts +12 -0
  118. package/dist/test/rewrite-test.d.ts.map +1 -1
  119. package/dist/test/rewrite-test.js +40 -3
  120. package/dist/test/rewrite-test.js.map +1 -1
  121. package/package.json +2 -2
  122. package/src/java/formatting-utils.ts +14 -10
  123. package/src/java/type.ts +5 -0
  124. package/src/javascript/add-import.ts +751 -247
  125. package/src/javascript/amd.ts +915 -0
  126. package/src/javascript/assertions.ts +33 -5
  127. package/src/javascript/binding.ts +449 -0
  128. package/src/javascript/comparator.ts +39 -10
  129. package/src/javascript/format/format.ts +9 -10
  130. package/src/javascript/format/tabs-and-indents-visitor.ts +18 -12
  131. package/src/javascript/index.ts +12 -1
  132. package/src/javascript/node-resolution-result.ts +59 -4
  133. package/src/javascript/package-exported-types.ts +60 -74
  134. package/src/javascript/parser-utils.ts +2 -0
  135. package/src/javascript/parser.ts +23 -3
  136. package/src/javascript/recipes/change-import.ts +10 -263
  137. package/src/javascript/remove-import.ts +20 -56
  138. package/src/javascript/scope.ts +338 -0
  139. package/src/javascript/style.ts +3 -11
  140. package/src/javascript/templating/bindings.ts +99 -0
  141. package/src/javascript/templating/comparator.ts +57 -20
  142. package/src/javascript/templating/engine.ts +148 -68
  143. package/src/javascript/templating/index.ts +1 -0
  144. package/src/javascript/templating/pattern.ts +19 -16
  145. package/src/javascript/templating/placeholder-replacement.ts +32 -11
  146. package/src/javascript/templating/precedence.ts +21 -13
  147. package/src/javascript/templating/rewrite.ts +23 -17
  148. package/src/javascript/templating/template.ts +84 -9
  149. package/src/javascript/templating/types.ts +46 -1
  150. package/src/javascript/templating/utils.ts +21 -2
  151. package/src/javascript/type-mapping.ts +54 -2
  152. package/src/style.ts +5 -0
  153. package/src/test/rewrite-test.ts +61 -3
@@ -18,9 +18,9 @@ import { Option, Recipe } from "../../recipe";
18
18
  import { TreeVisitor } from "../../visitor";
19
19
  import { ExecutionContext } from "../../execution";
20
20
  import { JavaScriptVisitor, JS } from "../index";
21
- import { maybeAddImport } from "../add-import";
22
- import { J, isIdentifier, Type } from "../../java";
23
- import { create as produce, Draft } from "mutative";
21
+ import { maybeRebind } from "../binding";
22
+ import { J, Type } from "../../java";
23
+ import { create as produce } from "mutative";
24
24
 
25
25
  /**
26
26
  * Changes an import from one module to another, updating all type attributions.
@@ -41,7 +41,7 @@ import { create as produce, Draft } from "mutative";
41
41
  * // After: import { act } from 'react';
42
42
  *
43
43
  * @example
44
- * // Change a named import to a different name
44
+ * // Change which member of the module a name is bound to
45
45
  * const recipe = new ChangeImport({
46
46
  * oldModule: "lodash",
47
47
  * oldMember: "extend",
@@ -49,7 +49,7 @@ import { create as produce, Draft } from "mutative";
49
49
  * newMember: "assign"
50
50
  * });
51
51
  * // Before: import { extend } from 'lodash';
52
- * // After: import { assign } from 'lodash';
52
+ * // After: import { assign as extend } from 'lodash';
53
53
  */
54
54
  export class ChangeImport extends Recipe {
55
55
  readonly name = "org.openrewrite.javascript.change-import";
@@ -85,19 +85,11 @@ export class ChangeImport extends Recipe {
85
85
  })
86
86
  newMember?: string;
87
87
 
88
- @Option({
89
- displayName: "New alias",
90
- description: "Optional alias for the new import. Required when newMember is 'default' or '*'.",
91
- required: false
92
- })
93
- newAlias?: string;
94
-
95
88
  constructor(options?: {
96
89
  oldModule?: string;
97
90
  oldMember?: string;
98
91
  newModule?: string;
99
92
  newMember?: string;
100
- newAlias?: string;
101
93
  }) {
102
94
  super(options);
103
95
  }
@@ -107,7 +99,6 @@ export class ChangeImport extends Recipe {
107
99
  const oldMember = this.oldMember;
108
100
  const newModule = this.newModule;
109
101
  const newMember = this.newMember ?? oldMember;
110
- const newAlias = this.newAlias;
111
102
 
112
103
  // Build the old and new FQNs for type attribution updates
113
104
  const oldFqn = oldMember === 'default' || oldMember === '*'
@@ -119,185 +110,14 @@ export class ChangeImport extends Recipe {
119
110
 
120
111
  return new class extends JavaScriptVisitor<ExecutionContext> {
121
112
  private hasOldImport = false;
122
- private oldAlias?: string;
123
- private transformedImport = false;
124
113
 
125
114
  override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
126
- // Reset tracking for each file
127
- this.hasOldImport = false;
128
- this.oldAlias = undefined;
129
- this.transformedImport = false;
130
-
131
- // First pass: check if the old import exists and capture any alias
132
- for (const statement of cu.statements) {
133
- const stmt = statement.element ?? statement;
134
- if (stmt.kind === JS.Kind.Import) {
135
- const jsImport = stmt as JS.Import;
136
- const aliasInfo = this.checkForOldImport(jsImport);
137
- if (aliasInfo.found) {
138
- this.hasOldImport = true;
139
- this.oldAlias = aliasInfo.alias;
140
- break;
141
- }
142
- }
143
- }
144
-
145
- // Visit the compilation unit (this will transform imports via visitJsImport)
146
- let result = await super.visitJsCompilationUnit(cu, ctx) as JS.CompilationUnit;
147
-
148
- // If we transformed an import but need to add to existing import from new module,
149
- // or if we only removed a member from a multi-import, use maybeAddImport
150
- if (this.hasOldImport && !this.transformedImport) {
151
- const aliasToUse = newAlias ?? this.oldAlias;
152
-
153
- if (newMember === 'default') {
154
- maybeAddImport(this, {
155
- module: newModule,
156
- member: 'default',
157
- alias: aliasToUse,
158
- onlyIfReferenced: false
159
- });
160
- } else if (newMember === '*') {
161
- maybeAddImport(this, {
162
- module: newModule,
163
- member: '*',
164
- alias: aliasToUse,
165
- onlyIfReferenced: false
166
- });
167
- } else if (aliasToUse && aliasToUse !== newMember) {
168
- maybeAddImport(this, {
169
- module: newModule,
170
- member: newMember,
171
- alias: aliasToUse,
172
- onlyIfReferenced: false
173
- });
174
- } else {
175
- maybeAddImport(this, {
176
- module: newModule,
177
- member: newMember,
178
- onlyIfReferenced: false
179
- });
180
- }
181
- }
182
-
183
- return result;
184
- }
185
-
186
- override async visitImportDeclaration(jsImport: JS.Import, ctx: ExecutionContext): Promise<J | undefined> {
187
- let imp = await super.visitImportDeclaration(jsImport, ctx) as JS.Import;
188
-
189
- if (!this.hasOldImport) {
190
- return imp;
191
- }
192
-
193
- const aliasInfo = this.checkForOldImport(imp);
194
- if (!aliasInfo.found) {
195
- return imp;
196
- }
197
-
198
- // Check if this is the only import from the old module
199
- const namedImports = this.getNamedImports(imp);
200
- const isOnlyImport = namedImports.length === 1 ||
201
- (oldMember === 'default' && !imp.importClause?.namedBindings) ||
202
- (oldMember === '*');
203
-
204
- if (isOnlyImport) {
205
- // Transform the module specifier in place
206
- this.transformedImport = true;
207
- return produce(imp, draft => {
208
- if (draft.moduleSpecifier) {
209
- const literal = draft.moduleSpecifier.element as Draft<J.Literal>;
210
- literal.value = newModule;
211
- // Update valueSource to preserve quote style
212
- const originalSource = literal.valueSource || `"${oldModule}"`;
213
- const quoteChar = originalSource.startsWith("'") ? "'" : '"';
214
- literal.valueSource = `${quoteChar}${newModule}${quoteChar}`;
215
- }
216
- // If we're also renaming the member, update the import specifier
217
- if (newMember !== oldMember && oldMember !== 'default' && oldMember !== '*') {
218
- const importClause = draft.importClause;
219
- if (importClause?.namedBindings?.kind === JS.Kind.NamedImports) {
220
- const namedImports = importClause.namedBindings as Draft<JS.NamedImports>;
221
- for (const elem of namedImports.elements.elements) {
222
- const specifier = elem.element;
223
- if (specifier.specifier.kind === J.Kind.Identifier &&
224
- specifier.specifier.simpleName === oldMember) {
225
- specifier.specifier.simpleName = newMember;
226
- } else if (specifier.specifier.kind === JS.Kind.Alias) {
227
- const aliasNode = specifier.specifier as Draft<JS.Alias>;
228
- const propertyName = aliasNode.propertyName.element;
229
- if (propertyName.kind === J.Kind.Identifier &&
230
- propertyName.simpleName === oldMember) {
231
- propertyName.simpleName = newMember;
232
- }
233
- }
234
- }
235
- }
236
- }
237
- });
238
- } else {
239
- // Remove just the specific member from the import
240
- // maybeAddImport will add the new import
241
- return this.removeNamedImportMember(imp, oldMember, ctx);
242
- }
243
- }
244
-
245
- private async removeNamedImportMember(imp: JS.Import, memberToRemove: string, _ctx: ExecutionContext): Promise<JS.Import> {
246
- return produce(imp, draft => {
247
- const importClause = draft.importClause;
248
- if (!importClause?.namedBindings) return;
249
- if (importClause.namedBindings.kind !== JS.Kind.NamedImports) return;
250
-
251
- const namedImports = importClause.namedBindings as Draft<JS.NamedImports>;
252
- const elements = namedImports.elements.elements;
253
- const filteredElements = elements.filter(elem => {
254
- const specifier = elem.element;
255
- const specifierNode = specifier.specifier;
256
-
257
- if (specifierNode.kind === J.Kind.Identifier) {
258
- return specifierNode.simpleName !== memberToRemove;
259
- }
260
-
261
- if (specifierNode.kind === JS.Kind.Alias) {
262
- const alias = specifierNode as JS.Alias;
263
- const propertyName = alias.propertyName.element;
264
- if (propertyName.kind === J.Kind.Identifier) {
265
- return propertyName.simpleName !== memberToRemove;
266
- }
267
- }
268
-
269
- return true;
270
- });
115
+ this.hasOldImport = maybeRebind(this, {
116
+ from: {module: oldModule, member: oldMember},
117
+ to: {module: newModule, member: newMember}
118
+ }) !== undefined;
271
119
 
272
- namedImports.elements.elements = filteredElements;
273
- });
274
- }
275
-
276
- private getNamedImports(imp: JS.Import): string[] {
277
- const imports: string[] = [];
278
- const importClause = imp.importClause;
279
- if (!importClause) return imports;
280
-
281
- const namedBindings = importClause.namedBindings;
282
- if (!namedBindings || namedBindings.kind !== JS.Kind.NamedImports) return imports;
283
-
284
- const namedImports = namedBindings as JS.NamedImports;
285
- for (const elem of namedImports.elements.elements) {
286
- const specifier = elem.element;
287
- const specifierNode = specifier.specifier;
288
-
289
- if (isIdentifier(specifierNode)) {
290
- imports.push(specifierNode.simpleName);
291
- } else if (specifierNode.kind === JS.Kind.Alias) {
292
- const alias = specifierNode as JS.Alias;
293
- const propertyName = alias.propertyName.element;
294
- if (isIdentifier(propertyName)) {
295
- imports.push(propertyName.simpleName);
296
- }
297
- }
298
- }
299
-
300
- return imports;
120
+ return super.visitJsCompilationUnit(cu, ctx);
301
121
  }
302
122
 
303
123
  override async visitIdentifier(identifier: J.Identifier, ctx: ExecutionContext): Promise<J | undefined> {
@@ -629,79 +449,6 @@ export class ChangeImport extends Recipe {
629
449
  }
630
450
  return arrayType;
631
451
  }
632
-
633
- private checkForOldImport(jsImport: JS.Import): { found: boolean; alias?: string } {
634
- // Check if this import is from the old module
635
- const moduleSpecifier = jsImport.moduleSpecifier;
636
- if (!moduleSpecifier) return { found: false };
637
-
638
- const literal = moduleSpecifier.element;
639
- if (literal.kind !== J.Kind.Literal) return { found: false };
640
-
641
- const value = (literal as J.Literal).value;
642
- if (value !== oldModule) return { found: false };
643
-
644
- const importClause = jsImport.importClause;
645
- if (!importClause) {
646
- // Side-effect import - not what we're looking for
647
- return { found: false };
648
- }
649
-
650
- // Check for default import
651
- if (oldMember === 'default') {
652
- if (importClause.name) {
653
- const nameElem = importClause.name.element;
654
- if (isIdentifier(nameElem)) {
655
- return { found: true, alias: nameElem.simpleName };
656
- }
657
- }
658
- return { found: false };
659
- }
660
-
661
- // Check for namespace import
662
- if (oldMember === '*') {
663
- const namedBindings = importClause.namedBindings;
664
- if (namedBindings?.kind === JS.Kind.Alias) {
665
- const alias = namedBindings as JS.Alias;
666
- if (isIdentifier(alias.alias)) {
667
- return { found: true, alias: alias.alias.simpleName };
668
- }
669
- }
670
- return { found: false };
671
- }
672
-
673
- // Check for named imports
674
- const namedBindings = importClause.namedBindings;
675
- if (!namedBindings) return { found: false };
676
-
677
- if (namedBindings.kind !== JS.Kind.NamedImports) return { found: false };
678
-
679
- const namedImports = namedBindings as JS.NamedImports;
680
- const elements = namedImports.elements.elements;
681
-
682
- for (const elem of elements) {
683
- const specifier = elem.element;
684
- const specifierNode = specifier.specifier;
685
-
686
- // Handle direct import: import { act }
687
- if (isIdentifier(specifierNode) && specifierNode.simpleName === oldMember) {
688
- return { found: true };
689
- }
690
-
691
- // Handle aliased import: import { act as something }
692
- if (specifierNode.kind === JS.Kind.Alias) {
693
- const alias = specifierNode as JS.Alias;
694
- const propertyName = alias.propertyName.element;
695
- if (isIdentifier(propertyName) && propertyName.simpleName === oldMember) {
696
- if (isIdentifier(alias.alias)) {
697
- return { found: true, alias: alias.alias.simpleName };
698
- }
699
- }
700
- }
701
- }
702
-
703
- return { found: false };
704
- }
705
452
  }();
706
453
  }
707
454
  }
@@ -1,44 +1,10 @@
1
1
  import {JavaScriptVisitor} from "./visitor";
2
2
  import {J} from "../java";
3
+ import {bindingNames} from "./scope";
3
4
  import {JS, JSX} from "./tree";
4
5
  import {mapAsync, updateIfChanged} from "../util";
5
6
  import {ElementRemovalFormatter} from "../java";
6
7
 
7
- /**
8
- * @param visitor The visitor to add the import removal to
9
- * @param module The module name (e.g., 'fs', 'react') to remove imports from
10
- * @param member Optionally, the specific member to remove from the import.
11
- * If not specified, removes all unused imports from the module.
12
- * Special values:
13
- * - 'default': Removes the default import from the module if unused,
14
- * regardless of its local name (e.g., `import React from 'react'`)
15
- * - '*': Removes the namespace import if unused (e.g., `import * as fs from 'fs'`)
16
- *
17
- * @example
18
- * // Remove a specific named import if unused
19
- * maybeRemoveImport(visitor, 'fs', 'readFile');
20
- *
21
- * @example
22
- * // Remove the default import from 'react' if unused (regardless of local name)
23
- * maybeRemoveImport(visitor, 'react', 'default');
24
- *
25
- * @example
26
- * // Remove all unused imports from 'react' module
27
- * maybeRemoveImport(visitor, 'react');
28
- *
29
- * @example
30
- * // Remove namespace import if unused
31
- * maybeRemoveImport(visitor, 'fs', '*');
32
- */
33
- export function maybeRemoveImport(visitor: JavaScriptVisitor<any>, module: string, member?: string) {
34
- for (const v of visitor.afterVisit || []) {
35
- if (v instanceof RemoveImport && v.module === module && v.member === member) {
36
- return;
37
- }
38
- }
39
- visitor.afterVisit.push(new RemoveImport(module, member));
40
- }
41
-
42
8
  // Type alias for RightPadded elements to simplify type signatures
43
9
  type RightPaddedElement<T extends J> = {
44
10
  element?: T;
@@ -268,7 +234,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
268
234
  shouldRemove = !usedIdentifiers.has(name) && !usedTypes.has(name);
269
235
  } else {
270
236
  // Regular case: check if the import name matches the removal criteria
271
- shouldRemove = this.shouldRemoveImport(name, usedIdentifiers, usedTypes);
237
+ shouldRemove = this.shouldRemoveImport(name, usedIdentifiers, usedTypes, name);
272
238
  }
273
239
 
274
240
  if (shouldRemove) {
@@ -325,7 +291,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
325
291
  }, p);
326
292
  }
327
293
  // Namespace is used, we can't remove individual members from it
328
- } else if (this.shouldRemoveImport(name, usedIdentifiers, usedTypes)) {
294
+ } else if (this.shouldRemoveImport(name, usedIdentifiers, usedTypes, name)) {
329
295
  // If there's no default import, remove the entire import
330
296
  if (!importClause.name) {
331
297
  return undefined;
@@ -355,7 +321,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
355
321
  }, p);
356
322
  }
357
323
  // Namespace is used, we can't remove individual members from it
358
- } else if (this.shouldRemoveImport(aliasName, usedIdentifiers, usedTypes)) {
324
+ } else if (this.shouldRemoveImport(aliasName, usedIdentifiers, usedTypes, aliasName)) {
359
325
  // If there's no default import, remove the entire import
360
326
  if (!importClause.name) {
361
327
  return undefined;
@@ -490,7 +456,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
490
456
  return true; // Keep imports that don't match the member
491
457
  } else {
492
458
  // We're removing based on the import name itself
493
- return !this.shouldRemoveImport(importName, usedIdentifiers, usedTypes);
459
+ return !this.shouldRemoveImport(importName, usedIdentifiers, usedTypes, importName);
494
460
  }
495
461
  }
496
462
  return true; // Keep non-ImportSpecifier elements
@@ -612,14 +578,10 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
612
578
  const {filtered, allRemoved} = await this.filterElementsWithPrefixPreservation(
613
579
  pattern.bindings.elements,
614
580
  (elem: J) => {
615
- if (elem.kind === JS.Kind.BindingElement) {
616
- const name = this.getBindingElementName(elem as JS.BindingElement);
617
- return !this.shouldRemoveImport(name, usedIdentifiers, new Set());
618
- } else if (elem.kind === J.Kind.Identifier) {
619
- const name = (elem as J.Identifier).simpleName;
620
- return !this.shouldRemoveImport(name, usedIdentifiers, new Set());
621
- }
622
- return true; // Keep other element types
581
+ const bound = this.boundByPatternElement(elem);
582
+ // An element whose names cannot be read is left alone.
583
+ return bound.length === 0 ||
584
+ bound.some(b => !this.shouldRemoveImport(b.name, usedIdentifiers, new Set(), b.member));
623
585
  },
624
586
  async (elem: J, prefix: J.Space) => {
625
587
  if (elem.kind === J.Kind.Identifier) {
@@ -687,23 +649,25 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
687
649
  return undefined;
688
650
  }
689
651
 
690
- private getBindingElementName(bindingElement: JS.BindingElement): string {
691
- const name = bindingElement.name;
692
- if (name?.kind === J.Kind.Identifier) {
693
- return (name as J.Identifier).simpleName;
694
- }
695
- return '';
652
+ /** The names a pattern element binds, and for each the member of the module it reads. */
653
+ private boundByPatternElement(elem: J): { name: string; member?: string }[] {
654
+ // Shorthand, so the name it binds is the member it reads.
655
+ return elem.kind === J.Kind.Identifier
656
+ ? [{name: (elem as J.Identifier).simpleName, member: (elem as J.Identifier).simpleName}]
657
+ : bindingNames(elem);
696
658
  }
697
659
 
698
660
  private shouldRemoveImport(
699
661
  name: string,
700
662
  usedIdentifiers: Set<string>,
701
- usedTypes: Set<string>
663
+ usedTypes: Set<string>,
664
+ member: string | undefined
702
665
  ): boolean {
703
666
  // If member is specified, we're removing a specific member from the module
704
667
  if (this.member !== undefined) {
705
- // Only remove if this is the specific member we're looking for
706
- if (this.member !== name) {
668
+ // A name bound under an alias reads one member and is referenced by another, and a name
669
+ // a nested pattern binds reads a property of a property, so it reads no member at all.
670
+ if (this.member !== member) {
707
671
  return false;
708
672
  }
709
673
  }