@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.
- package/dist/java/formatting-utils.d.ts +8 -5
- package/dist/java/formatting-utils.d.ts.map +1 -1
- package/dist/java/formatting-utils.js +14 -10
- package/dist/java/formatting-utils.js.map +1 -1
- package/dist/java/type.d.ts +2 -0
- package/dist/java/type.d.ts.map +1 -1
- package/dist/java/type.js +5 -0
- package/dist/java/type.js.map +1 -1
- package/dist/javascript/add-import.d.ts +96 -43
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +668 -236
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts +138 -0
- package/dist/javascript/amd.d.ts.map +1 -0
- package/dist/javascript/amd.js +768 -0
- package/dist/javascript/amd.js.map +1 -0
- package/dist/javascript/assertions.d.ts +7 -0
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +31 -5
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/binding.d.ts +94 -0
- package/dist/javascript/binding.d.ts.map +1 -0
- package/dist/javascript/binding.js +335 -0
- package/dist/javascript/binding.js.map +1 -0
- package/dist/javascript/comparator.d.ts +5 -0
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +34 -9
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +8 -9
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/format/tabs-and-indents-visitor.d.ts +2 -0
- package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -1
- package/dist/javascript/format/tabs-and-indents-visitor.js +17 -11
- package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
- package/dist/javascript/index.d.ts +8 -1
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +26 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/node-resolution-result.d.ts +19 -1
- package/dist/javascript/node-resolution-result.d.ts.map +1 -1
- package/dist/javascript/node-resolution-result.js +37 -2
- package/dist/javascript/node-resolution-result.js.map +1 -1
- package/dist/javascript/package-exported-types.d.ts.map +1 -1
- package/dist/javascript/package-exported-types.js +56 -72
- package/dist/javascript/package-exported-types.js.map +1 -1
- package/dist/javascript/parser-utils.d.ts.map +1 -1
- package/dist/javascript/parser-utils.js +2 -0
- package/dist/javascript/parser-utils.js.map +1 -1
- package/dist/javascript/parser.d.ts +7 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +17 -4
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/recipes/change-import.d.ts +2 -4
- package/dist/javascript/recipes/change-import.d.ts.map +1 -1
- package/dist/javascript/recipes/change-import.js +8 -244
- package/dist/javascript/recipes/change-import.js.map +1 -1
- package/dist/javascript/remove-import.d.ts +2 -28
- package/dist/javascript/remove-import.d.ts.map +1 -1
- package/dist/javascript/remove-import.js +19 -57
- package/dist/javascript/remove-import.js.map +1 -1
- package/dist/javascript/scope.d.ts +49 -0
- package/dist/javascript/scope.d.ts.map +1 -0
- package/dist/javascript/scope.js +324 -0
- package/dist/javascript/scope.js.map +1 -0
- package/dist/javascript/style.d.ts.map +1 -1
- package/dist/javascript/style.js +3 -10
- package/dist/javascript/style.js.map +1 -1
- package/dist/javascript/templating/bindings.d.ts +8 -0
- package/dist/javascript/templating/bindings.d.ts.map +1 -0
- package/dist/javascript/templating/bindings.js +92 -0
- package/dist/javascript/templating/bindings.js.map +1 -0
- package/dist/javascript/templating/comparator.d.ts +10 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +48 -20
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +34 -10
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +109 -55
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/index.d.ts +1 -1
- package/dist/javascript/templating/index.d.ts.map +1 -1
- package/dist/javascript/templating/index.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +13 -10
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +4 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +24 -10
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/precedence.d.ts +12 -4
- package/dist/javascript/templating/precedence.d.ts.map +1 -1
- package/dist/javascript/templating/precedence.js +18 -11
- package/dist/javascript/templating/precedence.js.map +1 -1
- package/dist/javascript/templating/rewrite.d.ts.map +1 -1
- package/dist/javascript/templating/rewrite.js +19 -17
- package/dist/javascript/templating/rewrite.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +14 -0
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +63 -3
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +39 -1
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +6 -1
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +20 -2
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +1 -0
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +52 -2
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/style.d.ts +2 -0
- package/dist/style.d.ts.map +1 -1
- package/dist/style.js +5 -0
- package/dist/style.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +12 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +40 -3
- package/dist/test/rewrite-test.js.map +1 -1
- package/package.json +2 -2
- package/src/java/formatting-utils.ts +14 -10
- package/src/java/type.ts +5 -0
- package/src/javascript/add-import.ts +751 -247
- package/src/javascript/amd.ts +915 -0
- package/src/javascript/assertions.ts +33 -5
- package/src/javascript/binding.ts +449 -0
- package/src/javascript/comparator.ts +39 -10
- package/src/javascript/format/format.ts +9 -10
- package/src/javascript/format/tabs-and-indents-visitor.ts +18 -12
- package/src/javascript/index.ts +12 -1
- package/src/javascript/node-resolution-result.ts +59 -4
- package/src/javascript/package-exported-types.ts +60 -74
- package/src/javascript/parser-utils.ts +2 -0
- package/src/javascript/parser.ts +23 -3
- package/src/javascript/recipes/change-import.ts +10 -263
- package/src/javascript/remove-import.ts +20 -56
- package/src/javascript/scope.ts +338 -0
- package/src/javascript/style.ts +3 -11
- package/src/javascript/templating/bindings.ts +99 -0
- package/src/javascript/templating/comparator.ts +57 -20
- package/src/javascript/templating/engine.ts +148 -68
- package/src/javascript/templating/index.ts +1 -0
- package/src/javascript/templating/pattern.ts +19 -16
- package/src/javascript/templating/placeholder-replacement.ts +32 -11
- package/src/javascript/templating/precedence.ts +21 -13
- package/src/javascript/templating/rewrite.ts +23 -17
- package/src/javascript/templating/template.ts +84 -9
- package/src/javascript/templating/types.ts +46 -1
- package/src/javascript/templating/utils.ts +21 -2
- package/src/javascript/type-mapping.ts +54 -2
- package/src/style.ts +5 -0
- 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
|
+
}
|
package/src/javascript/style.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
120
|
-
if (
|
|
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(
|
|
127
|
-
: new Cursor(
|
|
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(
|
|
171
|
+
if (!captureMarker.constraint(target, context)) {
|
|
135
172
|
const captureName = captureMarker.captureName || 'unnamed';
|
|
136
|
-
const targetKind = (
|
|
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,
|
|
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
|
-
//
|
|
348
|
-
if (
|
|
349
|
-
|
|
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
|
|
901
|
-
if (
|
|
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(
|
|
905
|
-
: new Cursor(
|
|
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(
|
|
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 ${(
|
|
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,
|
|
956
|
+
const success = this.matcher.handleCapture(captureMarker, target, undefined);
|
|
920
957
|
if (!success) {
|
|
921
958
|
return this.abort(j) as R;
|
|
922
959
|
}
|