@openrewrite/rewrite 8.91.1 → 8.91.3

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 (160) hide show
  1. package/dist/execution.d.ts +8 -0
  2. package/dist/execution.d.ts.map +1 -1
  3. package/dist/execution.js +31 -0
  4. package/dist/execution.js.map +1 -1
  5. package/dist/javascript/add-import.d.ts +127 -38
  6. package/dist/javascript/add-import.d.ts.map +1 -1
  7. package/dist/javascript/add-import.js +684 -195
  8. package/dist/javascript/add-import.js.map +1 -1
  9. package/dist/javascript/amd.d.ts +148 -0
  10. package/dist/javascript/amd.d.ts.map +1 -0
  11. package/dist/javascript/amd.js +802 -0
  12. package/dist/javascript/amd.js.map +1 -0
  13. package/dist/javascript/binding.d.ts +102 -0
  14. package/dist/javascript/binding.d.ts.map +1 -0
  15. package/dist/javascript/binding.js +308 -0
  16. package/dist/javascript/binding.js.map +1 -0
  17. package/dist/javascript/comparator.d.ts +2 -0
  18. package/dist/javascript/comparator.d.ts.map +1 -1
  19. package/dist/javascript/comparator.js +5 -1
  20. package/dist/javascript/comparator.js.map +1 -1
  21. package/dist/javascript/format/format.d.ts +5 -2
  22. package/dist/javascript/format/format.d.ts.map +1 -1
  23. package/dist/javascript/format/format.js +51 -11
  24. package/dist/javascript/format/format.js.map +1 -1
  25. package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts +19 -0
  26. package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts.map +1 -1
  27. package/dist/javascript/format/minimum-viable-spacing-visitor.js +180 -7
  28. package/dist/javascript/format/minimum-viable-spacing-visitor.js.map +1 -1
  29. package/dist/javascript/format/prettier-format.d.ts +2 -1
  30. package/dist/javascript/format/prettier-format.d.ts.map +1 -1
  31. package/dist/javascript/format/prettier-format.js +33 -23
  32. package/dist/javascript/format/prettier-format.js.map +1 -1
  33. package/dist/javascript/index.d.ts +8 -1
  34. package/dist/javascript/index.d.ts.map +1 -1
  35. package/dist/javascript/index.js +46 -1
  36. package/dist/javascript/index.js.map +1 -1
  37. package/dist/javascript/parser-utils.d.ts.map +1 -1
  38. package/dist/javascript/parser-utils.js +7 -9
  39. package/dist/javascript/parser-utils.js.map +1 -1
  40. package/dist/javascript/parser.d.ts +7 -1
  41. package/dist/javascript/parser.d.ts.map +1 -1
  42. package/dist/javascript/parser.js +55 -4
  43. package/dist/javascript/parser.js.map +1 -1
  44. package/dist/javascript/print.d.ts +1 -0
  45. package/dist/javascript/print.d.ts.map +1 -1
  46. package/dist/javascript/print.js +9 -0
  47. package/dist/javascript/print.js.map +1 -1
  48. package/dist/javascript/recipes/change-import.d.ts +4 -3
  49. package/dist/javascript/recipes/change-import.d.ts.map +1 -1
  50. package/dist/javascript/recipes/change-import.js +12 -504
  51. package/dist/javascript/recipes/change-import.js.map +1 -1
  52. package/dist/javascript/remove-import.d.ts +2 -28
  53. package/dist/javascript/remove-import.d.ts.map +1 -1
  54. package/dist/javascript/remove-import.js +19 -57
  55. package/dist/javascript/remove-import.js.map +1 -1
  56. package/dist/javascript/rpc.js +2 -0
  57. package/dist/javascript/rpc.js.map +1 -1
  58. package/dist/javascript/scope.d.ts +82 -0
  59. package/dist/javascript/scope.d.ts.map +1 -0
  60. package/dist/javascript/scope.js +476 -0
  61. package/dist/javascript/scope.js.map +1 -0
  62. package/dist/javascript/templating/bindings.d.ts +0 -11
  63. package/dist/javascript/templating/bindings.d.ts.map +1 -1
  64. package/dist/javascript/templating/bindings.js +2 -49
  65. package/dist/javascript/templating/bindings.js.map +1 -1
  66. package/dist/javascript/templating/comparator.d.ts +12 -0
  67. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  68. package/dist/javascript/templating/comparator.js +68 -14
  69. package/dist/javascript/templating/comparator.js.map +1 -1
  70. package/dist/javascript/templating/engine.d.ts +22 -4
  71. package/dist/javascript/templating/engine.d.ts.map +1 -1
  72. package/dist/javascript/templating/engine.js +68 -24
  73. package/dist/javascript/templating/engine.js.map +1 -1
  74. package/dist/javascript/templating/index.d.ts +1 -1
  75. package/dist/javascript/templating/index.d.ts.map +1 -1
  76. package/dist/javascript/templating/index.js.map +1 -1
  77. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  78. package/dist/javascript/templating/pattern.js +2 -2
  79. package/dist/javascript/templating/pattern.js.map +1 -1
  80. package/dist/javascript/templating/placeholder-replacement.d.ts +2 -0
  81. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  82. package/dist/javascript/templating/placeholder-replacement.js +10 -5
  83. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  84. package/dist/javascript/templating/precedence.d.ts +12 -4
  85. package/dist/javascript/templating/precedence.d.ts.map +1 -1
  86. package/dist/javascript/templating/precedence.js +18 -11
  87. package/dist/javascript/templating/precedence.js.map +1 -1
  88. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  89. package/dist/javascript/templating/rewrite.js +7 -1
  90. package/dist/javascript/templating/rewrite.js.map +1 -1
  91. package/dist/javascript/templating/template.d.ts +7 -1
  92. package/dist/javascript/templating/template.d.ts.map +1 -1
  93. package/dist/javascript/templating/template.js +54 -28
  94. package/dist/javascript/templating/template.js.map +1 -1
  95. package/dist/javascript/templating/types.d.ts +15 -18
  96. package/dist/javascript/templating/types.d.ts.map +1 -1
  97. package/dist/javascript/templating/utils.d.ts +1 -1
  98. package/dist/javascript/templating/utils.d.ts.map +1 -1
  99. package/dist/javascript/templating/utils.js +3 -2
  100. package/dist/javascript/templating/utils.js.map +1 -1
  101. package/dist/javascript/tree.d.ts +1 -0
  102. package/dist/javascript/tree.d.ts.map +1 -1
  103. package/dist/javascript/tree.js.map +1 -1
  104. package/dist/javascript/type-mapping.d.ts.map +1 -1
  105. package/dist/javascript/type-mapping.js +14 -0
  106. package/dist/javascript/type-mapping.js.map +1 -1
  107. package/dist/javascript/visitor.d.ts.map +1 -1
  108. package/dist/javascript/visitor.js +1 -0
  109. package/dist/javascript/visitor.js.map +1 -1
  110. package/dist/parser.d.ts +6 -0
  111. package/dist/parser.d.ts.map +1 -1
  112. package/dist/parser.js +23 -1
  113. package/dist/parser.js.map +1 -1
  114. package/dist/print.d.ts.map +1 -1
  115. package/dist/print.js +5 -2
  116. package/dist/print.js.map +1 -1
  117. package/dist/rewrite-javascript-version.txt +1 -1
  118. package/dist/rpc/request/parse-project.d.ts +11 -1
  119. package/dist/rpc/request/parse-project.d.ts.map +1 -1
  120. package/dist/rpc/request/parse-project.js +7 -2
  121. package/dist/rpc/request/parse-project.js.map +1 -1
  122. package/dist/rpc/request/parse.d.ts +13 -1
  123. package/dist/rpc/request/parse.d.ts.map +1 -1
  124. package/dist/rpc/request/parse.js +8 -2
  125. package/dist/rpc/request/parse.js.map +1 -1
  126. package/package.json +2 -2
  127. package/src/execution.ts +23 -0
  128. package/src/javascript/add-import.ts +753 -238
  129. package/src/javascript/amd.ts +954 -0
  130. package/src/javascript/binding.ts +435 -0
  131. package/src/javascript/comparator.ts +6 -1
  132. package/src/javascript/format/format.ts +56 -12
  133. package/src/javascript/format/minimum-viable-spacing-visitor.ts +196 -7
  134. package/src/javascript/format/prettier-format.ts +35 -25
  135. package/src/javascript/index.ts +19 -1
  136. package/src/javascript/parser-utils.ts +7 -11
  137. package/src/javascript/parser.ts +78 -6
  138. package/src/javascript/print.ts +10 -0
  139. package/src/javascript/recipes/change-import.ts +13 -593
  140. package/src/javascript/remove-import.ts +20 -56
  141. package/src/javascript/rpc.ts +2 -0
  142. package/src/javascript/scope.ts +517 -0
  143. package/src/javascript/templating/bindings.ts +2 -55
  144. package/src/javascript/templating/comparator.ts +81 -16
  145. package/src/javascript/templating/engine.ts +107 -38
  146. package/src/javascript/templating/index.ts +0 -1
  147. package/src/javascript/templating/pattern.ts +4 -2
  148. package/src/javascript/templating/placeholder-replacement.ts +16 -6
  149. package/src/javascript/templating/precedence.ts +21 -13
  150. package/src/javascript/templating/rewrite.ts +9 -1
  151. package/src/javascript/templating/template.ts +68 -29
  152. package/src/javascript/templating/types.ts +17 -18
  153. package/src/javascript/templating/utils.ts +4 -2
  154. package/src/javascript/tree.ts +1 -0
  155. package/src/javascript/type-mapping.ts +15 -0
  156. package/src/javascript/visitor.ts +1 -0
  157. package/src/parser.ts +24 -1
  158. package/src/print.ts +5 -2
  159. package/src/rpc/request/parse-project.ts +6 -2
  160. package/src/rpc/request/parse.ts +7 -2
