@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
@@ -34,6 +34,19 @@ import {Marker, replaceMarkerByKind} from "../markers";
34
34
  */
35
35
  export const sourceFileCache: Map<string, ts.SourceFile> = new Map();
36
36
 
37
+ // Pooled so a spec reuses the program the previous spec left behind; see `JavaScriptParser.parse`.
38
+ const parserPool: Map<string, JavaScriptParser> = new Map();
39
+
40
+ function pooledParser(relativeTo?: string): JavaScriptParser {
41
+ const key = relativeTo ?? "";
42
+ let parser = parserPool.get(key);
43
+ if (!parser) {
44
+ parser = new JavaScriptParser({sourceFileCache, relativeTo});
45
+ parserPool.set(key, parser);
46
+ }
47
+ return parser;
48
+ }
49
+
37
50
  // Automatically enable sourceFileCache for template parsing in tests
38
51
  setTemplateSourceFileCache(sourceFileCache);
39
52
 
@@ -211,7 +224,7 @@ export async function* npm(relativeTo: string, ...sourceSpecs: SourceSpec<any>[]
211
224
 
212
225
  yield {
213
226
  ...spec,
214
- parser: () => new JavaScriptParser({sourceFileCache, relativeTo}),
227
+ parser: () => pooledParser(relativeTo),
215
228
  // Add style marker before recipe runs if available
216
229
  // Compose with existing beforeRecipe if present
217
230
  beforeRecipe: styleMarker ? (sf: JS.CompilationUnit) => {
@@ -243,13 +256,28 @@ export function packageLockJson(before: string, after?: AfterRecipeText): Source
243
256
  };
244
257
  }
245
258
 
259
+ /**
260
+ * Gives a source file the style its own text is written in. A spec that formats its output is
261
+ * measured against the built-in defaults unless it opts in with
262
+ * `{...typescript(src), beforeRecipe: withDetectedStyle}`; a file parsed through {@link npm} is
263
+ * sampled alongside its siblings and needs no opt-in.
264
+ */
265
+ export async function withDetectedStyle(sourceFile: JS.CompilationUnit): Promise<JS.CompilationUnit> {
266
+ const detector = Autodetect.detector();
267
+ await detector.sample(sourceFile);
268
+ const detected = detector.build();
269
+ return produce(sourceFile, draft => {
270
+ draft.markers = replaceMarkerByKind(draft.markers, detected);
271
+ });
272
+ }
273
+
246
274
  export function javascript(before: string | null, after?: AfterRecipeText): SourceSpec<JS.CompilationUnit> {
247
275
  return {
248
276
  kind: JS.Kind.CompilationUnit,
249
277
  before: before,
250
278
  after: dedentAfter(after),
251
279
  ext: 'js',
252
- parser: ctx => new JavaScriptParser({sourceFileCache})
280
+ parser: ctx => pooledParser()
253
281
  };
254
282
  }
255
283
 
@@ -259,7 +287,7 @@ export function typescript(before: string | null, after?: AfterRecipeText): Sour
259
287
  before: before,
260
288
  after: dedentAfter(after),
261
289
  ext: 'ts',
262
- parser: () => new JavaScriptParser({sourceFileCache})
290
+ parser: () => pooledParser()
263
291
  };
264
292
  }
265
293
 
@@ -269,7 +297,7 @@ export function tsx(before: string | null, after?: AfterRecipeText): SourceSpec<
269
297
  before: before,
270
298
  after: dedentAfter(after),
271
299
  ext: 'tsx',
272
- parser: () => new JavaScriptParser({sourceFileCache})
300
+ parser: () => pooledParser()
273
301
  };
274
302
  }
275
303
 
@@ -279,6 +307,6 @@ export function jsx(before: string | null, after?: AfterRecipeText): SourceSpec<
279
307
  before: before,
280
308
  after: dedentAfter(after),
281
309
  ext: 'jsx',
282
- parser: () => new JavaScriptParser({sourceFileCache})
310
+ parser: () => pooledParser()
283
311
  };
284
312
  }
