@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
@@ -0,0 +1,338 @@
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 {Cursor, isTree} from "../tree";
17
+ import {J} from "../java";
18
+ import {JS} from "./tree";
19
+ // scope.ts sits below the visitor, so this stays type-only.
20
+ import type {JavaScriptVisitor} from "./visitor";
21
+
22
+ /** The names code at some position can reach unqualified. */
23
+ export interface Scope {
24
+ /** Whether this scope or one enclosing it binds `name`. */
25
+ declares(name: string): boolean;
26
+
27
+ names(): ReadonlySet<string>;
28
+ }
29
+
30
+ /**
31
+ * What the code at `cursor` can name: what every enclosing scope binds, plus the declarations that
32
+ * hoist into those scopes from blocks that do not enclose it. Where a declaration's shape leaves its
33
+ * reach unreadable the answer counts it rather than miss it, so a name reported here may not truly
34
+ * reach the cursor.
35
+ */
36
+ export function scopeOf(cursor: Cursor): Scope {
37
+ let resolved: Set<string> | undefined;
38
+ const names = () => resolved ??= namesInScope(cursor);
39
+ return {declares: name => names().has(name), names};
40
+ }
41
+
42
+ /**
43
+ * Every name the file declares, wherever it sits. A binding the whole file shares is referenced from
44
+ * sites that are not known when it is named, so it has to steer clear of every name that could
45
+ * shadow it at one of them.
46
+ */
47
+ export function namesDeclaredIn(cu: JS.CompilationUnit): ReadonlySet<string> {
48
+ return namesDeclaredWithin(cu.statements, cu);
49
+ }
50
+
51
+ /** As {@link namesDeclaredIn}, over one subtree, for a binding shared across only that much of a file. */
52
+ export function namesDeclaredWithin(node: unknown, cacheKey: object = node as object): ReadonlySet<string> {
53
+ const cached = declared.get(cacheKey);
54
+ if (cached) {
55
+ return cached;
56
+ }
57
+
58
+ const names = new Set<string>();
59
+ const collect = (node: any): boolean => {
60
+ declarationNames(node).forEach(name => names.add(name));
61
+ if (node.kind !== J.Kind.ClassDeclaration) {
62
+ return true;
63
+ }
64
+ // A member's name is not one the file binds, though the code inside one still declares names.
65
+ for (const member of (node as J.ClassDeclaration).body?.statements ?? []) {
66
+ const element = unwrap(member);
67
+ walk(element, node => node === element || collect(node));
68
+ }
69
+ return false;
70
+ };
71
+ walk(node, collect);
72
+
73
+ declared.set(cacheKey, names);
74
+ return names;
75
+ }
76
+
77
+ /**
78
+ * Every name a binding pattern introduces. `member` is the property a name takes its value from,
79
+ * which only a name an object pattern binds directly has: anything deeper reads a property of a
80
+ * property, an array element is chosen by position, and a rest name gathers what nothing claimed.
81
+ */
82
+ export function bindingNames(pattern: J | undefined): { name: string; member?: string }[] {
83
+ switch (pattern?.kind) {
84
+ case J.Kind.Identifier: {
85
+ const simpleName = (pattern as J.Identifier).simpleName;
86
+ return simpleName ? [{name: simpleName}] : [];
87
+ }
88
+ case JS.Kind.Spread:
89
+ return bindingNames((pattern as JS.Spread).expression);
90
+ case JS.Kind.ArrayBindingPattern:
91
+ return unnamedMembers((pattern as JS.ArrayBindingPattern).elements.elements
92
+ .flatMap(element => bindingNames(unwrap(element))));
93
+ case JS.Kind.ObjectBindingPattern:
94
+ return (pattern as JS.ObjectBindingPattern).bindings.elements
95
+ .flatMap(element => bindingNames(unwrap(element)));
96
+ case JS.Kind.BindingElement: {
97
+ const element = pattern as JS.BindingElement;
98
+ if (element.name?.kind !== J.Kind.Identifier) {
99
+ return unnamedMembers(bindingNames(element.name as J));
100
+ }
101
+ const name = (element.name as J.Identifier).simpleName;
102
+ const propertyName = unwrap(element.propertyName);
103
+ return [{
104
+ name,
105
+ member: propertyName?.kind === J.Kind.Identifier ? (propertyName as J.Identifier).simpleName : name
106
+ }];
107
+ }
108
+ default:
109
+ return [];
110
+ }
111
+ }
112
+
113
+ function unnamedMembers(bound: { name: string }[]): { name: string }[] {
114
+ return bound.map(({name}) => ({name}));
115
+ }
116
+
117
+ function namesInScope(cursor: Cursor): Set<string> {
118
+ const names = new Set<string>();
119
+ for (let c: Cursor | undefined = cursor; c; c = c.parent) {
120
+ for (const name of frameBindings(c.value, c.parent?.value)) {
121
+ names.add(name);
122
+ }
123
+ }
124
+ return names;
125
+ }
126
+
127
+ /** What one node on the cursor path binds, `parent` being the node it hangs from. */
128
+ function frameBindings(node: any, parent: any): string[] {
129
+ switch (node?.kind) {
130
+ case JS.Kind.CompilationUnit: {
131
+ const statements = (node as JS.CompilationUnit).statements;
132
+ return [...declaredNames(statements), ...hoistedNames(statements)];
133
+ }
134
+ case J.Kind.Block:
135
+ // A class body holds members, which are reached through an instance rather than by name.
136
+ return parent?.kind === J.Kind.ClassDeclaration ? [] : declaredNames((node as J.Block).statements);
137
+ case J.Kind.MethodDeclaration: {
138
+ const method = node as J.MethodDeclaration;
139
+ // A function expression is the only function whose own name its body reaches: a
140
+ // declaration's name belongs to the enclosing block, a method's to an instance.
141
+ const self = parent?.kind === JS.Kind.StatementExpression ? bindingNames(method.name) : [];
142
+ return [
143
+ ...self.map(bound => bound.name),
144
+ ...declaredNames(method.parameters.elements),
145
+ ...hoistedNames(method.body)
146
+ ];
147
+ }
148
+ case J.Kind.Lambda: {
149
+ const lambda = node as J.Lambda;
150
+ return [...declaredNames(lambda.parameters.parameters), ...hoistedNames(lambda.body)];
151
+ }
152
+ case J.Kind.ClassDeclaration:
153
+ return bindingNames((node as J.ClassDeclaration).name).map(bound => bound.name);
154
+ case J.Kind.TryCatch:
155
+ return declaredNames([(node as J.Try.Catch).parameter.tree]);
156
+ case J.Kind.ForLoop:
157
+ return declaredNames((node as J.ForLoop).control.init);
158
+ case J.Kind.ForEachLoop:
159
+ return declaredNames([(node as J.ForEachLoop).control.variable]);
160
+ case JS.Kind.ForInLoop:
161
+ return declaredNames([(node as JS.ForInLoop).control.variable]);
162
+ default:
163
+ return [];
164
+ }
165
+ }
166
+
167
+ /** The names statements declare directly in the scope holding them. */
168
+ function declaredNames(statements: any[]): string[] {
169
+ return statements.flatMap(statement => declarationNames(unwrap(statement)));
170
+ }
171
+
172
+ function declarationNames(statement: any): string[] {
173
+ switch (statement?.kind) {
174
+ case JS.Kind.Import:
175
+ return importNames(statement as JS.Import);
176
+ case J.Kind.VariableDeclarations:
177
+ return (statement as J.VariableDeclarations).variables
178
+ .flatMap(variable => bindingNames(unwrap(variable)?.name))
179
+ .map(bound => bound.name);
180
+ case JS.Kind.ScopedVariableDeclarations:
181
+ return declaredNames((statement as JS.ScopedVariableDeclarations).variables);
182
+ case J.Kind.MethodDeclaration:
183
+ return bindingNames((statement as J.MethodDeclaration).name).map(bound => bound.name);
184
+ case J.Kind.ClassDeclaration:
185
+ return bindingNames((statement as J.ClassDeclaration).name).map(bound => bound.name);
186
+ case JS.Kind.NamespaceDeclaration:
187
+ return bindingNames(unwrap((statement as JS.NamespaceDeclaration).name)).map(bound => bound.name);
188
+ case JS.Kind.TypeDeclaration:
189
+ return bindingNames(unwrap((statement as JS.TypeDeclaration).name)).map(bound => bound.name);
190
+ case J.Kind.Case:
191
+ // The cases of a switch share the block it opens, so each one's declarations bind in all.
192
+ return declaredNames((statement as J.Case).statements.elements);
193
+ default:
194
+ return [];
195
+ }
196
+ }
197
+
198
+ /** The names an import binds, which for an aliased or namespace specifier is the alias. */
199
+ function importNames(jsImport: JS.Import): string[] {
200
+ const importClause = jsImport.importClause;
201
+ if (!importClause) {
202
+ return [];
203
+ }
204
+ const names = bindingNames(unwrap(importClause.name)).map(bound => bound.name);
205
+ const namedBindings = importClause.namedBindings;
206
+ if (namedBindings?.kind === JS.Kind.NamedImports) {
207
+ for (const element of (namedBindings as JS.NamedImports).elements.elements) {
208
+ const specifier = unwrap(element);
209
+ if (specifier?.kind === JS.Kind.ImportSpecifier) {
210
+ names.push(...aliasedName((specifier as JS.ImportSpecifier).specifier));
211
+ }
212
+ }
213
+ } else {
214
+ names.push(...aliasedName(namedBindings));
215
+ }
216
+ return names;
217
+ }
218
+
219
+ function aliasedName(specifier: J | undefined): string[] {
220
+ const name = specifier?.kind === JS.Kind.Alias ? (specifier as JS.Alias).alias : specifier;
221
+ return bindingNames(name).map(bound => bound.name);
222
+ }
223
+
224
+ const blockScoped = new Set(['let', 'const', 'using']);
225
+
226
+ // A walk of an immutable subtree has one answer, so it runs once. Keyed on the subtree itself, a
227
+ // replaced one is walked afresh: every call site in a function asks what that body hoists, and every
228
+ // import added to a file asks what that file declares.
229
+ const hoisted = new WeakMap<object, string[]>();
230
+ const declared = new WeakMap<object, ReadonlySet<string>>();
231
+
232
+ /**
233
+ * The names blocks under `scope` hoist out to it. A `var` or function declaration reaches the whole
234
+ * function it sits in, so one nested in a block is in scope outside that block; only a `let`, `const`
235
+ * or `using` keyword says otherwise.
236
+ */
237
+ function hoistedNames(scope: any): string[] {
238
+ if (typeof scope !== 'object' || scope === null) {
239
+ return [];
240
+ }
241
+ const cached = hoisted.get(scope);
242
+ if (cached) {
243
+ return cached;
244
+ }
245
+
246
+ const names: string[] = [];
247
+ const collect = (node: any): boolean => {
248
+ switch (node.kind) {
249
+ case J.Kind.MethodDeclaration:
250
+ names.push(...declarationNames(node));
251
+ return false;
252
+ case J.Kind.VariableDeclarations:
253
+ case JS.Kind.ScopedVariableDeclarations:
254
+ if (!((node.modifiers ?? []) as J.Modifier[]).some(m => blockScoped.has(m.keyword!))) {
255
+ names.push(...declarationNames(node));
256
+ }
257
+ return false;
258
+ case J.Kind.TryCatch:
259
+ // A catch parameter carries no keyword to read, and binds only in the catch block.
260
+ walk((node as J.Try.Catch).body, collect);
261
+ return false;
262
+ case JS.Kind.StatementExpression:
263
+ // A function or class used as an expression names itself for its own body alone.
264
+ return false;
265
+ case J.Kind.Lambda:
266
+ case J.Kind.ClassDeclaration:
267
+ return false;
268
+ default:
269
+ return true;
270
+ }
271
+ };
272
+ walk(scope, collect);
273
+ hoisted.set(scope, names);
274
+ return names;
275
+ }
276
+
277
+ /** Visits every LST node under `node`, leaving a subtree unvisited where `visit` returns false. */
278
+ export function walk(node: unknown, visit: (node: any) => boolean): void {
279
+ if (Array.isArray(node)) {
280
+ node.forEach(child => walk(child, visit));
281
+ return;
282
+ }
283
+ const kind = (node as any)?.kind;
284
+ if (kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded) {
285
+ walk((node as J.RightPadded<any>).element, visit);
286
+ } else if (kind === J.Kind.Container) {
287
+ walk((node as J.Container<any>).elements, visit);
288
+ } else if (isTree(node) && visit(node)) {
289
+ // Markers hang off a node rather than being part of the code it holds.
290
+ Object.entries(node).forEach(([key, value]) => key !== 'markers' && walk(value, visit));
291
+ }
292
+ }
293
+
294
+ /** The element a padding wrapper holds, or the node itself. */
295
+ function unwrap(node: any): any {
296
+ return node?.kind === J.Kind.RightPadded || node?.kind === J.Kind.LeftPadded ? unwrap(node.element) : node;
297
+ }
298
+
299
+ /** `cursor` is protected on `TreeVisitor` and these APIs are free functions, so reaching it takes a cast. */
300
+ export function cursorOf(visitor: JavaScriptVisitor<any>): Cursor | undefined {
301
+ return (visitor as unknown as {cursor?: Cursor}).cursor;
302
+ }
303
+
304
+ /** The compilation unit a cursor sits in, or the one a visitor is currently positioned in. */
305
+ export function compilationUnitOf(from: Cursor | JavaScriptVisitor<any>): JS.CompilationUnit | undefined {
306
+ const cursor = from instanceof Cursor ? from : cursorOf(from);
307
+ return cursor?.firstEnclosing((v): v is JS.CompilationUnit => v?.kind === JS.Kind.CompilationUnit);
308
+ }
309
+
310
+ /** `preferred`, or the first `preferred_N` that `isTaken` rejects, so a new name never shadows one in scope. */
311
+ export function deconflict(preferred: string, isTaken: (name: string) => boolean): string {
312
+ if (!isTaken(preferred)) {
313
+ return preferred;
314
+ }
315
+ for (let suffix = 1; ; suffix++) {
316
+ const candidate = `${preferred}_${suffix}`;
317
+ if (!isTaken(candidate)) {
318
+ return candidate;
319
+ }
320
+ }
321
+ }
322
+
323
+ /**
324
+ * The `J.VariableDeclarations` a statement declares — itself for a bare `const x = …`, one per
325
+ * declarator for `const a = …, b = …`, which the parser wraps in a `JS.ScopedVariableDeclarations`
326
+ * instead.
327
+ */
328
+ export function declarationsOf(statement: J | undefined): J.VariableDeclarations[] {
329
+ if (statement?.kind === J.Kind.VariableDeclarations) {
330
+ return [statement as J.VariableDeclarations];
331
+ }
332
+ if (statement?.kind === JS.Kind.ScopedVariableDeclarations) {
333
+ return (statement as JS.ScopedVariableDeclarations).variables
334
+ .map(v => v.element)
335
+ .filter((v): v is J.VariableDeclarations => v?.kind === J.Kind.VariableDeclarations);
336
+ }
337
+ return [];
338
+ }
@@ -14,10 +14,9 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import {NamedStyles, Style} from "../style";
17
+ import {isNamedStyles, NamedStyles, Style} from "../style";
18
18
  import {randomId} from "../uuid";