@@ -0,0 +1,435 @@
1
+ /*
2
+ * Copyright 2025 the original author or authors.
3
+ * <p>
4
+ * Licensed under the Moderne Source Available License (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ * <p>
8
+ * https://docs.moderne.io/licensing/moderne-source-available-license
9
+ * <p>
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import {J} from "../java";
17
+ import {JS} from "./tree";
18
+ import {JavaScriptVisitor} from "./visitor";
19
+ import {compilationUnitOf, cursorOf, declarationsOf, namesUsedIn, scopeOf} from "./scope";
20
+ import {
21
+ AddImportOptions, bindImport, existingImportBinding, ExistingImportBinding, hasEsmSyntax, isCommonJs, memberName,
22
+ moduleNameOf, nameTaken, RebindImport, requiredModuleOfDeclaration
23
+ } from "./add-import";
24
+ import {RemoveImport} from "./remove-import";
25
+ import {
26
+ AmdCalleeOptions, amdBlockOf, bindAmd, calleesOf, dependencyNames, derivedBindingName, enclosingAmdBlock,
27
+ isBindableName, parameterNames, RebindAmdDependency, RemoveAmdDependency
28
+ } from "./amd";
29
+
30
+ /**
31
+ * A bare string is shorthand for `{module}`, the overwhelmingly common call with nothing else to
32
+ * configure. A factory parameter binds a whole module under a name and nothing else, so on the AMD
33
+ * lane `member`, `typeOnly`, `sideEffectOnly`, `onlyIfReferenced`, `quoteStyle` and `style` do not
34
+ * apply: the first three refuse, and the rest have nothing to shape.
35
+ */
36
+ export interface MaybeBindOptions extends AddImportOptions {
37
+ /** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
38
+ amdCallee?: string | readonly string[];
39
+ }
40
+
41
+ /** A bare string is shorthand for `{module}`, the overwhelmingly common call with nothing else to configure. */
42
+ export interface MaybeUnbindOptions {
43
+ module: string;
44
+
45
+ /** The member to remove; unset removes every unused binding of the module. */
46
+ member?: string;
47
+
48
+ /** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
49
+ amdCallee?: string | readonly string[];
50
+ }
51
+
52
+ export interface MaybeRebindOptions {
53
+ from: {module: string; member?: string};
54
+
55
+ /**
56
+ * `alias` names the moved binding, taken verbatim. Left unset, an alias on the `from` side
57
+ * survives the move — `{Old as X}` becomes `{New as X}`, since `X` is a name the file chose —
58
+ * while an unaliased binding takes the new member's name where that name is free. Pin `alias`
59
+ * to the old local name to keep it. See CLAUDE.md: Which name a rebind binds.
60
+ */
61
+ to: {module: string; member?: string; alias?: string};
62
+
63
+ /** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
64
+ amdCallee?: string | readonly string[];
65
+ }
66
+
67
+ export interface ModuleBindings {
68
+ /**
69
+ * The module `localName` refers to, or undefined when it is not a module binding — of any
70
+ * shape, so a namespace import's name answers here even though `maybeBind` will not reuse it
71
+ * for a plain `{module}` request. See `bindingOf`.
72
+ */
73
+ moduleOf(localName: string): string | undefined;
74
+
75
+ /**
76
+ * The local name bound to `module`, or undefined when nothing binds it — of any shape: a
77
+ * namespace import counts, even though `maybeBind` will not treat it as answering a plain
78
+ * `{module}` request. What a name can stand in for is `maybeBind`'s question, not this one's.
79
+ */
80
+ bindingOf(module: string): string | undefined;
81
+
82
+ /**
83
+ * The lane these bindings come from, and the one `maybeBind` would use. `"none"` is a
84
+ * plain script — no import, export, `require` binding, or enclosing AMD block — which
85
+ * `maybeBind` still turns into a module on request; a caller that must not do that checks
86
+ * for `"none"` itself.
87
+ */
88
+ readonly moduleSystem: "esm" | "amd" | "commonjs" | "none";
89
+ }
90
+
91
+ export function moduleBindings(
92
+ visitor: JavaScriptVisitor<any>,
93
+ options?: AmdCalleeOptions
94
+ ): ModuleBindings {
95
+ const amd = enclosingAmdBlock(visitor, options);
96
+ if (amd !== undefined) {
97
+ const modules = dependencyNames(amd.block);
98
+ const bindings = parameterNames(amd.block);
99
+ return {
100
+ moduleSystem: "amd",
101
+ moduleOf: localName => {
102
+ const index = bindings.indexOf(localName);
103
+ // `dependencyNames` pads a non-literal element with "" to hold its position; that
104
+ // filler names no module, so it answers neither lookup.
105
+ return index < 0 || modules[index] === "" ? undefined : modules[index];
106
+ },
107
+ bindingOf: module => {
108
+ const index = module === "" ? -1 : modules.indexOf(module);
109
+ return index < 0 ? undefined : bindings[index];
110
+ }
111
+ };
112
+ }
113
+
114
+ const cu = compilationUnitOf(visitor);
115
+ const bound = cu === undefined ? [] : moduleObjectBindings(cu);
116
+ return {
117
+ moduleSystem: cu === undefined ? "none" :
118
+ isCommonJs(cu) ? "commonjs" :
119
+ hasEsmSyntax(cu) ? "esm" : "none",
120
+ moduleOf: localName => bound.find(b => b.name === localName)?.module,
121
+ bindingOf: module => bound.find(b => b.module === module)?.name
122
+ };
123
+ }
124
+
125
+ export function isAmdBlock(node: J, options?: AmdCalleeOptions): boolean {
126
+ return node.kind === J.Kind.MethodInvocation &&
127
+ amdBlockOf(node as J.MethodInvocation, calleesOf(options)) !== undefined;
128
+ }
129
+
130
+ interface ModuleObjectBinding {
131
+ name: string;
132
+ module: string;
133
+
134
+ /**
135
+ * `"default"` and `"namespace"` bind different values — a namespace object's default sits at
136
+ * `.default` — so only one answers a whole-module request of the matching form. CommonJS has
137
+ * no such split: `"require"` answers either. A caller wanting the namespace asks for
138
+ * `member: "*"`, which is what a module exporting no default is bound by.
139
+ */
140
+ shape: "default" | "namespace" | "require";
141
+
142
+ /** A type-only import erases, so the name it binds stands for no value at runtime. */
143
+ typeOnly: boolean;
144
+ }
145
+
146
+ /**
147
+ * The module a `const X = await import("m")` declaration names, for the one variable it declares.
148
+ * A dynamic import resolves to the module namespace object, the same value `import * as X`
149
+ * binds, so it shares that shape rather than getting one of its own.
150
+ */
151
+ function dynamicallyImportedModule(declaration: J.VariableDeclarations): string | undefined {
152
+ const variables = declaration.variables;
153
+ const initializer = variables.length === 1 ? variables[0].element?.initializer?.element : undefined;
154
+ const awaited = initializer?.kind === JS.Kind.Await ? (initializer as JS.Await).expression : undefined;
155
+ // `import(...)` is a keyword, not an identifier, so the parser maps it to `JS.FunctionCall`
156
+ // rather than the `J.MethodInvocation` an ordinary call gets.
157
+ if (awaited?.kind !== JS.Kind.FunctionCall) {
158
+ return undefined;
159
+ }
160
+ const call = awaited as JS.FunctionCall;
161
+ const callee = call.function?.element;
162
+ if (callee?.kind !== J.Kind.Identifier || (callee as J.Identifier).simpleName !== "import") {
163
+ return undefined;
164
+ }
165
+ const argument = call.arguments.elements[0]?.element;
166
+ return argument?.kind === J.Kind.Literal && typeof (argument as J.Literal).value === "string"
167
+ ? (argument as J.Literal).value as string
168
+ : undefined;
169
+ }
170
+
171
+ /** Bindings from top-level `const X = <moduleOf-recognised call>` declarations, all of one shape. */
172
+ function wholeModuleBindingsVia(
173
+ cu: JS.CompilationUnit,
174
+ moduleOf: (declaration: J.VariableDeclarations) => string | undefined,
175
+ shape: ModuleObjectBinding["shape"]
176
+ ): ModuleObjectBinding[] {
177
+ const bindings: ModuleObjectBinding[] = [];
178
+ for (const stmt of cu.statements) {
179
+ for (const declaration of declarationsOf(stmt.element)) {
180
+ const module = moduleOf(declaration);
181
+ const name = declaration.variables[0]?.element?.name;
182
+ if (module !== undefined && name?.kind === J.Kind.Identifier) {
183
+ bindings.push({name: (name as J.Identifier).simpleName, module, shape, typeOnly: false});
184
+ }
185
+ }
186
+ }
187
+ return bindings;
188
+ }
189
+
190
+ /** Only whole-module bindings: a named member does not name the module object. */
191
+ function moduleObjectBindings(cu: JS.CompilationUnit): ModuleObjectBinding[] {
192
+ const bindings: ModuleObjectBinding[] = [];
193
+ for (const stmt of cu.statements) {
194
+ const statement = stmt.element;
195
+ if (statement?.kind !== JS.Kind.Import) {
196
+ continue;
197
+ }
198
+ const jsImport = statement as JS.Import;
199
+ const specifier = jsImport.moduleSpecifier?.element;
200
+ if (specifier?.kind !== J.Kind.Literal) {
201
+ continue;
202
+ }
203
+ const module = (specifier as J.Literal).value;
204
+ if (typeof module !== "string") {
205
+ continue;
206
+ }
207
+ const clause = jsImport.importClause;
208
+ const typeOnly = clause?.typeOnly ?? false;
209
+ if (clause?.name?.element?.kind === J.Kind.Identifier) {
210
+ bindings.push({name: (clause.name.element as J.Identifier).simpleName, module, shape: "default", typeOnly});
211
+ }
212
+ // `namedBindings` is a `JS.Alias` only for `import * as X from "m"`; a renamed
213
+ // named import (`{a as b}`) nests its alias inside `NamedImports` instead.
214
+ const named = clause?.namedBindings;
215
+ if (named?.kind === JS.Kind.Alias) {
216
+ const alias = (named as JS.Alias).alias;
217
+ if (alias?.kind === J.Kind.Identifier) {
218
+ bindings.push({name: (alias as J.Identifier).simpleName, module, shape: "namespace", typeOnly});
219
+ }
220
+ }
221
+ }
222
+ bindings.push(...wholeModuleBindingsVia(cu, requiredModuleOfDeclaration, "require"));
223
+ bindings.push(...wholeModuleBindingsVia(cu, dynamicallyImportedModule, "namespace"));
224
+ return bindings;
225
+ }
226
+
227
+ /**
228
+ * Whether `binding` answers a whole-module request: the namespace form when `wantsNamespace`, and
229
+ * never a type-only import for a value, which erases and would leave the reference unbound.
230
+ */
231
+ function answersWholeModuleRequest(binding: ModuleObjectBinding, wantsNamespace: boolean, typeOnly: boolean): boolean {
232
+ return binding.typeOnly === typeOnly &&
233
+ (binding.shape === "require" || binding.shape === (wantsNamespace ? "namespace" : "default"));
234
+ }
235
+
236
+ /**
237
+ * A local binding for `module` or one of its members, creating one where none exists, or
238
+ * `undefined` where no safe binding is possible. The lane — AMD or ESM/CommonJS — is decided
239
+ * from the cursor; AMD binds only the whole module, so a `member` request there refuses rather
240
+ * than guess. `onlyIfReferenced` defaults to true, so the import may never appear.
241
+ */
242
+ export function maybeBind(
243
+ visitor: JavaScriptVisitor<any>,
244
+ options: MaybeBindOptions | string
245
+ ): string | undefined {
246
+ if (typeof options === "string") {
247
+ options = {module: options};
248
+ }
249
+ const module = moduleNameOf(options.module);
250
+ const key = memberName(options.member);
251
+
252
+ const amd = enclosingAmdBlock(visitor, options);
253
+ if (amd !== undefined) {
254
+ if (key !== undefined || options.sideEffectOnly) {
255
+ // Nor can a factory parameter load a module without binding it to a name.
256
+ return undefined;
257
+ }
258
+ return bindAmd(visitor, amd, module, options.alias ?? options.preferredName,
259
+ calleesOf(options), options.alias !== undefined);
260
+ }
261
+
262
+ const cu = compilationUnitOf(visitor);
263
+ const isWholeModule = !options.sideEffectOnly && (key === undefined || key === "*");
264
+ if (isWholeModule && cu) {
265
+ const scope = scopeOf(cursorOf(visitor)!);
266
+ const bound = moduleObjectBindings(cu).find(b =>
267
+ b.module === module && answersWholeModuleRequest(b, key === "*", options.typeOnly ?? false) &&
268
+ // A pinned alias asks for a binding of that name, so another name for the same
269
+ // module does not answer it; `bindImport`'s own lookup applies the same rule.
270
+ (options.alias === undefined || b.name === options.alias) &&
271
+ scope.declaringScope(b.name) === cu);
272
+ if (bound !== undefined) {
273
+ return bound.name;
274
+ }
275
+ }
276
+
277
+ if (isWholeModule && options.preferredName === undefined && derivedBindingName(module) === undefined) {
278
+ // The module's last path segment is not a legal identifier, and the caller named no
279
+ // preference of its own — there is no name left to bind it to.
280
+ return undefined;
281
+ }
282
+
283
+ // `bindImport`'s own lookup finds and reuses a member-specific binding on its own, so
284
+ // refusal here only has to gate the point where it would create a new one.
285
+ const refuseCreate = cu !== undefined && isCommonJs(cu);
286
+ return bindImport(visitor, {
287
+ ...options,
288
+ preferredName: options.preferredName ?? (isWholeModule ? derivedBindingName(module) : undefined)
289
+ }, refuseCreate);
290
+ }
291
+
292
+ /**
293
+ * Removes `module`'s import(s) where unused, or one `member` of it — `'default'` and `'*'` select
294
+ * the default and namespace import regardless of local name. A member-scoped request does not
295
+ * apply to an AMD dependency, which binds a module rather than one of its members.
296
+ */
297
+ export function maybeUnbind(visitor: JavaScriptVisitor<any>, options: MaybeUnbindOptions | string): void {
298
+ if (typeof options === "string") {
299
+ options = {module: options};
300
+ }
301
+ const callees = calleesOf(options);
302
+ const queued = visitor.afterVisit || [];
303
+ if (!queued.some(v => v instanceof RemoveImport && v.module === options.module && v.member === options.member)) {
304
+ visitor.afterVisit.push(new RemoveImport(options.module, options.member));
305
+ }
306
+ // Both queue unconditionally so the caller need not know which lane the file uses: each
307
+ // visitor removes whatever matching construct it finds, ESM import or AMD dependency, and a
308
+ // file with both gets both removed. `amdCallee` says which block a call means, not how
309
+ // anything prints, so — unlike `preferredName`/`quoteStyle` on the bind side — it is part of
310
+ // the dedup key.
311
+ if (!queued.some(v => v instanceof RemoveAmdDependency && v.module === options.module &&
312
+ v.member === options.member && sameCallees(v.callees, callees))) {
313
+ visitor.afterVisit.push(new RemoveAmdDependency(options.module, options.member, callees));
314
+ }
315
+ }
316
+
317
+ function sameCallees(a: readonly string[], b: readonly string[]): boolean {
318
+ return a.length === b.length && a.every((callee, i) => callee === b[i]);
319
+ }
320
+
321
+ /** Which of an import clause's three slots `member` binds — `import`, `import *`, or `import {}`. */
322
+ function bindingShape(member: string | undefined): "default" | "namespace" | "named" {
323
+ const key = memberName(member);
324
+ return key === undefined ? "default" : key === "*" ? "namespace" : "named";
325
+ }
326
+
327
+ /**
328
+ * Moves the binding for `from` to `to` and answers with the name it now carries — the primitive
329
+ * behind a member rename or a module move. Returns `undefined`, changing nothing, where the move
330
+ * is not safely expressible; the refusals and the choice of name are listed in CLAUDE.md:
331
+ * JavaScript module bindings.
332
+ */
333
+ export function maybeRebind(visitor: JavaScriptVisitor<any>, options: MaybeRebindOptions): string | undefined {
334
+ const amd = enclosingAmdBlock(visitor, options);
335
+ if (amd !== undefined) {
336
+ if (memberName(options.from.member) !== undefined || memberName(options.to.member) !== undefined) {
337
+ return undefined;
338
+ }
339
+ const index = dependencyNames(amd.block).indexOf(options.from.module);
340
+ const binding = index < 0 ? undefined : parameterNames(amd.block)[index];
341
+ if (binding === undefined ||
342
+ // An AMD binding's name is its factory parameter, so naming it something else means
343
+ // rewriting the factory's references, which this lane's dependency swap does not reach.
344
+ (options.to.alias !== undefined && options.to.alias !== binding)) {
345
+ return undefined;
346
+ }
347
+ const callees = calleesOf(options);
348
+ const from = options.from.module;
349
+ if (!(visitor.afterVisit || []).some(v => v instanceof RebindAmdDependency &&
350
+ v.blockId === amd.call.id && v.fromModule === from && sameCallees(v.callees, callees))) {
351
+ visitor.afterVisit.push(new RebindAmdDependency(amd.call.id, from, options.to.module, callees));
352
+ }
353
+ return binding;
354
+ }
355
+
356
+ const cu = compilationUnitOf(visitor);
357
+ if (cu === undefined) {
358
+ return undefined;
359
+ }
360
+ const existing = existingImportBinding(cu, options.from.module, options.from.member);
361
+ if (existing === undefined) {
362
+ return undefined;
363
+ }
364
+ if (existing.onlyMemberOfStatement && bindingShape(options.from.member) !== bindingShape(options.to.member)) {
365
+ return undefined;
366
+ }
367
+ if (!existing.onlyMemberOfStatement && isCommonJs(cu)) {
368
+ return undefined;
369
+ }
370
+ const boundName = rebindingName(visitor, cu, options, existing);
371
+ if (boundName === undefined) {
372
+ return undefined;
373
+ }
374
+ if (!(visitor.afterVisit || []).some(v => v instanceof RebindImport &&
375
+ v.from.module === options.from.module && v.from.member === options.from.member &&
376
+ v.localName === existing.localName)) {
377
+ visitor.afterVisit.push(new RebindImport(options.from, options.to, existing.localName, boundName));
378
+ }
379
+ return boundName;
380
+ }
381
+
382
+ /**
383
+ * The name the moved binding takes, or undefined where a pinned alias cannot be bound verbatim,
384
+ * since deconflicting it would leave the references the caller emits unbound. The rule is
385
+ * CLAUDE.md: Which name a rebind binds.
386
+ */
387
+ function rebindingName(
388
+ visitor: JavaScriptVisitor<any>,
389
+ cu: JS.CompilationUnit,
390
+ options: MaybeRebindOptions,
391
+ existing: ExistingImportBinding
392
+ ): string | undefined {
393
+ const taken = (name: string) => nameTaken(name, namesUsedIn(cu), visitor);
394
+ const alias = options.to.alias;
395
+ if (alias !== undefined) {
396
+ return isBindableName(alias) && (alias === existing.localName || !taken(alias)) ? alias : undefined;
397
+ }
398
+ const member = memberName(options.to.member);
399
+ if (existing.aliased || member === undefined || member === '*' || member === existing.localName) {
400
+ return existing.localName;
401
+ }
402
+ return taken(member) ? existing.localName : member;
403
+ }
404
+
405
+ /**
406
+ * @deprecated Use {@link maybeUnbind} instead — this is a call-shape change, not a behaviour
407
+ * change: `maybeRemoveImport(v, module, member)` is `maybeUnbind(v, {module, member})`.
408
+ */
409
+ export function maybeRemoveImport(visitor: JavaScriptVisitor<any>, module: string, member?: string): void {
410
+ maybeUnbind(visitor, {module, member});
411
+ }
412
+
413
+ /**
414
+ * @deprecated Use {@link maybeBind} instead. Beyond binding through an AMD factory parameter,
415
+ * `maybeBind` returns `undefined` rather than creating an import where the file binds its modules
416
+ * with `require`, or where no legal identifier can be derived from the module and none was named.
417
+ */
418
+ export function maybeAddImport(
419
+ visitor: JavaScriptVisitor<any>,
420
+ options: AddImportOptions & { sideEffectOnly: true }
421
+ ): undefined;
422
+ export function maybeAddImport(
423
+ visitor: JavaScriptVisitor<any>,
424
+ options: AddImportOptions & { sideEffectOnly?: false }
425
+ ): string | undefined;
426
+ export function maybeAddImport(
427
+ visitor: JavaScriptVisitor<any>,
428
+ options: AddImportOptions
429
+ ): string | undefined;
430
+ export function maybeAddImport(
431
+ visitor: JavaScriptVisitor<any>,
432
+ options: AddImportOptions
433
+ ): string | undefined {
434
+ return maybeBind(visitor, options);
435
+ }
@@ -2347,6 +2347,11 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
2347
2347
  return true;
2348
2348
  }
