@openrewrite/rewrite 8.66.0-SNAPSHOT → 8.66.1

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 (84) hide show
  1. package/dist/javascript/comparator.d.ts +91 -5
  2. package/dist/javascript/comparator.d.ts.map +1 -1
  3. package/dist/javascript/comparator.js +679 -3091
  4. package/dist/javascript/comparator.js.map +1 -1
  5. package/dist/javascript/format.d.ts.map +1 -1
  6. package/dist/javascript/format.js +4 -3
  7. package/dist/javascript/format.js.map +1 -1
  8. package/dist/javascript/index.d.ts +1 -1
  9. package/dist/javascript/index.d.ts.map +1 -1
  10. package/dist/javascript/index.js +1 -1
  11. package/dist/javascript/index.js.map +1 -1
  12. package/dist/javascript/parser.d.ts.map +1 -1
  13. package/dist/javascript/parser.js +22 -21
  14. package/dist/javascript/parser.js.map +1 -1
  15. package/dist/javascript/print.d.ts +2 -2
  16. package/dist/javascript/print.d.ts.map +1 -1
  17. package/dist/javascript/print.js +4 -4
  18. package/dist/javascript/print.js.map +1 -1
  19. package/dist/javascript/templating/capture.d.ts +226 -0
  20. package/dist/javascript/templating/capture.d.ts.map +1 -0
  21. package/dist/javascript/templating/capture.js +371 -0
  22. package/dist/javascript/templating/capture.js.map +1 -0
  23. package/dist/javascript/templating/comparator.d.ts +61 -0
  24. package/dist/javascript/templating/comparator.d.ts.map +1 -0
  25. package/dist/javascript/templating/comparator.js +393 -0
  26. package/dist/javascript/templating/comparator.js.map +1 -0
  27. package/dist/javascript/templating/engine.d.ts +75 -0
  28. package/dist/javascript/templating/engine.d.ts.map +1 -0
  29. package/dist/javascript/templating/engine.js +228 -0
  30. package/dist/javascript/templating/engine.js.map +1 -0
  31. package/dist/javascript/templating/index.d.ts +6 -0
  32. package/dist/javascript/templating/index.d.ts.map +1 -0
  33. package/dist/javascript/templating/index.js +42 -0
  34. package/dist/javascript/templating/index.js.map +1 -0
  35. package/dist/javascript/templating/pattern.d.ts +171 -0
  36. package/dist/javascript/templating/pattern.d.ts.map +1 -0
  37. package/dist/javascript/templating/pattern.js +681 -0
  38. package/dist/javascript/templating/pattern.js.map +1 -0
  39. package/dist/javascript/templating/placeholder-replacement.d.ts +58 -0
  40. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -0
  41. package/dist/javascript/templating/placeholder-replacement.js +365 -0
  42. package/dist/javascript/templating/placeholder-replacement.js.map +1 -0
  43. package/dist/javascript/templating/rewrite.d.ts +39 -0
  44. package/dist/javascript/templating/rewrite.d.ts.map +1 -0
  45. package/dist/javascript/templating/rewrite.js +81 -0
  46. package/dist/javascript/templating/rewrite.js.map +1 -0
  47. package/dist/javascript/templating/template.d.ts +204 -0
  48. package/dist/javascript/templating/template.d.ts.map +1 -0
  49. package/dist/javascript/templating/template.js +293 -0
  50. package/dist/javascript/templating/template.js.map +1 -0
  51. package/dist/javascript/templating/types.d.ts +263 -0
  52. package/dist/javascript/templating/types.d.ts.map +1 -0
  53. package/dist/javascript/templating/types.js +3 -0
  54. package/dist/javascript/templating/types.js.map +1 -0
  55. package/dist/javascript/templating/utils.d.ts +118 -0
  56. package/dist/javascript/templating/utils.d.ts.map +1 -0
  57. package/dist/javascript/templating/utils.js +253 -0
  58. package/dist/javascript/templating/utils.js.map +1 -0
  59. package/dist/test/rewrite-test.d.ts.map +1 -1
  60. package/dist/test/rewrite-test.js +65 -9
  61. package/dist/test/rewrite-test.js.map +1 -1
  62. package/dist/version.txt +1 -1
  63. package/package.json +2 -2
  64. package/src/javascript/comparator.ts +721 -3607
  65. package/src/javascript/format.ts +3 -2
  66. package/src/javascript/index.ts +1 -1
  67. package/src/javascript/parser.ts +23 -22
  68. package/src/javascript/print.ts +6 -6
  69. package/src/javascript/templating/capture.ts +503 -0
  70. package/src/javascript/templating/comparator.ts +430 -0
  71. package/src/javascript/templating/engine.ts +252 -0
  72. package/src/javascript/templating/index.ts +60 -0
  73. package/src/javascript/templating/pattern.ts +727 -0
  74. package/src/javascript/templating/placeholder-replacement.ts +372 -0
  75. package/src/javascript/templating/rewrite.ts +95 -0
  76. package/src/javascript/templating/template.ts +326 -0
  77. package/src/javascript/templating/types.ts +300 -0
  78. package/src/javascript/templating/utils.ts +284 -0
  79. package/src/test/rewrite-test.ts +65 -1
  80. package/dist/javascript/templating.d.ts +0 -265
  81. package/dist/javascript/templating.d.ts.map +0 -1
  82. package/dist/javascript/templating.js +0 -1069
  83. package/dist/javascript/templating.js.map +0 -1
  84. package/src/javascript/templating.ts +0 -1277