19
19
  import {Tree} from "../tree";
20
- import {MarkersKind} from "../markers";
21
20
 
22
21
  export const JavaScriptStyles = {
23
22
  IntelliJ: "org.openrewrite.javascript.style.IntelliJ",
@@ -562,13 +561,7 @@ export namespace IntelliJ {
562
561
  }
563
562
 
564
563
  export function styleFromSourceFile(styleKind: string, sourceFile: Tree): Style | undefined {
565
- const namedStyles = sourceFile.markers.markers.filter(marker => marker.kind === MarkersKind.NamedStyles) as NamedStyles[];
566
- const candidate = namedStyles.map(namedStyle => namedStyle.styles).flat()
567
- .find(style => style.kind === styleKind)
568
- if (candidate) {
569
- return candidate;
570
- }
571
- return IntelliJ.TypeScript.defaults.styles.find(style => style.kind === styleKind) as Style;
564
+ return getStyle(styleKind, sourceFile);
572
565
  }
573
566
 
574
567
  /**
@@ -591,8 +584,7 @@ export function getStyle(styleKind: string, sourceFile: Tree, styles?: NamedStyl
591
584
  }
592
585
 
593
586
  // Then check source file markers
594
- const namedStyles = sourceFile.markers.markers.filter(marker => marker.kind === MarkersKind.NamedStyles) as NamedStyles[];
595
- for (const namedStyle of namedStyles) {
587
+ for (const namedStyle of sourceFile.markers.markers.filter(isNamedStyles)) {
596
588
  const found = namedStyle.styles.find(s => s.kind === styleKind);
597
589
  if (found) {
598
590
  return found;
@@ -0,0 +1,99 @@
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, Type} from '../../java';
17
+ import {JavaScriptVisitor} from '../visitor';
18
+ import {Cursor} from '../../tree';
19
+
20
+ /**
21
+ * Renames the identifiers a template uses for its declared bindings to the names the file
22
+ * actually binds. Runs before parameter substitution, so only the template's own code is in
23
+ * scope and a caller's captured code is never rewritten.
24
+ */
25
+ export async function renameBindings<T extends J>(tree: T, renames: Record<string, string>, modules: Record<string, string>): Promise<T> {
26
+ return new RenameBindingsVisitor(renames, modules).visit(tree, undefined) as Promise<T>;
27
+ }
28
+
29
+ class RenameBindingsVisitor extends JavaScriptVisitor<undefined> {
30
+ constructor(private readonly renames: Record<string, string>,
31
+ private readonly modules: Record<string, string>) {
32
+ super();
33
+ }
34
+
35
+ override async visitIdentifier(identifier: J.Identifier, p: undefined): Promise<J | undefined> {
36
+ const renamed = this.renames[identifier.simpleName];
37
+ if (renamed === undefined || renamed === identifier.simpleName) {
38
+ return identifier;
39
+ }
40
+
41
+ // Attribution settles it where the context import resolved. It is absent for a module the
42
+ // parse could not reach, and the identifier's position decides instead.
43
+ const resolved = resolvedModule(identifier);
44
+ const refersToBinding = resolved !== undefined
45
+ ? resolved === this.modules[identifier.simpleName]
46
+ : !namesItsParent(this.cursor, identifier);
47
+
48
+ return refersToBinding ? {...identifier, simpleName: renamed} as J.Identifier : identifier;
49
+ }
50
+ }
51
+
52
+ /** Whether the parent is naming this identifier — as a property, a method, a declaration — rather than referencing it. */
53
+ function namesItsParent(cursor: Cursor, identifier: J.Identifier): boolean {
54
+ let c: Cursor | undefined = cursor.parent;
55
+ while (c && isPadding(c.value)) {
56
+ c = c.parent;
57
+ }
58
+ const parent = c?.value as { kind?: string; name?: unknown; select?: unknown } | undefined;
59
+
60
+ // A call names a member of whatever it selects from. With nothing selected there is no member,
61
+ // and its `name` is a reference to the function being called.
62
+ if (parent?.kind === J.Kind.MethodInvocation && !parent.select) {
63
+ return false;
64
+ }
65
+
66
+ const name = parent?.name;
67
+ return name === identifier || (name as { element?: unknown } | undefined)?.element === identifier;
68
+ }
69
+
70
+ function isPadding(value: unknown): boolean {
71
+ const kind = (value as { kind?: string } | undefined)?.kind;
72
+ return kind === J.Kind.RightPadded || kind === J.Kind.LeftPadded || kind === J.Kind.Container;
73
+ }
74
+
75
+ /** The module an identifier's attribution traces back to, following the owning-class chain to its root. */
76
+ function resolvedModule(identifier: J.Identifier): string | undefined {
77
+ const fieldType = identifier.fieldType;
78
+ if (fieldType?.kind === Type.Kind.Variable) {
79
+ const owner = (fieldType as Type.Variable).owner;
80
+ return owner && Type.isClass(owner) ? rootName(owner as Type.Class) : undefined;
81
+ }
82
+ const type = identifier.type;
83
+ if (type && Type.isMethod(type)) {
84
+ const declaring = (type as Type.Method).declaringType;
85
+ return declaring ? rootName(declaring as Type.Class) : undefined;
86
+ }
87
+ if (type && Type.isClass(type)) {
88
+ return rootName(type as Type.Class);
89
+ }
90
+ return undefined;
91
+ }
92
+
93
+ function rootName(classType: Type.Class): string {
94
+ let current: Type.Class = classType;
95
+ while (current.owningClass && Type.isClass(current.owningClass)) {
96
+ current = current.owningClass as Type.Class;
97
+ }
98
+ return Type.FullyQualified.getFullyQualifiedName(current);
99
+ }
@@ -85,6 +85,17 @@ class CaptureMapImpl implements CaptureMap {
85
85
  }
86
86
  }