2349
2349
 
2350
+ /** Whether the receiver has to be walked even where attribution alone settles the call. */
2351
+ protected selectMustBeVisited(_method: J.MethodInvocation): boolean {
2352
+ return false;
2353
+ }
2354
+
2350
2355
  /**
2351
2356
  * Override method invocation comparison to include type attribution checking.
2352
2357
  * When types match semantically, we allow matching even if one has a receiver
@@ -2432,7 +2437,7 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
2432
2437
 
2433
2438
  // When types match (canSkipNameCheck = true), we can skip select comparison entirely.
2434
2439
  // This allows matching forwardRef() vs React.forwardRef() where types indicate same method.
2435
- if (!canSkipNameCheck) {
2440
+ if (!canSkipNameCheck || this.selectMustBeVisited(method)) {
2436
2441
  // Types didn't provide a match - must compare receivers structurally
2437
2442
  if ((method.select === undefined) !== (otherMethod.select === undefined)) {
2438
2443
  return this.structuralMismatch('select');
@@ -18,7 +18,7 @@ import {JavaScriptVisitor} from "../visitor";
18
18
  import {Comment, J, lastWhitespace, replaceLastWhitespace, Statement} from "../../java";
19
19
  import {create as produce, Draft} from "mutative";
20
20
  import {Cursor, isScope, isSourceFile, Tree} from "../../tree";
21
- import {BlankLinesStyle, getStyle, SpacesStyle, StyleKind, TabsAndIndentsStyle, WrappingAndBracesStyle} from "../style";
21
+ import {BlankLinesStyle, getStyle, PrettierStyle, SpacesStyle, StyleKind, TabsAndIndentsStyle, WrappingAndBracesStyle} from "../style";
22
22
  import {NamedStyles} from "../../style";
23
23
  import {produceAsync} from "../../visitor";
24
24
  import {Generator} from "../markers";
@@ -55,8 +55,9 @@ export const autoFormat = async <J2 extends J, P>(
55
55
  * 2. Styles from source file markers (NamedStyles)
56
56
  * 3. IntelliJ defaults
57
57
  *
58
- * When a PrettierStyle is present (either in the styles array or as a marker on the source file),
59
- * Prettier is used for formatting. Otherwise, built-in formatting visitors are used.
58
+ * A PrettierStyle, in the styles array or as a marker on the source file, formats through Prettier.
59
+ * The built-in visitors lay out a generated subtree Prettier cannot carry back, and any file
60
+ * without such a style.
60
61
  */