@@ -0,0 +1,449 @@
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, walk} from "./scope";
20
+ import {AddImportOptions, bindImport, existingImportBinding, memberName, moduleNameOf, RebindImport, requiredModuleOf} from "./add-import";
21
+ import {RemoveImport} from "./remove-import";
22
+ import {
23
+ AmdCalleeOptions, amdBlockOf, bindAmd, calleesOf, dependencyNames, derivedBindingName, enclosingAmdBlock,
24
+ parameterNames, RebindAmdDependency, RemoveAmdDependency
25
+ } from "./amd";
26
+
27
+ /**
28
+ * A bare string is shorthand for `{module}`, the overwhelmingly common call with nothing else to
29
+ * configure. A factory parameter binds a whole module under a name and nothing else, so on the AMD
30
+ * lane `member`, `typeOnly`, `sideEffectOnly`, `onlyIfReferenced`, `quoteStyle` and `style` do not
31
+ * apply: the first three refuse, and the rest have nothing to shape.
32
+ */
33
+ export interface MaybeBindOptions extends AddImportOptions {
34
+ /** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
35
+ amdCallee?: string | readonly string[];
36
+ }
37
+
38
+ /** A bare string is shorthand for `{module}`, the overwhelmingly common call with nothing else to configure. */
39
+ export interface MaybeUnbindOptions {
40
+ module: string;
41
+
42
+ /** The member to remove; unset removes every unused binding of the module. */
43
+ member?: string;
44
+
45
+ /** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
46
+ amdCallee?: string | readonly string[];
47
+ }
48
+
49
+ export interface MaybeRebindOptions {
50
+ from: {module: string; member?: string};
51
+ to: {module: string; member?: string};
52
+
53
+ /** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
54
+ amdCallee?: string | readonly string[];
55
+ }
56
+
57
+ export interface ModuleBindings {
58
+ /**
59
+ * The module `localName` refers to, or undefined when it is not a module binding — of any
60
+ * shape, so a namespace import's name answers here even though `maybeBind` will not reuse it
61
+ * for a plain `{module}` request. See `bindingOf`.
62
+ */
63
+ moduleOf(localName: string): string | undefined;
64
+
65
+ /**
66
+ * The local name bound to `module`, or undefined when nothing binds it — of any shape: a
67
+ * namespace import counts, even though `maybeBind` will not treat it as answering a plain
68
+ * `{module}` request. What a name can stand in for is `maybeBind`'s question, not this one's.
69
+ */
70
+ bindingOf(module: string): string | undefined;
71
+
72
+ /**
73
+ * The lane these bindings come from, and the one `maybeBind` would use. `"none"` is a
74
+ * plain script — no import, export, `require` binding, or enclosing AMD block — which
75
+ * `maybeBind` still turns into a module on request; a caller that must not do that checks
76
+ * for `"none"` itself.
77
+ */
78
+ readonly moduleSystem: "esm" | "amd" | "commonjs" | "none";
79
+ }
80
+
81
+ export function moduleBindings(
82
+ visitor: JavaScriptVisitor<any>,
83
+ options?: AmdCalleeOptions
84
+ ): ModuleBindings {
85
+ const amd = enclosingAmdBlock(visitor, options);
86
+ if (amd !== undefined) {
87
+ const modules = dependencyNames(amd.block);
88
+ const bindings = parameterNames(amd.block);
89
+ return {
90
+ moduleSystem: "amd",
91
+ moduleOf: localName => {
92
+ const index = bindings.indexOf(localName);
93
+ // `dependencyNames` pads a non-literal element with "" to hold its position; that
94
+ // filler names no module, so it answers neither lookup.
95
+ return index < 0 || modules[index] === "" ? undefined : modules[index];
96
+ },
97
+ bindingOf: module => {
98
+ const index = module === "" ? -1 : modules.indexOf(module);
99
+ return index < 0 ? undefined : bindings[index];
100
+ }
101
+ };
102
+ }
103
+
104
+ const cu = compilationUnitOf(visitor);
105
+ const bound = cu === undefined ? [] : moduleObjectBindings(cu);
106
+ return {
107
+ moduleSystem: cu === undefined ? "none" :
108
+ isCommonJs(cu) ? "commonjs" :
109
+ hasEsmSyntax(cu) ? "esm" : "none",
110
+ moduleOf: localName => bound.find(b => b.name === localName)?.module,
111
+ bindingOf: module => bound.find(b => b.module === module)?.name
112
+ };
113
+ }
114
+
115
+ export function isAmdBlock(node: J, options?: AmdCalleeOptions): boolean {
116
+ return node.kind === J.Kind.MethodInvocation &&
117
+ amdBlockOf(node as J.MethodInvocation, calleesOf(options)) !== undefined;
118
+ }
119
+
120
+ /** Whether a top-level statement already marks the file as a module: an import or any export form. */
121
+ function hasEsmSyntax(cu: JS.CompilationUnit): boolean {
122
+ return cu.statements.some(stmt => {
123
+ const element = stmt.element;
124
+ // `export {a, b}`, `export * from` and `export default` are their own statement kinds;
125
+ // `export class`/`function`/`const` instead carry `export` as a modifier on the
126
+ // declaration itself, the same way TypeScript's own AST models it.
127
+ const modifiers = (element as {modifiers?: J.Modifier[]} | undefined)?.modifiers;
128
+ return element?.kind === JS.Kind.Import ||
129
+ element?.kind === JS.Kind.ExportDeclaration ||
130
+ element?.kind === JS.Kind.ExportAssignment ||
131
+ (modifiers?.some(m => m.keyword === "export") ?? false);
132
+ }) || hasTopLevelAwait(cu);
133
+ }
134
+
135
+ /**
136
+ * Whether a statement holds an `await` outside any function of its own — legal only at module
137
+ * top level, unlike an `await` inside an `async function`, which says nothing about the file.
138
+ */
139
+ function hasTopLevelAwait(cu: JS.CompilationUnit): boolean {
140
+ let found = false;
141
+ walk(cu.statements, node => {
142
+ if (found) {
143
+ return false;
144
+ }
145
+ if (node.kind === JS.Kind.Await) {
146
+ found = true;
147
+ return false;
148
+ }
149
+ return node.kind !== J.Kind.MethodDeclaration && node.kind !== J.Kind.Lambda;
150
+ });
151
+ return found;
152
+ }
153
+
154
+ interface ModuleObjectBinding {
155
+ name: string;
156
+ module: string;
157
+
158
+ /**
159
+ * `"default"` and `"namespace"` bind different values — a namespace object's default sits at
160
+ * `.default` — so only one answers a whole-module request of the matching form. CommonJS has
161
+ * no such split: `"require"` answers either. A caller wanting the namespace asks for
162
+ * `member: "*"`, which is what a module exporting no default is bound by.
163
+ */
164
+ shape: "default" | "namespace" | "require";
165
+
166
+ /** A type-only import erases, so the name it binds stands for no value at runtime. */
167
+ typeOnly: boolean;
168
+ }
169
+
170
+ /** Whether the file binds its modules with `require`, which decides whether a create is possible. */
171
+ function isCommonJs(cu: JS.CompilationUnit): boolean {
172
+ if (cu.sourcePath.endsWith(".cjs") || cu.sourcePath.endsWith(".cts")) {
173
+ return true;
174
+ }
175
+ // Node treats these as ES modules regardless of what they contain, the same way
176
+ // `AddImport`'s own `determineImportStyle` reads them as ES6-preferring; `.js`/`.ts`/`.tsx`
177
+ // stay ambiguous and fall through to the statements below.
178
+ if (cu.sourcePath.endsWith(".mjs") || cu.sourcePath.endsWith(".mts")) {
179
+ return false;
180
+ }
181
+ if (hasEsmSyntax(cu)) {
182
+ return false;
183
+ }
184
+ return cu.statements.some(stmt =>
185
+ declarationsOf(stmt.element).some(d => requiredModule(d) !== undefined));
186
+ }
187
+
188
+ /** The module a `const X = require("m")` declaration names, for the one variable it declares. */
189
+ function requiredModule(declaration: J.VariableDeclarations): string | undefined {
190
+ const variables = declaration.variables;
191
+ const initializer = variables.length === 1 ? variables[0].element?.initializer?.element : undefined;
192
+ return initializer?.kind === J.Kind.MethodInvocation
193
+ ? requiredModuleOf(initializer as J.MethodInvocation)
194
+ : undefined;
195
+ }
196
+
197
+ /**
198
+ * The module a `const X = await import("m")` declaration names, for the one variable it declares.
199
+ * A dynamic import resolves to the module namespace object, the same value `import * as X`
200
+ * binds, so it shares that shape rather than getting one of its own.
201
+ */
202
+ function dynamicallyImportedModule(declaration: J.VariableDeclarations): string | undefined {
203
+ const variables = declaration.variables;
204
+ const initializer = variables.length === 1 ? variables[0].element?.initializer?.element : undefined;
205
+ const awaited = initializer?.kind === JS.Kind.Await ? (initializer as JS.Await).expression : undefined;
206
+ // `import(...)` is a keyword, not an identifier, so the parser maps it to `JS.FunctionCall`
207
+ // rather than the `J.MethodInvocation` an ordinary call gets.
208
+ if (awaited?.kind !== JS.Kind.FunctionCall) {
209
+ return undefined;
210
+ }
211
+ const call = awaited as JS.FunctionCall;
212
+ const callee = call.function?.element;
213
+ if (callee?.kind !== J.Kind.Identifier || (callee as J.Identifier).simpleName !== "import") {
214
+ return undefined;
215
+ }
216
+ const argument = call.arguments.elements[0]?.element;
217
+ return argument?.kind === J.Kind.Literal && typeof (argument as J.Literal).value === "string"
218
+ ? (argument as J.Literal).value as string
219
+ : undefined;
220
+ }
221
+
222
+ /** Bindings from top-level `const X = <moduleOf-recognised call>` declarations, all of one shape. */
223
+ function wholeModuleBindingsVia(
224
+ cu: JS.CompilationUnit,
225
+ moduleOf: (declaration: J.VariableDeclarations) => string | undefined,
226
+ shape: ModuleObjectBinding["shape"]
227
+ ): ModuleObjectBinding[] {
228
+ const bindings: ModuleObjectBinding[] = [];
229
+ for (const stmt of cu.statements) {
230
+ for (const declaration of declarationsOf(stmt.element)) {
231
+ const module = moduleOf(declaration);
232
+ const name = declaration.variables[0]?.element?.name;
233
+ if (module !== undefined && name?.kind === J.Kind.Identifier) {
234
+ bindings.push({name: (name as J.Identifier).simpleName, module, shape, typeOnly: false});
235
+ }
236
+ }
237
+ }
238
+ return bindings;
239
+ }
240
+
241
+ /** Only whole-module bindings: a named member does not name the module object. */
242
+ function moduleObjectBindings(cu: JS.CompilationUnit): ModuleObjectBinding[] {
243
+ const bindings: ModuleObjectBinding[] = [];
244
+ for (const stmt of cu.statements) {
245
+ const statement = stmt.element;
246
+ if (statement?.kind !== JS.Kind.Import) {
247
+ continue;
248
+ }
249
+ const jsImport = statement as JS.Import;
250
+ const specifier = jsImport.moduleSpecifier?.element;
251
+ if (specifier?.kind !== J.Kind.Literal) {
252
+ continue;
253
+ }
254
+ const module = (specifier as J.Literal).value;
255
+ if (typeof module !== "string") {
256
+ continue;
257
+ }
258
+ const clause = jsImport.importClause;
259
+ const typeOnly = clause?.typeOnly ?? false;
260
+ if (clause?.name?.element?.kind === J.Kind.Identifier) {
261
+ bindings.push({name: (clause.name.element as J.Identifier).simpleName, module, shape: "default", typeOnly});
262
+ }
263
+ // `namedBindings` is a `JS.Alias` only for `import * as X from "m"`; a renamed
264
+ // named import (`{a as b}`) nests its alias inside `NamedImports` instead.
265
+ const named = clause?.namedBindings;
266
+ if (named?.kind === JS.Kind.Alias) {
267
+ const alias = (named as JS.Alias).alias;
268
+ if (alias?.kind === J.Kind.Identifier) {
269
+ bindings.push({name: (alias as J.Identifier).simpleName, module, shape: "namespace", typeOnly});
270
+ }
271
+ }
272
+ }
273
+ bindings.push(...wholeModuleBindingsVia(cu, requiredModule, "require"));
274
+ bindings.push(...wholeModuleBindingsVia(cu, dynamicallyImportedModule, "namespace"));
275
+ return bindings;
276
+ }
277
+
278
+ /**
279
+ * Whether `binding` answers a whole-module request: the namespace form when `wantsNamespace`, and
280
+ * never a type-only import for a value, which erases and would leave the reference unbound.
281
+ */
282
+ function answersWholeModuleRequest(binding: ModuleObjectBinding, wantsNamespace: boolean, typeOnly: boolean): boolean {
283
+ return binding.typeOnly === typeOnly &&
284
+ (binding.shape === "require" || binding.shape === (wantsNamespace ? "namespace" : "default"));
285
+ }
286
+
287
+ /**
288
+ * A local binding for `module` or one of its members, creating one where none exists, or
289
+ * `undefined` where no safe binding is possible. The lane — AMD or ESM/CommonJS — is decided
290
+ * from the cursor; AMD binds only the whole module, so a `member` request there refuses rather
291
+ * than guess. `onlyIfReferenced` defaults to true, so the import may never appear.
292
+ */
293
+ export function maybeBind(
294
+ visitor: JavaScriptVisitor<any>,
295
+ options: MaybeBindOptions | string
296
+ ): string | undefined {
297
+ if (typeof options === "string") {
298
+ options = {module: options};
299
+ }
300
+ const module = moduleNameOf(options.module);
301
+ const key = memberName(options.member);
302
+
303
+ const amd = enclosingAmdBlock(visitor, options);
304
+ if (amd !== undefined) {
305
+ if (key !== undefined || options.sideEffectOnly) {
306
+ // Nor can a factory parameter load a module without binding it to a name.
307
+ return undefined;
308
+ }
309
+ return bindAmd(visitor, amd, module, options.alias ?? options.preferredName,
310
+ calleesOf(options), options.alias !== undefined);
311
+ }
312
+
313
+ const cu = compilationUnitOf(visitor);
314
+ const isWholeModule = !options.sideEffectOnly && (key === undefined || key === "*");
315
+ if (isWholeModule) {
316
+ const bound = cu && moduleObjectBindings(cu).find(b =>
317
+ b.module === module && answersWholeModuleRequest(b, key === "*", options.typeOnly ?? false) &&
318
+ // A pinned alias asks for a binding of that name, so another name for the same
319
+ // module does not answer it; `bindImport`'s own lookup applies the same rule.
320
+ (options.alias === undefined || b.name === options.alias));
321
+ if (bound !== undefined) {
322
+ return bound.name;
323
+ }
324
+ }
325
+
326
+ if (isWholeModule && options.preferredName === undefined && derivedBindingName(module) === undefined) {
327
+ // The module's last path segment is not a legal identifier, and the caller named no
328
+ // preference of its own — there is no name left to bind it to.
329
+ return undefined;
330
+ }
331
+
332
+ // `bindImport`'s own lookup finds and reuses a member-specific binding on its own, so
333
+ // refusal here only has to gate the point where it would create a new one.
334
+ const refuseCreate = cu !== undefined && isCommonJs(cu);
335
+ return bindImport(visitor, {
336
+ ...options,
337
+ preferredName: options.preferredName ?? (isWholeModule ? derivedBindingName(module) : undefined)
338
+ }, refuseCreate);
339
+ }
340
+
341
+ /**
342
+ * Removes `module`'s import(s) where unused, or one `member` of it — `'default'` and `'*'` select
343
+ * the default and namespace import regardless of local name. A member-scoped request does not
344
+ * apply to an AMD dependency, which binds a module rather than one of its members.
345
+ */
346
+ export function maybeUnbind(visitor: JavaScriptVisitor<any>, options: MaybeUnbindOptions | string): void {
347
+ if (typeof options === "string") {
348
+ options = {module: options};
349
+ }
350
+ const callees = calleesOf(options);
351
+ const queued = visitor.afterVisit || [];
352
+ if (!queued.some(v => v instanceof RemoveImport && v.module === options.module && v.member === options.member)) {
353
+ visitor.afterVisit.push(new RemoveImport(options.module, options.member));
354
+ }
355
+ // Both queue unconditionally so the caller need not know which lane the file uses: each
356
+ // visitor removes whatever matching construct it finds, ESM import or AMD dependency, and a
357
+ // file with both gets both removed. `amdCallee` says which block a call means, not how
358
+ // anything prints, so — unlike `preferredName`/`quoteStyle` on the bind side — it is part of
359
+ // the dedup key.
360
+ if (!queued.some(v => v instanceof RemoveAmdDependency && v.module === options.module &&
361
+ v.member === options.member && sameCallees(v.callees, callees))) {
362
+ visitor.afterVisit.push(new RemoveAmdDependency(options.module, options.member, callees));
363
+ }
364
+ }
365
+
366
+ function sameCallees(a: readonly string[], b: readonly string[]): boolean {
367
+ return a.length === b.length && a.every((callee, i) => callee === b[i]);
368
+ }
369
+
370
+ /** Which of an import clause's three slots `member` binds — `import`, `import *`, or `import {}`. */
371
+ function bindingShape(member: string | undefined): "default" | "namespace" | "named" {
372
+ const key = memberName(member);
373
+ return key === undefined ? "default" : key === "*" ? "namespace" : "named";
374
+ }
375
+
376
+ /**
377
+ * Moves the binding for `from` to `to`, keeping the local name it already had — the primitive
378
+ * behind a member rename or a module move. Returns `undefined`, changing nothing, where the move
379
+ * is not safely expressible; the refusals are listed in CLAUDE.md: JavaScript module bindings.
380
+ */
381
+ export function maybeRebind(visitor: JavaScriptVisitor<any>, options: MaybeRebindOptions): string | undefined {
382
+ const amd = enclosingAmdBlock(visitor, options);
383
+ if (amd !== undefined) {
384
+ if (memberName(options.from.member) !== undefined || memberName(options.to.member) !== undefined) {
385
+ return undefined;
386
+ }
387
+ const index = dependencyNames(amd.block).indexOf(options.from.module);
388
+ const binding = index < 0 ? undefined : parameterNames(amd.block)[index];
389
+ if (binding === undefined) {
390
+ return undefined;
391
+ }
392
+ const callees = calleesOf(options);
393
+ const from = options.from.module;
394
+ if (!(visitor.afterVisit || []).some(v => v instanceof RebindAmdDependency &&
395
+ v.blockId === amd.call.id && v.fromModule === from && sameCallees(v.callees, callees))) {
396
+ visitor.afterVisit.push(new RebindAmdDependency(amd.call.id, from, options.to.module, callees));
397
+ }
398
+ return binding;
399
+ }
400
+
401
+ const cu = compilationUnitOf(visitor);
402
+ if (cu === undefined) {
403
+ return undefined;
404
+ }
405
+ const existing = existingImportBinding(cu, options.from.module, options.from.member);
406
+ if (existing === undefined) {
407
+ return undefined;
408
+ }
409
+ if (existing.onlyMemberOfStatement && bindingShape(options.from.member) !== bindingShape(options.to.member)) {
410
+ return undefined;
411
+ }
412
+ if (!existing.onlyMemberOfStatement && isCommonJs(cu)) {
413
+ return undefined;
414
+ }
415
+ visitor.afterVisit.push(new RebindImport(options.from, options.to, existing.localName));
416
+ return existing.localName;
417
+ }
418
+
419
+ /**
420
+ * @deprecated Use {@link maybeUnbind} instead — this is a call-shape change, not a behaviour
421
+ * change: `maybeRemoveImport(v, module, member)` is `maybeUnbind(v, {module, member})`.
422
+ */
423
+ export function maybeRemoveImport(visitor: JavaScriptVisitor<any>, module: string, member?: string): void {
424
+ maybeUnbind(visitor, {module, member});
425
+ }
426
+
427
+ /**
428
+ * @deprecated Use {@link maybeBind} instead. Beyond binding through an AMD factory parameter,
429
+ * `maybeBind` returns `undefined` rather than creating an import where the file binds its modules
430
+ * with `require`, or where no legal identifier can be derived from the module and none was named.
431
+ */
432
+ export function maybeAddImport(
433
+ visitor: JavaScriptVisitor<any>,
434
+ options: AddImportOptions & { sideEffectOnly: true }
435
+ ): undefined;
436
+ export function maybeAddImport(
437
+ visitor: JavaScriptVisitor<any>,
438
+ options: AddImportOptions & { sideEffectOnly?: false }
439
+ ): string | undefined;
440
+ export function maybeAddImport(
441
+ visitor: JavaScriptVisitor<any>,
442
+ options: AddImportOptions
443
+ ): string | undefined;
444
+ export function maybeAddImport(
445
+ visitor: JavaScriptVisitor<any>,
446
+ options: AddImportOptions
447
+ ): string | undefined {
448
+ return maybeBind(visitor, options);
449
+ }
@@ -2410,11 +2410,6 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
2410
2410
  }
