@openrewrite/rewrite 8.66.1 → 8.66.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/java/tree.d.ts +10 -1
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +21 -5
- package/dist/java/tree.js.map +1 -1
- package/dist/java/type-visitor.d.ts +1 -1
- package/dist/java/type-visitor.d.ts.map +1 -1
- package/dist/java/visitor.d.ts +2 -2
- package/dist/java/visitor.d.ts.map +1 -1
- package/dist/java/visitor.js +8 -2
- package/dist/java/visitor.js.map +1 -1
- package/dist/javascript/assertions.d.ts +6 -0
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +14 -6
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/comparator.d.ts +154 -7
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +623 -180
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format.d.ts +5 -3
- package/dist/javascript/format.d.ts.map +1 -1
- package/dist/javascript/format.js +85 -43
- package/dist/javascript/format.js.map +1 -1
- package/dist/javascript/index.d.ts +1 -0
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +1 -0
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/parser.d.ts +2 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +39 -30
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/templating/capture.d.ts +81 -14
- package/dist/javascript/templating/capture.d.ts.map +1 -1
- package/dist/javascript/templating/capture.js +98 -8
- package/dist/javascript/templating/capture.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +125 -15
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +946 -118
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +58 -25
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +527 -94
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/index.d.ts +3 -3
- package/dist/javascript/templating/index.d.ts.map +1 -1
- package/dist/javascript/templating/index.js +3 -1
- package/dist/javascript/templating/index.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +121 -16
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +528 -257
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +30 -5
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +183 -81
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/rewrite.d.ts +56 -11
- package/dist/javascript/templating/rewrite.d.ts.map +1 -1
- package/dist/javascript/templating/rewrite.js +143 -16
- package/dist/javascript/templating/rewrite.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +31 -5
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +89 -15
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +359 -12
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +52 -35
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +107 -109
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +21 -11
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/json/rpc.js +2 -2
- package/dist/json/rpc.js.map +1 -1
- package/dist/recipe/order-imports.js.map +1 -1
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +10 -6
- package/dist/test/rewrite-test.js.map +1 -1
- package/dist/version.txt +1 -1
- package/dist/visitor.d.ts +4 -4
- package/dist/visitor.d.ts.map +1 -1
- package/dist/visitor.js +8 -3
- package/dist/visitor.js.map +1 -1
- package/package.json +4 -2
- package/src/java/tree.ts +10 -3
- package/src/java/type-visitor.ts +1 -1
- package/src/java/visitor.ts +11 -5
- package/src/javascript/assertions.ts +9 -3
- package/src/javascript/comparator.ts +676 -185
- package/src/javascript/format.ts +72 -34
- package/src/javascript/index.ts +1 -0
- package/src/javascript/parser.ts +51 -31
- package/src/javascript/templating/capture.ts +107 -15
- package/src/javascript/templating/comparator.ts +1087 -134
- package/src/javascript/templating/engine.ts +601 -103
- package/src/javascript/templating/index.ts +9 -2
- package/src/javascript/templating/pattern.ts +655 -281
- package/src/javascript/templating/placeholder-replacement.ts +183 -80
- package/src/javascript/templating/rewrite.ts +152 -18
- package/src/javascript/templating/template.ts +110 -22
- package/src/javascript/templating/types.ts +386 -12
- package/src/javascript/templating/utils.ts +116 -102
- package/src/javascript/type-mapping.ts +20 -11
- package/src/json/rpc.ts +2 -2
- package/src/recipe/order-imports.ts +1 -1
- package/src/test/rewrite-test.ts +12 -7
- package/src/visitor.ts +14 -6
|
@@ -20,7 +20,7 @@ import {JavaScriptVisitor} from '../visitor';
|
|
|
20
20
|
import {produce} from 'immer';
|
|
21
21
|
import {PlaceholderUtils} from './utils';
|
|
22
22
|
import {CaptureImpl, TemplateParamImpl, CaptureValue, CAPTURE_NAME_SYMBOL} from './capture';
|
|
23
|
-
import {Parameter} from './
|
|
23
|
+
import {Parameter} from './types';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Visitor that replaces placeholder nodes with actual parameter values.
|
|
@@ -34,6 +34,123 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
34
34
|
super();
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
async visit<R extends J>(tree: J, p: any, parent?: Cursor): Promise<R | undefined> {
|
|
38
|
+
// Check if this node is a placeholder
|
|
39
|
+
// BUT: Don't handle `JS.BindingElement` here - let `visitBindingElement` preserve `propertyName`
|
|
40
|
+
if (tree.kind !== JS.Kind.BindingElement && this.isPlaceholder(tree)) {
|
|
41
|
+
const replacement = this.replacePlaceholder(tree);
|
|
42
|
+
if (replacement !== tree) {
|
|
43
|
+
return replacement as R;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Continue with normal traversal
|
|
48
|
+
return super.visit(tree, p, parent);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Override visitBindingElement to preserve propertyName from template when replacing.
|
|
53
|
+
* For example, in `{ ref: ${ref} }`, we want to preserve `ref:` when replacing ${ref}.
|
|
54
|
+
*/
|
|
55
|
+
override async visitBindingElement(bindingElement: JS.BindingElement, p: any): Promise<J | undefined> {
|
|
56
|
+
// Visit the name to potentially replace placeholders
|
|
57
|
+
const visitedName = await this.visit(bindingElement.name, p);
|
|
58
|
+
|
|
59
|
+
// If the name changed (placeholder was replaced), preserve the BindingElement structure
|
|
60
|
+
// including the propertyName from the template
|
|
61
|
+
if (visitedName !== bindingElement.name) {
|
|
62
|
+
return produce(bindingElement, draft => {
|
|
63
|
+
draft.name = visitedName as any;
|
|
64
|
+
// propertyName is already set from the template and will be preserved by produce
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return bindingElement;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Override visitContainer to handle variadic expansion for containers.
|
|
73
|
+
* This handles J.Container instances anywhere in the AST (method arguments, etc.).
|
|
74
|
+
*/
|
|
75
|
+
override async visitContainer<T extends J>(container: J.Container<T>, p: any): Promise<J.Container<T>> {
|
|
76
|
+
// Check if any elements are placeholders (possibly variadic)
|
|
77
|
+
const hasPlaceholder = container.elements.some(elem => this.isPlaceholder(elem.element));
|
|
78
|
+
|
|
79
|
+
if (!hasPlaceholder) {
|
|
80
|
+
return super.visitContainer(container, p);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Expand variadic placeholders in the container's elements
|
|
84
|
+
const newElements = await this.expandVariadicElements(container.elements, undefined, p);
|
|
85
|
+
|
|
86
|
+
return produce(container, draft => {
|
|
87
|
+
draft.elements = newElements as any;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Override visitRightPadded to handle single placeholder replacements.
|
|
93
|
+
* The base implementation will visit the element, which triggers our visit() override
|
|
94
|
+
* for placeholder detection and replacement.
|
|
95
|
+
*/
|
|
96
|
+
override async visitRightPadded<T extends J | boolean>(right: J.RightPadded<T>, p: any): Promise<J.RightPadded<T> | undefined> {
|
|
97
|
+
return super.visitRightPadded(right, p);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Override visitBlock to handle variadic expansion in block statements.
|
|
102
|
+
* Block.statements is J.RightPadded<Statement>[] (not a Container), so we need
|
|
103
|
+
* array-level access for variadic expansion.
|
|
104
|
+
*/
|
|
105
|
+
override async visitBlock(block: J.Block, p: any): Promise<J | undefined> {
|
|
106
|
+
const hasPlaceholder = block.statements.some(stmt => {
|
|
107
|
+
const stmtElement = stmt.element;
|
|
108
|
+
// Check if it's an ExpressionStatement containing a placeholder
|
|
109
|
+
if (stmtElement.kind === JS.Kind.ExpressionStatement) {
|
|
110
|
+
const exprStmt = stmtElement as JS.ExpressionStatement;
|
|
111
|
+
return this.isPlaceholder(exprStmt.expression);
|
|
112
|
+
}
|
|
113
|
+
return this.isPlaceholder(stmtElement);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (!hasPlaceholder) {
|
|
117
|
+
return super.visitBlock(block, p);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Unwrap function to extract placeholder from ExpressionStatement
|
|
121
|
+
const unwrapStatement = (element: J): J => {
|
|
122
|
+
if (element.kind === JS.Kind.ExpressionStatement) {
|
|
123
|
+
return (element as JS.ExpressionStatement).expression;
|
|
124
|
+
}
|
|
125
|
+
return element;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const newStatements = await this.expandVariadicElements(block.statements, unwrapStatement, p);
|
|
129
|
+
|
|
130
|
+
return produce(block, draft => {
|
|
131
|
+
draft.statements = newStatements;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Override visitJsCompilationUnit to handle variadic expansion in top-level statements.
|
|
137
|
+
* CompilationUnit.statements is J.RightPadded<Statement>[] (not a Container), so we need
|
|
138
|
+
* array-level access for variadic expansion.
|
|
139
|
+
*/
|
|
140
|
+
override async visitJsCompilationUnit(compilationUnit: JS.CompilationUnit, p: any): Promise<J | undefined> {
|
|
141
|
+
const hasPlaceholder = compilationUnit.statements.some(stmt => this.isPlaceholder(stmt.element));
|
|
142
|
+
|
|
143
|
+
if (!hasPlaceholder) {
|
|
144
|
+
return super.visitJsCompilationUnit(compilationUnit, p);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const newStatements = await this.expandVariadicElements(compilationUnit.statements, undefined, p);
|
|
148
|
+
|
|
149
|
+
return produce(compilationUnit, draft => {
|
|
150
|
+
draft.statements = newStatements;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
37
154
|
/**
|
|
38
155
|
* Merges prefixes by preserving comments from the source element
|
|
39
156
|
* while using whitespace from the template placeholder.
|
|
@@ -83,8 +200,15 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
83
200
|
if (param) {
|
|
84
201
|
let arrayToExpand: J[] | J.RightPadded<J>[] | undefined = undefined;
|
|
85
202
|
|
|
203
|
+
// Check if it's a J.Container
|
|
204
|
+
const isContainer = param.value && typeof param.value === 'object' &&
|
|
205
|
+
param.value.kind === J.Kind.Container;
|
|
206
|
+
if (isContainer) {
|
|
207
|
+
// Extract elements from J.Container
|
|
208
|
+
arrayToExpand = param.value.elements as J.RightPadded<J>[];
|
|
209
|
+
}
|
|
86
210
|
// Check if it's a direct Tree[] array
|
|
87
|
-
if (Array.isArray(param.value)) {
|
|
211
|
+
else if (Array.isArray(param.value)) {
|
|
88
212
|
arrayToExpand = param.value as J[];
|
|
89
213
|
}
|
|
90
214
|
// Check if it's a CaptureValue
|
|
@@ -97,7 +221,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
97
221
|
// Check if it's a direct variadic capture
|
|
98
222
|
else {
|
|
99
223
|
const isCapture = param.value instanceof CaptureImpl ||
|
|
100
|
-
|
|
224
|
+
(param.value && typeof param.value === 'object' && param.value[CAPTURE_NAME_SYMBOL]);
|
|
101
225
|
if (isCapture) {
|
|
102
226
|
const name = param.value[CAPTURE_NAME_SYMBOL] || param.value.name;
|
|
103
227
|
const capture = Array.from(this.substitutions.values())
|
|
@@ -167,7 +291,28 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
167
291
|
// Not a placeholder (or expansion failed) - process normally
|
|
168
292
|
const replacedElement = await this.visit(element, p);
|
|
169
293
|
if (replacedElement) {
|
|
170
|
-
|
|
294
|
+
// Check if the replacement came from a capture with a wrapper (to preserve markers)
|
|
295
|
+
const placeholderNode = unwrapElement(element);
|
|
296
|
+
const placeholderText = this.getPlaceholderText(placeholderNode);
|
|
297
|
+
let wrapperToUse = wrapped;
|
|
298
|
+
|
|
299
|
+
if (placeholderText && this.isPlaceholder(placeholderNode)) {
|
|
300
|
+
const param = this.substitutions.get(placeholderText);
|
|
301
|
+
if (param) {
|
|
302
|
+
const isCapture = param.value instanceof CaptureImpl ||
|
|
303
|
+
(param.value && typeof param.value === 'object' && param.value[CAPTURE_NAME_SYMBOL]);
|
|
304
|
+
if (isCapture) {
|
|
305
|
+
const name = param.value[CAPTURE_NAME_SYMBOL] || param.value.name;
|
|
306
|
+
const wrapper = this.wrappersMap.get(name);
|
|
307
|
+
// Use captured wrapper if available and not an array (non-variadic)
|
|
308
|
+
if (wrapper && !Array.isArray(wrapper)) {
|
|
309
|
+
wrapperToUse = wrapper as J.RightPadded<J>;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
newElements.push(produce(wrapperToUse, draft => {
|
|
171
316
|
draft.element = replacedElement;
|
|
172
317
|
}));
|
|
173
318
|
}
|
|
@@ -176,80 +321,6 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
176
321
|
return newElements;
|
|
177
322
|
}
|
|
178
323
|
|
|
179
|
-
async visit<R extends J>(tree: J, p: any, parent?: Cursor): Promise<R | undefined> {
|
|
180
|
-
// Check if this node is a placeholder
|
|
181
|
-
if (this.isPlaceholder(tree)) {
|
|
182
|
-
const replacement = this.replacePlaceholder(tree);
|
|
183
|
-
if (replacement !== tree) {
|
|
184
|
-
return replacement as R;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Continue with normal traversal
|
|
189
|
-
return super.visit(tree, p, parent);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
override async visitMethodInvocation(method: J.MethodInvocation, p: any): Promise<J | undefined> {
|
|
193
|
-
// Check if any arguments are placeholders (possibly variadic)
|
|
194
|
-
const hasPlaceholder = method.arguments.elements.some(arg => this.isPlaceholder(arg.element));
|
|
195
|
-
|
|
196
|
-
if (!hasPlaceholder) {
|
|
197
|
-
return super.visitMethodInvocation(method, p);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const newArguments = await this.expandVariadicElements(method.arguments.elements, undefined, p);
|
|
201
|
-
|
|
202
|
-
return produce(method, draft => {
|
|
203
|
-
draft.arguments.elements = newArguments;
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
override async visitBlock(block: J.Block, p: any): Promise<J | undefined> {
|
|
208
|
-
// Check if any statements are placeholders (possibly variadic)
|
|
209
|
-
const hasPlaceholder = block.statements.some(stmt => {
|
|
210
|
-
const stmtElement = stmt.element;
|
|
211
|
-
// Check if it's an ExpressionStatement containing a placeholder
|
|
212
|
-
if (stmtElement.kind === JS.Kind.ExpressionStatement) {
|
|
213
|
-
const exprStmt = stmtElement as JS.ExpressionStatement;
|
|
214
|
-
return this.isPlaceholder(exprStmt.expression);
|
|
215
|
-
}
|
|
216
|
-
return this.isPlaceholder(stmtElement);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
if (!hasPlaceholder) {
|
|
220
|
-
return super.visitBlock(block, p);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Unwrap function to extract placeholder from ExpressionStatement
|
|
224
|
-
const unwrapStatement = (element: J): J => {
|
|
225
|
-
if (element.kind === JS.Kind.ExpressionStatement) {
|
|
226
|
-
return (element as JS.ExpressionStatement).expression;
|
|
227
|
-
}
|
|
228
|
-
return element;
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const newStatements = await this.expandVariadicElements(block.statements, unwrapStatement, p);
|
|
232
|
-
|
|
233
|
-
return produce(block, draft => {
|
|
234
|
-
draft.statements = newStatements;
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
override async visitJsCompilationUnit(compilationUnit: JS.CompilationUnit, p: any): Promise<J | undefined> {
|
|
239
|
-
// Check if any statements are placeholders (possibly variadic)
|
|
240
|
-
const hasPlaceholder = compilationUnit.statements.some(stmt => this.isPlaceholder(stmt.element));
|
|
241
|
-
|
|
242
|
-
if (!hasPlaceholder) {
|
|
243
|
-
return super.visitJsCompilationUnit(compilationUnit, p);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const newStatements = await this.expandVariadicElements(compilationUnit.statements, undefined, p);
|
|
247
|
-
|
|
248
|
-
return produce(compilationUnit, draft => {
|
|
249
|
-
draft.statements = newStatements;
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
|
|
253
324
|
/**
|
|
254
325
|
* Checks if a node is a placeholder.
|
|
255
326
|
*
|
|
@@ -263,6 +334,10 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
263
334
|
} else if (node.kind === J.Kind.Literal) {
|
|
264
335
|
const literal = node as J.Literal;
|
|
265
336
|
return literal.valueSource?.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX) || false;
|
|
337
|
+
} else if (node.kind === JS.Kind.BindingElement) {
|
|
338
|
+
// Check if the BindingElement's name is a placeholder
|
|
339
|
+
const bindingElement = node as JS.BindingElement;
|
|
340
|
+
return this.isPlaceholder(bindingElement.name);
|
|
266
341
|
}
|
|
267
342
|
return false;
|
|
268
343
|
}
|
|
@@ -323,13 +398,13 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
323
398
|
|
|
324
399
|
// Check if the parameter value is a Capture (could be a Proxy) or TemplateParam
|
|
325
400
|
const isCapture = param.value instanceof CaptureImpl ||
|
|
326
|
-
|
|
401
|
+
(param.value && typeof param.value === 'object' && param.value[CAPTURE_NAME_SYMBOL]);
|
|
327
402
|
const isTemplateParam = param.value instanceof TemplateParamImpl;
|
|
328
403
|
|
|
329
404
|
if (isCapture || isTemplateParam) {
|
|
330
405
|
// Simple capture/template param (no property path for template params)
|
|
331
406
|
const name = isTemplateParam ? param.value.name :
|
|
332
|
-
|
|
407
|
+
(param.value[CAPTURE_NAME_SYMBOL] || param.value.name);
|
|
333
408
|
const matchedNode = this.values.get(name);
|
|
334
409
|
if (matchedNode && !Array.isArray(matchedNode)) {
|
|
335
410
|
return produce(matchedNode, draft => {
|
|
@@ -342,6 +417,30 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
342
417
|
return placeholder;
|
|
343
418
|
}
|
|
344
419
|
|
|
420
|
+
// Check if the parameter value is a J.RightPadded wrapper
|
|
421
|
+
const isRightPadded = param.value && typeof param.value === 'object' &&
|
|
422
|
+
param.value.kind === J.Kind.RightPadded && isTree(param.value.element);
|
|
423
|
+
|
|
424
|
+
if (isRightPadded) {
|
|
425
|
+
// Extract the element from the J.RightPadded wrapper
|
|
426
|
+
const element = param.value.element as J;
|
|
427
|
+
return produce(element, draft => {
|
|
428
|
+
draft.markers = placeholder.markers;
|
|
429
|
+
draft.prefix = this.mergePrefix(element.prefix, placeholder.prefix);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Check if the parameter value is a J.Container
|
|
434
|
+
const isContainer = param.value && typeof param.value === 'object' &&
|
|
435
|
+
param.value.kind === J.Kind.Container;
|
|
436
|
+
|
|
437
|
+
if (isContainer) {
|
|
438
|
+
// J.Container should be handled by expandVariadicElements
|
|
439
|
+
// For now, return placeholder - the expansion will happen at a higher level
|
|
440
|
+
// This should not happen in normal usage, as containers are typically used in argument positions
|
|
441
|
+
return placeholder;
|
|
442
|
+
}
|
|
443
|
+
|
|
345
444
|
// If the parameter value is an AST node, use it directly
|
|
346
445
|
if (isTree(param.value)) {
|
|
347
446
|
// Return the AST node, preserving comments from the source
|
|
@@ -365,6 +464,10 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
365
464
|
return (node as J.Identifier).simpleName;
|
|
366
465
|
} else if (node.kind === J.Kind.Literal) {
|
|
367
466
|
return (node as J.Literal).valueSource || null;
|
|
467
|
+
} else if (node.kind === JS.Kind.BindingElement) {
|
|
468
|
+
// Extract placeholder text from the BindingElement's name
|
|
469
|
+
const bindingElement = node as JS.BindingElement;
|
|
470
|
+
return this.getPlaceholderText(bindingElement.name);
|
|
368
471
|
}
|
|
369
472
|
return null;
|
|
370
473
|
}
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {Cursor} from '../..';
|
|
16
|
+
import {Cursor, ExecutionContext, Recipe} from '../..';
|
|
17
17
|
import {J} from '../../java';
|
|
18
18
|
import {RewriteRule, RewriteConfig} from './types';
|
|
19
|
-
import {Pattern} from './pattern';
|
|
19
|
+
import {Pattern, MatchResult} from './pattern';
|
|
20
20
|
import {Template} from './template';
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -25,24 +25,92 @@ import {Template} from './template';
|
|
|
25
25
|
class RewriteRuleImpl implements RewriteRule {
|
|
26
26
|
constructor(
|
|
27
27
|
private readonly before: Pattern[],
|
|
28
|
-
private readonly after: Template
|
|
28
|
+
private readonly after: Template | ((match: MatchResult) => Template),
|
|
29
|
+
private readonly where?: (node: J, cursor: Cursor) => boolean | Promise<boolean>,
|
|
30
|
+
private readonly whereNot?: (node: J, cursor: Cursor) => boolean | Promise<boolean>
|
|
29
31
|
) {
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
async tryOn(cursor: Cursor, node: J): Promise<J | undefined> {
|
|
33
35
|
for (const pattern of this.before) {
|
|
34
|
-
|
|
36
|
+
// Pass cursor to pattern.match() for context-aware capture constraints
|
|
37
|
+
const match = await pattern.match(node, cursor);
|
|
35
38
|
if (match) {
|
|
36
|
-
|
|
39
|
+
// Evaluate context predicates after structural match
|
|
40
|
+
if (this.where) {
|
|
41
|
+
const whereResult = await this.where(node, cursor);
|
|
42
|
+
if (!whereResult) {
|
|
43
|
+
continue; // Pattern matched but context doesn't, try next pattern
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (this.whereNot) {
|
|
48
|
+
const whereNotResult = await this.whereNot(node, cursor);
|
|
49
|
+
if (whereNotResult) {
|
|
50
|
+
continue; // Pattern matched but context is excluded, try next pattern
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Apply transformation
|
|
55
|
+
let result: J | undefined;
|
|
56
|
+
|
|
57
|
+
if (typeof this.after === 'function') {
|
|
58
|
+
// Call the function to get a template, then apply it
|
|
59
|
+
const template = this.after(match);
|
|
60
|
+
result = await template.apply(cursor, node, match);
|
|
61
|
+
} else {
|
|
62
|
+
// Use template.apply() as before
|
|
63
|
+
result = await this.after.apply(cursor, node, match);
|
|
64
|
+
}
|
|
65
|
+
|
|
37
66
|
if (result) {
|
|
38
67
|
return result;
|
|
39
68
|
}
|
|
40
69
|
}
|
|
41
70
|
}
|
|
42
71
|
|
|
43
|
-
// Return undefined if no patterns match
|
|
72
|
+
// Return undefined if no patterns match or all context checks failed
|
|
44
73
|
return undefined;
|
|
45
74
|
}
|
|
75
|
+
|
|
76
|
+
andThen(next: RewriteRule): RewriteRule {
|
|
77
|
+
const first = this;
|
|
78
|
+
return new (class extends RewriteRuleImpl {
|
|
79
|
+
constructor() {
|
|
80
|
+
// Pass empty patterns and a function that will never be called
|
|
81
|
+
// since we override tryOn
|
|
82
|
+
super([], () => undefined as unknown as Template);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async tryOn(cursor: Cursor, node: J): Promise<J | undefined> {
|
|
86
|
+
const firstResult = await first.tryOn(cursor, node);
|
|
87
|
+
if (firstResult !== undefined) {
|
|
88
|
+
const secondResult = await next.tryOn(cursor, firstResult);
|
|
89
|
+
return secondResult ?? firstResult;
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
})();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
orElse(alternative: RewriteRule): RewriteRule {
|
|
97
|
+
const first = this;
|
|
98
|
+
return new (class extends RewriteRuleImpl {
|
|
99
|
+
constructor() {
|
|
100
|
+
// Pass empty patterns and a function that will never be called
|
|
101
|
+
// since we override tryOn
|
|
102
|
+
super([], () => undefined as unknown as Template);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async tryOn(cursor: Cursor, node: J): Promise<J | undefined> {
|
|
106
|
+
const firstResult = await first.tryOn(cursor, node);
|
|
107
|
+
if (firstResult !== undefined) {
|
|
108
|
+
return firstResult;
|
|
109
|
+
}
|
|
110
|
+
return await alternative.tryOn(cursor, node);
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
113
|
+
}
|
|
46
114
|
}
|
|
47
115
|
|
|
48
116
|
/**
|
|
@@ -53,20 +121,26 @@ class RewriteRuleImpl implements RewriteRule {
|
|
|
53
121
|
*
|
|
54
122
|
* @example
|
|
55
123
|
* // Single pattern
|
|
56
|
-
* const swapOperands = rewrite(() =>
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* }
|
|
124
|
+
* const swapOperands = rewrite(() => {
|
|
125
|
+
* const { left, right } = { left: capture(), right: capture() };
|
|
126
|
+
* return {
|
|
127
|
+
* before: pattern`${left} + ${right}`,
|
|
128
|
+
* after: template`${right} + ${left}`
|
|
129
|
+
* };
|
|
130
|
+
* });
|
|
60
131
|
*
|
|
61
132
|
* @example
|
|
62
133
|
* // Multiple patterns
|
|
63
|
-
* const normalizeComparisons = rewrite(() =>
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
134
|
+
* const normalizeComparisons = rewrite(() => {
|
|
135
|
+
* const { left, right } = { left: capture(), right: capture() };
|
|
136
|
+
* return {
|
|
137
|
+
* before: [
|
|
138
|
+
* pattern`${left} == ${right}`,
|
|
139
|
+
* pattern`${left} === ${right}`
|
|
140
|
+
* ],
|
|
141
|
+
* after: template`${left} === ${right}`
|
|
142
|
+
* };
|
|
143
|
+
* });
|
|
70
144
|
*
|
|
71
145
|
* @example
|
|
72
146
|
* // Using in a visitor - IMPORTANT: use `|| node` to handle undefined when no match
|
|
@@ -91,5 +165,65 @@ export function rewrite(
|
|
|
91
165
|
throw new Error('Builder function must return an object with before and after properties');
|
|
92
166
|
}
|
|
93
167
|
|
|
94
|
-
return new RewriteRuleImpl(
|
|
168
|
+
return new RewriteRuleImpl(
|
|
169
|
+
Array.isArray(config.before) ? config.before : [config.before],
|
|
170
|
+
config.after,
|
|
171
|
+
config.where,
|
|
172
|
+
config.whereNot
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Creates a RewriteRule from a Recipe by using its editor visitor.
|
|
178
|
+
*
|
|
179
|
+
* This allows recipes to be used in the same chaining pattern as other rewrite rules,
|
|
180
|
+
* enabling composition with `andThen()`.
|
|
181
|
+
*
|
|
182
|
+
* @param recipe The recipe whose editor will be used to transform nodes
|
|
183
|
+
* @param ctx The execution context to pass to the recipe's editor
|
|
184
|
+
* @returns A RewriteRule that applies the recipe's editor to nodes
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* class MyRecipe extends Recipe {
|
|
189
|
+
* name = "my.recipe";
|
|
190
|
+
* displayName = "My Recipe";
|
|
191
|
+
* description = "Transforms code.";
|
|
192
|
+
*
|
|
193
|
+
* async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
194
|
+
* return new MyVisitor();
|
|
195
|
+
* }
|
|
196
|
+
* }
|
|
197
|
+
*
|
|
198
|
+
* // In a visitor:
|
|
199
|
+
* override async visitBinary(binary: J.Binary, p: ExecutionContext): Promise<J | undefined> {
|
|
200
|
+
* const rule1 = rewrite(() => ({
|
|
201
|
+
* before: pattern`${capture('a')} + ${capture('b')}`,
|
|
202
|
+
* after: template`${capture('b')} + ${capture('a')}`
|
|
203
|
+
* }));
|
|
204
|
+
*
|
|
205
|
+
* const rule2 = fromRecipe(new MyRecipe(), p);
|
|
206
|
+
*
|
|
207
|
+
* // Chain the pattern-based rule with the recipe
|
|
208
|
+
* const combined = rule1.andThen(rule2);
|
|
209
|
+
* return await combined.tryOn(this.cursor, binary) || binary;
|
|
210
|
+
* }
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export const fromRecipe = (recipe: Recipe, ctx: ExecutionContext): RewriteRule => {
|
|
214
|
+
return new (class extends RewriteRuleImpl {
|
|
215
|
+
constructor() {
|
|
216
|
+
// Pass empty patterns and a function that will never be called
|
|
217
|
+
// since we override tryOn
|
|
218
|
+
super([], () => undefined as unknown as Template);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async tryOn(cursor: Cursor, tree: J): Promise<J | undefined> {
|
|
222
|
+
const visitor = await recipe.editor();
|
|
223
|
+
const result = await visitor.visit<J>(tree, ctx, cursor);
|
|
224
|
+
|
|
225
|
+
// Return undefined if the visitor didn't change the node
|
|
226
|
+
return result !== tree ? result : undefined;
|
|
227
|
+
}
|
|
228
|
+
})();
|
|
95
229
|
}
|