61
62
  export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
62
63
  private readonly styles?: NamedStyles<string>[];
@@ -67,23 +68,27 @@ export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
67
68
  }
68
69
 
69
70
  async visit<R extends J>(tree: Tree, p: P, cursor?: Cursor): Promise<R | undefined> {
70
- // Check for PrettierStyle in styles array or as marker on source file
71
- // If found, delegate entirely to Prettier (skip other formatting visitors)
72
71
  const prettierStyle = getPrettierStyle(tree, cursor, this.styles);
73
72
  if (prettierStyle) {
74
- return applyPrettierFormatting(tree as R, prettierStyle, p, cursor, this.stopAfter);
73
+ const formatted = await applyPrettierFormatting(tree as R, prettierStyle, p, cursor, this.stopAfter);
74
+ if (formatted !== undefined) {
75
+ return formatted;
76
+ }
75
77
  }
76
78
 
77
79
  // Style markers live on the source file, which is `tree` itself only when a whole file is being formatted
78
80
  const styleSource = (isSourceFile(tree) ? tree : cursor?.firstEnclosing(isSourceFile)) ?? tree;
81
+ const tabsAndIndents = getStyle(StyleKind.TabsAndIndentsStyle, styleSource, this.styles) as TabsAndIndentsStyle;
82
+ const spaces = getStyle(StyleKind.SpacesStyle, styleSource, this.styles) as SpacesStyle;
79
83
 
80
84
  const visitors = [
81
85
  new NormalizeWhitespaceVisitor(this.stopAfter),
82
86
  new MinimumViableSpacingVisitor(this.stopAfter),
83
87
  new BlankLinesVisitor(getStyle(StyleKind.BlankLinesStyle, styleSource, this.styles) as BlankLinesStyle, this.stopAfter),
84
88
  new WrappingAndBracesVisitor(getStyle(StyleKind.WrappingAndBracesStyle, styleSource, this.styles) as WrappingAndBracesStyle, this.stopAfter),
85
- new SpacesVisitor(getStyle(StyleKind.SpacesStyle, styleSource, this.styles) as SpacesStyle, this.stopAfter),
86
- new TabsAndIndentsVisitor(getStyle(StyleKind.TabsAndIndentsStyle, styleSource, this.styles) as TabsAndIndentsStyle, this.stopAfter),
89
+ new SpacesVisitor(prettierStyle ? spacesFrom(prettierStyle, spaces) : spaces, this.stopAfter),
90
+ new TabsAndIndentsVisitor(
91
+ prettierStyle ? tabsAndIndentsFrom(prettierStyle, tabsAndIndents) : tabsAndIndents, this.stopAfter),
87
92
  ]
88
93
 
89
94
  let t: R | undefined = tree as R;
@@ -98,6 +103,22 @@ export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
98
103
  }