@@ -0,0 +1,372 @@
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 '../..';
17
+ import {J} from '../../java';
18
+ import {JS} from '..';
19
+ import {JavaScriptVisitor} from '../visitor';
20
+ import {produce} from 'immer';
21
+ import {PlaceholderUtils} from './utils';
22
+ import {CaptureImpl, TemplateParamImpl, CaptureValue, CAPTURE_NAME_SYMBOL} from './capture';
23
+ import {Parameter} from './engine';
24
+
25
+ /**
26
+ * Visitor that replaces placeholder nodes with actual parameter values.
27
+ */
28
+ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
29
+ constructor(
30
+ private readonly substitutions: Map<string, Parameter>,
31
+ private readonly values: Pick<Map<string, J | J[]>, 'get'> = new Map(),
32
+ private readonly wrappersMap: Pick<Map<string, J.RightPadded<J> | J.RightPadded<J>[]>, 'get'> = new Map()
33
+ ) {
34
+ super();
35
+ }
36
+
37
+ /**
38
+ * Merges prefixes by preserving comments from the source element
39
+ * while using whitespace from the template placeholder.
40
+ *
41
+ * @param sourcePrefix The prefix from the captured element (may contain comments)
42
+ * @param templatePrefix The prefix from the template placeholder (defines whitespace)
43
+ * @returns A merged prefix with source comments and template whitespace
44
+ */
45
+ private mergePrefix(sourcePrefix: J.Space, templatePrefix: J.Space): J.Space {
46
+ // If source has no comments, just use template prefix
47
+ if (sourcePrefix.comments.length === 0) {
48
+ return templatePrefix;
49
+ }
50
+
51
+ // Preserve comments from source, use whitespace from template
52
+ return {
53
+ kind: J.Kind.Space,
54
+ comments: sourcePrefix.comments,
55
+ whitespace: templatePrefix.whitespace
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Expands variadic placeholders in a list of elements.
61
+ *
62
+ * @param elements The list of wrapped elements to process
63
+ * @param unwrapElement Optional function to unwrap the placeholder node from its container (e.g., ExpressionStatement)
64
+ * @param p Context parameter for visitor
65
+ * @returns Promise of new list with placeholders expanded
66
+ */
67
+ private async expandVariadicElements(
68
+ elements: J.RightPadded<J>[],
69
+ unwrapElement: (element: J) => J = (e) => e,
70
+ p: any
71
+ ): Promise<J.RightPadded<J>[]> {
72
+ const newElements: J.RightPadded<J>[] = [];
73
+
74
+ for (const wrapped of elements) {
75
+ const element = wrapped.element;
76
+ const placeholderNode = unwrapElement(element);
77
+
78
+ // Check if this element contains a placeholder
79
+ if (this.isPlaceholder(placeholderNode)) {
80
+ const placeholderText = this.getPlaceholderText(placeholderNode);
81
+ if (placeholderText) {
82
+ const param = this.substitutions.get(placeholderText);
83
+ if (param) {
84
+ let arrayToExpand: J[] | J.RightPadded<J>[] | undefined = undefined;
85
+
86
+ // Check if it's a direct Tree[] array
87
+ if (Array.isArray(param.value)) {
88
+ arrayToExpand = param.value as J[];
89
+ }
90
+ // Check if it's a CaptureValue
91
+ else if (param.value instanceof CaptureValue) {
92
+ const resolved = param.value.resolve(this.values);
93
+ if (Array.isArray(resolved)) {
94
+ arrayToExpand = resolved;
95
+ }
96
+ }
97
+ // Check if it's a direct variadic capture
98
+ else {
99
+ const isCapture = param.value instanceof CaptureImpl ||
100
+ (param.value && typeof param.value === 'object' && param.value[CAPTURE_NAME_SYMBOL]);
101
+ if (isCapture) {
102
+ const name = param.value[CAPTURE_NAME_SYMBOL] || param.value.name;
103
+ const capture = Array.from(this.substitutions.values())
104
+ .map(p => p.value)
105
+ .find(v => v instanceof CaptureImpl && v.getName() === name) as CaptureImpl | undefined;
106
+
107
+ if (capture?.isVariadic()) {
108
+ // Prefer wrappers if available (to preserve markers like Semicolon)
109
+ // Otherwise fall back to elements
110
+ const wrappersArray = this.wrappersMap.get(name);
111
+ if (Array.isArray(wrappersArray)) {
112
+ arrayToExpand = wrappersArray;
113
+ } else {
114
+ const matchedArray = this.values.get(name);
115
+ if (Array.isArray(matchedArray)) {
116
+ arrayToExpand = matchedArray;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ // Expand the array if we found one
124
+ if (arrayToExpand !== undefined) {
125
+ if (arrayToExpand.length > 0) {
126
+ for (let i = 0; i < arrayToExpand.length; i++) {
127
+ const item = arrayToExpand[i];
128
+
129
+ // Check if item is a JRightPadded wrapper or just an element
130
+ // JRightPadded wrappers have 'element', 'after', and 'markers' properties
131
+ // Also ensure the element field is not null
132
+ const isWrapper = item && typeof item === 'object' && 'element' in item && 'after' in item && item.element != null;
133
+
134
+ if (isWrapper) {
135
+ // Item is a JRightPadded wrapper - use it directly to preserve markers
136
+ newElements.push(produce(item, draft => {
137
+ if (i === 0 && draft.element) {
138
+ // Merge the placeholder's prefix with the first item's prefix
139
+ // Modify prefix directly without nested produce to avoid immer issues
140
+ draft.element.prefix = this.mergePrefix(draft.element.prefix, element.prefix);
141
+ }
142
+ // Keep all other wrapper properties (including markers with Semicolon)
143
+ }));
144
+ } else if (item) {
145
+ // Item is just an element (not a wrapper) - wrap it (backward compatibility)
146
+ const elem = item as J;
147
+ newElements.push(produce(wrapped, draft => {
148
+ draft.element = produce(elem, itemDraft => {
149
+ if (i === 0) {
150
+ itemDraft.prefix = this.mergePrefix(elem.prefix, element.prefix);
151
+ }
152
+ // For i > 0, prefix is already correct, no changes needed
153
+ });
154
+ }));
155
+ }
156
+ }
157
+ continue; // Skip adding the placeholder itself
158
+ } else {
159
+ // Empty array - don't add any elements
160
+ continue;
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ // Not a placeholder (or expansion failed) - process normally
168
+ const replacedElement = await this.visit(element, p);
169
+ if (replacedElement) {
170
+ newElements.push(produce(wrapped, draft => {
171
+ draft.element = replacedElement;
172
+ }));
173
+ }
174
+ }
175
+
176
+ return newElements;
177
+ }
178
+
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
+ /**
254
+ * Checks if a node is a placeholder.
255
+ *
256
+ * @param node The node to check
257
+ * @returns True if the node is a placeholder
258
+ */
259
+ private isPlaceholder(node: J): boolean {
260
+ if (node.kind === J.Kind.Identifier) {
261
+ const identifier = node as J.Identifier;
262
+ return identifier.simpleName.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX);
263
+ } else if (node.kind === J.Kind.Literal) {
264
+ const literal = node as J.Literal;
265
+ return literal.valueSource?.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX) || false;
266
+ }
267
+ return false;
268
+ }
269
+
270
+ /**
271
+ * Replaces a placeholder node with the actual parameter value.
272
+ *
273
+ * @param placeholder The placeholder node
274
+ * @returns The replacement node or the original if not a placeholder
275
+ */
276
+ private replacePlaceholder(placeholder: J): J {
277
+ const placeholderText = this.getPlaceholderText(placeholder);
278
+
279
+ if (!placeholderText || !placeholderText.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX)) {
280
+ return placeholder;
281
+ }
282
+
283
+ // Find the corresponding parameter
284
+ const param = this.substitutions.get(placeholderText);
285
+ if (!param || param.value === undefined) {
286
+ return placeholder;
287
+ }
288
+
289
+ // Check if the parameter value is a CaptureValue
290
+ const isCaptureValue = param.value instanceof CaptureValue;
291
+
292
+ if (isCaptureValue) {
293
+ // Resolve the capture value to get the actual property value
294
+ const propertyValue = param.value.resolve(this.values);
295
+
296
+ if (propertyValue !== undefined) {
297
+ // If the property value is already a J node, use it
298
+ if (isTree(propertyValue)) {
299
+ const propValueAsJ = propertyValue as J;
300
+ return produce(propValueAsJ, draft => {
301
+ draft.markers = placeholder.markers;
302
+ draft.prefix = this.mergePrefix(propValueAsJ.prefix, placeholder.prefix);
303
+ });
304
+ }
305
+ // If it's a primitive value and placeholder is an identifier, update the simpleName
306
+ if (typeof propertyValue === 'string' && placeholder.kind === J.Kind.Identifier) {
307
+ return produce(placeholder as J.Identifier, draft => {
308
+ draft.simpleName = propertyValue;
309
+ });
310
+ }
311
+ // If it's a primitive value and placeholder is a literal, update the value
312
+ if (typeof propertyValue === 'string' && placeholder.kind === J.Kind.Literal) {
313
+ return produce(placeholder as J.Literal, draft => {
314
+ draft.value = propertyValue;
315
+ draft.valueSource = `"${propertyValue}"`;
316
+ });
317
+ }
318
+ }
319
+
320
+ // If no match found or unhandled type, return placeholder unchanged
321
+ return placeholder;
322
+ }
323
+
324
+ // Check if the parameter value is a Capture (could be a Proxy) or TemplateParam
325
+ const isCapture = param.value instanceof CaptureImpl ||
326
+ (param.value && typeof param.value === 'object' && param.value[CAPTURE_NAME_SYMBOL]);
327
+ const isTemplateParam = param.value instanceof TemplateParamImpl;
328
+
329
+ if (isCapture || isTemplateParam) {
330
+ // Simple capture/template param (no property path for template params)
331
+ const name = isTemplateParam ? param.value.name :
332
+ (param.value[CAPTURE_NAME_SYMBOL] || param.value.name);
333
+ const matchedNode = this.values.get(name);
334
+ if (matchedNode && !Array.isArray(matchedNode)) {
335
+ return produce(matchedNode, draft => {
336
+ draft.markers = placeholder.markers;
337
+ draft.prefix = this.mergePrefix(matchedNode.prefix, placeholder.prefix);
338
+ });
339
+ }
340
+
341
+ // If no match found, return placeholder unchanged
342
+ return placeholder;
343
+ }
344
+
345
+ // If the parameter value is an AST node, use it directly
346
+ if (isTree(param.value)) {
347
+ // Return the AST node, preserving comments from the source
348
+ return produce(param.value as J, draft => {
349
+ draft.markers = placeholder.markers;
350
+ draft.prefix = this.mergePrefix(param.value.prefix, placeholder.prefix);
351
+ });
352
+ }
353
+
354
+ return placeholder;
355
+ }
356
+
357
+ /**
358
+ * Gets the placeholder text from a node.
359
+ *
360
+ * @param node The node to get placeholder text from
361
+ * @returns The placeholder text or null
362
+ */
363
+ private getPlaceholderText(node: J): string | null {
364
+ if (node.kind === J.Kind.Identifier) {
365
+ return (node as J.Identifier).simpleName;
366
+ } else if (node.kind === J.Kind.Literal) {
367
+ return (node as J.Literal).valueSource || null;
368
+ }
369
+ return null;
370
+ }
371
+
372
+ }
@@ -0,0 +1,95 @@
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} from '../..';
17
+ import {J} from '../../java';
18
+ import {RewriteRule, RewriteConfig} from './types';
19
+ import {Pattern} from './pattern';
20
+ import {Template} from './template';
21
+
22
+ /**
23
+ * Implementation of a replacement rule.
24
+ */
25
+ class RewriteRuleImpl implements RewriteRule {
26
+ constructor(
27
+ private readonly before: Pattern[],
28
+ private readonly after: Template
29
+ ) {
30
+ }
31
+
32
+ async tryOn(cursor: Cursor, node: J): Promise<J | undefined> {
33
+ for (const pattern of this.before) {
34
+ const match = await pattern.match(node);
35
+ if (match) {
36
+ const result = await this.after.apply(cursor, node, match);
37
+ if (result) {
38
+ return result;
39
+ }
40
+ }
41
+ }
42
+
43
+ // Return undefined if no patterns match
44
+ return undefined;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Creates a replacement rule using a capture context and configuration.
50
+ *
51
+ * @param builderFn Function that takes a capture context and returns before/after configuration
52
+ * @returns A replacement rule that can be applied to AST nodes
53
+ *
54
+ * @example
55
+ * // Single pattern
56
+ * const swapOperands = rewrite(() => ({
57
+ * before: pattern`${"left"} + ${"right"}`,
58
+ * after: template`${"right"} + ${"left"}`
59
+ * }));
60
+ *
61
+ * @example
62
+ * // Multiple patterns
63
+ * const normalizeComparisons = rewrite(() => ({
64
+ * before: [
65
+ * pattern`${"left"} == ${"right"}`,
66
+ * pattern`${"left"} === ${"right"}`
67
+ * ],
68
+ * after: template`${"left"} === ${"right"}`
69
+ * }));
70
+ *
71
+ * @example
72
+ * // Using in a visitor - IMPORTANT: use `|| node` to handle undefined when no match
73
+ * class MyVisitor extends JavaScriptVisitor<any> {
74
+ * override async visitBinary(binary: J.Binary, p: any): Promise<J | undefined> {
75
+ * const rule = rewrite(() => ({
76
+ * before: pattern`${capture('a')} + ${capture('b')}`,
77
+ * after: template`${capture('b')} + ${capture('a')}`
78
+ * }));
79
+ * // tryOn() returns undefined if no pattern matches, so always use || node
80
+ * return await rule.tryOn(this.cursor, binary) || binary;
81
+ * }
82
+ * }
83
+ */
84
+ export function rewrite(
85
+ builderFn: () => RewriteConfig
86
+ ): RewriteRule {
87
+ const config = builderFn();
88
+
89
+ // Ensure we have valid before and after properties
90
+ if (!config.before || !config.after) {
91
+ throw new Error('Builder function must return an object with before and after properties');
92
+ }
93
+
94
+ return new RewriteRuleImpl(Array.isArray(config.before) ? config.before : [config.before], config.after);
95
+ }