2411
2411
  }
2412
2412
 
2413
- // If neither has type, use structural comparison
2414
- if (!method.methodType && !otherMethod.methodType) {
2415
- return super.visitMethodInvocation(method, other);
2416
- }
2417
-
2418
2413
  // If both have types with FQ declaring types, verify they're compatible
2419
2414
  // (This prevents matching completely different methods like util.isArray vs util.isBoolean)
2420
2415
  if (method.methodType && otherMethod.methodType) {
@@ -2450,12 +2445,12 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
2450
2445
  }
2451
2446
  // else: types matched, skip select comparison (allows namespace vs named imports)
2452
2447
 
2453
- // Compare type parameters
2454
- if ((method.typeParameters === undefined) !== (otherMethod.typeParameters === undefined)) {
2455
- return this.structuralMismatch('typeParameters');
2456
- }
2448
+ // A pattern that spells out no type arguments says nothing about them, as with parentheses
2449
+ if (method.typeParameters) {
2450
+ if (!otherMethod.typeParameters) {
2451
+ return this.structuralMismatch('typeParameters');
2452
+ }
2457
2453
 
2458
- if (method.typeParameters && otherMethod.typeParameters) {
2459
2454
  await this.visitContainerProperty('typeParameters', method.typeParameters, otherMethod.typeParameters);
2460
2455
  if (!this.match) return method;
2461
2456
  }
@@ -2475,6 +2470,32 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
2475
2470
  return method;
2476
2471
  }