99
104
  }
100
105
 
106
+ /** The brace spacing a Prettier configuration states, over the style the rest of the spacing comes from. */
107
+ function spacesFrom(prettierStyle: PrettierStyle, fallback: SpacesStyle): SpacesStyle {
108
+ // Prettier's default, in force for a configuration that does not name it
109
+ const bracketSpacing = prettierStyle.config.bracketSpacing !== false;
110
+ return {...fallback, within: {...fallback.within, objectLiteralBraces: bracketSpacing, es6ImportExportBraces: bracketSpacing}};
111
+ }
112
+
113
+ /** The widths a Prettier configuration states, over the style the rest of the layout comes from. */
114
+ function tabsAndIndentsFrom(prettierStyle: PrettierStyle, fallback: TabsAndIndentsStyle): TabsAndIndentsStyle {
115
+ const config = prettierStyle.config;
116
+ // Prettier's defaults, in force for a configuration that names neither
117
+ const tabWidth = typeof config.tabWidth === 'number' ? config.tabWidth : 2;
118
+ const useTabCharacter = config.useTabs === true;
119
+ return {...fallback, useTabCharacter, tabSize: tabWidth, indentSize: tabWidth, continuationIndent: tabWidth};
120
+ }
121
+
101
122
  export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
102
123
  constructor(private style: SpacesStyle, private stopAfter?: Tree) {
103
124
  super();
@@ -279,6 +300,14 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
279
300
  })