87
87
 
88
+ /** A capture found in a pattern, and the target node it binds to. */
89
+ interface CaptureAt {
90
+ marker: CaptureMarker;
91
+ target: J;
92
+ }
93
+
94
+ /** Parentheses that `JavaScriptSemanticComparatorVisitor.unwrap()` sees through. */
95
+ function isParenthesized(j: any): boolean {
96
+ return j?.kind === J.Kind.Parentheses || j?.kind === J.Kind.ControlParentheses;
97
+ }
98
+
88
99
  /**
89
100
  * A comparator for pattern matching that is lenient about optional properties.
90
101
  * Allows patterns without type annotations to match actual code with type annotations.
@@ -112,33 +123,59 @@ export class PatternMatchingComparator extends JavaScriptSemanticComparatorVisit
112
123
  };
113
124
  }
114
125
 
126
+ /** The capture this pattern node stands for, paired with the target node it should bind to. */
127
+ protected captureAt(j: Tree, p: J): CaptureAt | undefined {
128
+ const marker = PlaceholderUtils.getCaptureMarker(j as J);
129
+ return marker ? {marker, target: p} : this.captureBehindParentheses(j, p);
130
+ }
131
+
132
+ /** The marker `attachCaptureMarkers` moved onto a parenthesized capture's `J.RightPadded`, which `unwrap()` skips past. */
133
+ private captureBehindParentheses(j: Tree, p: J): CaptureAt | undefined {
134
+ let pattern: any = j;
135
+ let target: any = p;
136
+ while (isParenthesized(pattern)) {
137
+ const padded = pattern.tree as J.RightPadded<J> | undefined;
138
+ if (!padded) {
139
+ return undefined;
140
+ }
141
+ target = isParenthesized(target) ? target.tree.element : target;
142
+ const marker = PlaceholderUtils.getCaptureMarker(padded);
143
+ if (marker) {
144
+ return {marker, target: target as J};
145
+ }
146
+ pattern = padded.element;
147
+ }
148
+ return undefined;
149
+ }
150
+
115
151
  override async visit<R extends J>(j: Tree, p: J, parent?: Cursor): Promise<R | undefined> {
116
152
  // Check if the pattern node is a capture - this handles unwrapped captures
117
153
  // (Wrapped captures in J.RightPadded are handled by visitRightPadded override)
118
154
  // Note: targetCursor will be pushed by parent's visit() method after this check
119
- const captureMarker = PlaceholderUtils.getCaptureMarker(j)!;
120
- if (captureMarker) {
155
+ const capture = this.captureAt(j, p);
156
+ if (capture) {
157
+ const {marker: captureMarker, target} = capture;
121
158
 
122
159
  // Push targetCursor to position it at the captured node for constraint evaluation
123
160
  // Only create cursor if targetCursor was initialized (meaning user provided one)
124
161
  const savedTargetCursor = this.targetCursor;
125
162
  const cursorAtCapturedNode = this.targetCursor !== undefined
126
- ? new Cursor(p, this.targetCursor)
127
- : new Cursor(p);
163
+ ? new Cursor(target, this.targetCursor)
164
+ : new Cursor(target);
128
165
  this.targetCursor = cursorAtCapturedNode;
129
166
  try {
130
167
  // Evaluate constraint with context (cursor + previous captures)
131
168
  // Skip constraint for variadic captures - they're evaluated in matchSequence with the full array
132
169
  if (captureMarker.constraint && !captureMarker.variadicOptions) {
133
170
  const context = this.buildConstraintContext(cursorAtCapturedNode);
134
- if (!captureMarker.constraint(p, context)) {
171
+ if (!captureMarker.constraint(target, context)) {
135
172
  const captureName = captureMarker.captureName || 'unnamed';
136
- const targetKind = (p as any).kind || 'unknown';
173
+ const targetKind = (target as any).kind || 'unknown';
137
174
  return this.constraintFailed(captureName, targetKind) as R;
138
175
  }
139
176
  }
140
177
 
141
- const success = this.matcher.handleCapture(captureMarker, p, undefined);
178
+ const success = this.matcher.handleCapture(captureMarker, target, undefined);
142
179
  if (!success) {
143
180
  const captureName = captureMarker.captureName || 'unnamed';
144
181
  return this.captureConflict(captureName) as R;
@@ -344,13 +381,12 @@ export class PatternMatchingComparator extends JavaScriptSemanticComparatorVisit
344
381
  if (!this.match) return methodInvocation;
345
382
  }
346
383
 
347
- // Compare typeParameters
348
- if ((methodInvocation.typeParameters === undefined) !== (otherMethodInvocation.typeParameters === undefined)) {
349
- return this.structuralMismatch('typeParameters');
350
- }
384
+ // A pattern that spells out no type arguments says nothing about them, as with parentheses
385
+ if (methodInvocation.typeParameters) {
386
+ if (!otherMethodInvocation.typeParameters) {
387
+ return this.structuralMismatch('typeParameters');
388
+ }
351
389
 
352
- // Visit typeParameters if present
353
- if (methodInvocation.typeParameters && otherMethodInvocation.typeParameters) {
354
390
  if (methodInvocation.typeParameters.elements.length !== otherMethodInvocation.typeParameters.elements.length) {
355
391
  return this.arrayLengthMismatch('typeParameters.elements');
356
392
  }
@@ -897,26 +933,27 @@ export class DebugPatternMatchingComparator extends PatternMatchingComparator {
897
933
  }
898
934
 
899
935
  override async visit<R extends J>(j: Tree, p: J, parent?: Cursor): Promise<R | undefined> {
900
- const captureMarker = PlaceholderUtils.getCaptureMarker(j)!;
901
- if (captureMarker) {
936
+ const capture = this.captureAt(j, p);
937
+ if (capture) {
938
+ const {marker: captureMarker, target} = capture;
902
939
  const savedTargetCursor = this.targetCursor;
903
940
  const cursorAtCapturedNode = this.targetCursor !== undefined
904
- ? new Cursor(p, this.targetCursor)
905
- : new Cursor(p);
941
+ ? new Cursor(target, this.targetCursor)
942
+ : new Cursor(target);
906
943
  this.targetCursor = cursorAtCapturedNode;
907
944
  try {
908
945
  if (captureMarker.constraint && !captureMarker.variadicOptions) {
909
946
  this.debug.log('debug', 'constraint', `Evaluating constraint for capture: ${captureMarker.captureName}`);
910
- const constraintResult = captureMarker.constraint(p, this.buildConstraintContext(cursorAtCapturedNode));
947
+ const constraintResult = captureMarker.constraint(target, this.buildConstraintContext(cursorAtCapturedNode));
911
948
  if (!constraintResult) {
912
949
  this.debug.log('info', 'constraint', `Constraint failed for capture: ${captureMarker.captureName}`);
913
- this.debug.setExplanation('constraint-failed', `Capture ${captureMarker.captureName} with valid constraint`, `Constraint failed for ${(p as any).kind}`, `Constraint evaluation returned false`);
950
+ this.debug.setExplanation('constraint-failed', `Capture ${captureMarker.captureName} with valid constraint`, `Constraint failed for ${(target as any).kind}`, `Constraint evaluation returned false`);
914
951
  return this.abort(j) as R;
915
952
  }
916
953
  this.debug.log('debug', 'constraint', `Constraint passed for capture: ${captureMarker.captureName}`);
917
954
  }
918
955
 
919
- const success = this.matcher.handleCapture(captureMarker, p, undefined);
956
+ const success = this.matcher.handleCapture(captureMarker, target, undefined);
920
957
  if (!success) {
921
958
  return this.abort(j) as R;
922
959
  }