2477
2472
 
2473
+ /**
2474
+ * A call whose callee is not a plain identifier, such as `getFn()(x)`, `arr[0](x)` or `fn?.(x)`.
2475
+ * Type arguments constrain it under the same rule as `visitMethodInvocation`.
2476
+ */
2477
+ override async visitFunctionCall(functionCall: JS.FunctionCall, other: J): Promise<J | undefined> {
2478
+ if (other.kind !== JS.Kind.FunctionCall) {
2479
+ return this.kindMismatch();
2480
+ }
2481
+
2482
+ const otherFunctionCall = other as JS.FunctionCall;
2483
+ if (functionCall.typeParameters) {
2484
+ if (!otherFunctionCall.typeParameters) {
2485
+ return this.structuralMismatch('typeParameters');
2486
+ }
2487
+
2488
+ await this.visitContainerProperty('typeParameters', functionCall.typeParameters, otherFunctionCall.typeParameters);
2489
+ if (!this.match) return functionCall;
2490
+ }
2491
+
2492
+ // Type arguments are settled, so they come off both sides and the rest compares property by property
2493
+ const patternCall: JS.FunctionCall = {...functionCall, typeParameters: undefined};
2494
+ const targetCall: JS.FunctionCall = {...otherFunctionCall, typeParameters: undefined};
2495
+ await super.visitFunctionCall(patternCall, targetCall);
2496
+ return functionCall;
2497
+ }
2498
+
2478
2499
  /**
2479
2500
  * Override identifier comparison to include:
2480
2501
  * 1. Type checking for field access
@@ -2520,6 +2541,10 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
2520
2541
  * code with typeExpression.
2521
2542
  */