280
301
  }
281
302
 
303
+ protected async visitExportSpecifier(exportSpecifier: JS.ExportSpecifier, p: P): Promise<J | undefined> {
304
+ const ret = await super.visitExportSpecifier(exportSpecifier, p) as JS.ExportSpecifier;
305
+ return produce(ret, draft => {
306
+ // `type` is separated from the name it qualifies by that name's own prefix
307
+ draft.specifier.prefix.whitespace = draft.typeOnly.element ? " " : "";
308
+ });
309
+ }
310
+
282
311
  protected async visitForLoop(forLoop: J.ForLoop, p: P): Promise<J | undefined> {
283
312
  const ret = await super.visitForLoop(forLoop, p) as J.ForLoop;
284
313
  return produceAsync(ret, async draft => {
@@ -337,12 +366,16 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
337
366
  : "";
338
367
  }
339
368
  if (draft.importClause.namedBindings) {
340
- const hasDefaultImport = !!draft.importClause.name;
341
- if (hasDefaultImport || draft.importClause.typeOnly) {
342
- draft.importClause.namedBindings.prefix.whitespace = " ";
343
- }
369
+ // `type`, or a default name and its comma, stands between keyword and bindings
370
+ const afterClause = draft.importClause.name || draft.importClause.typeOnly ? " " : "";
344
371
  if (draft.importClause.namedBindings.kind == JS.Kind.NamedImports) {
345
372
  const ni = draft.importClause.namedBindings as Draft<JS.NamedImports>;
373
+ // For named imports the space before `{` sits in the container, as the parser
374
+ // writes it; a newline there is layout, which spacing does not decide
375
+ if (!ni.prefix.whitespace.includes("\n") && !ni.elements.before.whitespace.includes("\n")) {
376
+ ni.prefix.whitespace = "";
377
+ ni.elements.before.whitespace = afterClause;
378
+ }
346
379
  const isMultiLine = ni.elements.elements.some(e => e.element.prefix.whitespace.includes("\n"));
347
380
  if (!isMultiLine) {
348
381
  const braceSpace = this.style.within.es6ImportExportBraces ? " " : "";
@@ -355,6 +388,8 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
355
388
  lastAfter.whitespace = this.style.other.beforeComma ? " " : "";
356
389
  }
357
390
  }
391
+ } else if (afterClause) {
392
+ draft.importClause.namedBindings.prefix.whitespace = afterClause;
358
393
  }
359
394
  }
360
395
  }
@@ -365,6 +400,15 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
365
400
  })
366
401
  }
367
402
 
403
+ protected async visitImportSpecifier(importSpecifier: JS.ImportSpecifier, p: P): Promise<J | undefined> {
404
+ const ret = await super.visitImportSpecifier(importSpecifier, p) as JS.ImportSpecifier;
405
+ return produce(ret, draft => {
406
+ // The specifier's leading space is the brace and comma rules'; this is the
407
+ // gap between `type` and the name, which the name's own prefix carries
408
+ draft.specifier.prefix.whitespace = draft.importType.element ? " " : "";
409
+ });
410
+ }
411
+
368
412
  protected async visitIndexSignatureDeclaration(indexSignatureDeclaration: JS.IndexSignatureDeclaration, p: P): Promise<J | undefined> {
369
413
  const ret = await super.visitIndexSignatureDeclaration(indexSignatureDeclaration, p) as JS.IndexSignatureDeclaration;
370
414
  return produce(ret, draft => {