2522
2543
  override async visitVariableDeclarations(variableDeclarations: J.VariableDeclarations, other: J): Promise<J | undefined> {
2544
+ if (other.kind !== J.Kind.VariableDeclarations) {
2545
+ return this.kindMismatch();
2546
+ }
2547
+
2523
2548
  const otherVariableDeclarations = other as J.VariableDeclarations;
2524
2549
 
2525
2550
  // Visit leading annotations
@@ -2578,6 +2603,10 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
2578
2603
  * code with returnTypeExpression.
2579
2604
  */
2580
2605
  override async visitMethodDeclaration(methodDeclaration: J.MethodDeclaration, other: J): Promise<J | undefined> {
2606
+ if (other.kind !== J.Kind.MethodDeclaration) {
2607
+ return this.kindMismatch();
2608
+ }
2609
+
2581
2610
  const otherMethodDeclaration = other as J.MethodDeclaration;
2582
2611
 
2583
2612
  // Visit leading annotations
@@ -17,7 +17,7 @@ import {JS} from "../tree";
17
17
  import {JavaScriptVisitor} from "../visitor";
18
18
  import {Comment, J, lastWhitespace, replaceLastWhitespace, Statement} from "../../java";
19
19
  import {create as produce, Draft} from "mutative";
20
- import {Cursor, isScope, Tree} from "../../tree";
20
+ import {Cursor, isScope, isSourceFile, Tree} from "../../tree";
21
21
  import {BlankLinesStyle, getStyle, SpacesStyle, StyleKind, TabsAndIndentsStyle, WrappingAndBracesStyle} from "../style";
22
22
  import {NamedStyles} from "../../style";
23
23
  import {produceAsync} from "../../visitor";
@@ -74,13 +74,16 @@ export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
74
74
  return applyPrettierFormatting(tree as R, prettierStyle, p, cursor, this.stopAfter);
75
75
  }
76
76
 
77
+ // Style markers live on the source file, which is `tree` itself only when a whole file is being formatted
78
+ const styleSource = (isSourceFile(tree) ? tree : cursor?.firstEnclosing(isSourceFile)) ?? tree;
79
+
77
80
  const visitors = [
78
81
  new NormalizeWhitespaceVisitor(this.stopAfter),
79
82
  new MinimumViableSpacingVisitor(this.stopAfter),
80
- new BlankLinesVisitor(getStyle(StyleKind.BlankLinesStyle, tree, this.styles) as BlankLinesStyle, this.stopAfter),
81
- new WrappingAndBracesVisitor(getStyle(StyleKind.WrappingAndBracesStyle, tree, this.styles) as WrappingAndBracesStyle, this.stopAfter),
82
- new SpacesVisitor(getStyle(StyleKind.SpacesStyle, tree, this.styles) as SpacesStyle, this.stopAfter),
83
- new TabsAndIndentsVisitor(getStyle(StyleKind.TabsAndIndentsStyle, tree, this.styles) as TabsAndIndentsStyle, this.stopAfter),
83
+ new BlankLinesVisitor(getStyle(StyleKind.BlankLinesStyle, styleSource, this.styles) as BlankLinesStyle, this.stopAfter),
84
+ 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),
84
87
  ]
85
88
 
86
89
  let t: R | undefined = tree as R;
@@ -227,11 +230,7 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
227
230
  // Apply beforeComma rule to all elements except the last
228
231
  // (last element's after is before closing bracket, not a comma)
229
232
  for (let i = 0; i < draft.elements.length - 1; i++) {
230
- const afterWs = draft.elements[i].after.whitespace;
231
- // Preserve newlines - only adjust when on same line
232
- if (!afterWs.includes("\n")) {
233
- draft.elements[i].after.whitespace = this.style.other.beforeComma ? " " : "";
234
- }
233
+ this.spaceAfterRightPaddedDraft(draft.elements[i], this.style.other.beforeComma);
235
234
  }
236
235
  }
237
236
  if (draft.elements.length > 1) {