@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
@@ -1,1069 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.Template = exports.MatchResult = exports.Pattern = exports._ = void 0;
13
- exports.capture = capture;
14
- exports.pattern = pattern;
15
- exports.template = template;
16
- exports.rewrite = rewrite;
17
- /*
18
- * Copyright 2025 the original author or authors.
19
- * <p>
20
- * Licensed under the Moderne Source Available License (the "License");
21
- * you may not use this file except in compliance with the License.
22
- * You may obtain a copy of the License at
23
- * <p>
24
- * https://docs.moderne.io/licensing/moderne-source-available-license
25
- * <p>
26
- * Unless required by applicable law or agreed to in writing, software
27
- * distributed under the License is distributed on an "AS IS" BASIS,
28
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29
- * See the License for the specific language governing permissions and
30
- * limitations under the License.
31
- */
32
- const _1 = require(".");
33
- const parser_1 = require("./parser");
34
- const visitor_1 = require("./visitor");
35
- const __1 = require("..");
36
- const java_1 = require("../java");
37
- const immer_1 = require("immer");
38
- const comparator_1 = require("./comparator");
39
- const dependency_workspace_1 = require("./dependency-workspace");
40
- const uuid_1 = require("../uuid");
41
- /**
42
- * Cache for compiled templates and patterns.
43
- * Stores parsed ASTs to avoid expensive re-parsing and dependency resolution.
44
- */
45
- class TemplateCache {
46
- constructor() {
47
- this.cache = new Map();
48
- }
49
- /**
50
- * Generates a cache key from template string, captures, and options.
51
- */
52
- generateKey(templateString, captures, imports, dependencies) {
53
- // Use the actual template string (with placeholders) as the primary key
54
- const templateKey = templateString;
55
- // Capture names
56
- const capturesKey = captures.map(c => c.name).join(',');
57
- // Imports
58
- const importsKey = imports.join(';');
59
- // Dependencies
60
- const depsKey = JSON.stringify(dependencies || {});
61
- return `${templateKey}::${capturesKey}::${importsKey}::${depsKey}`;
62
- }
63
- /**
64
- * Gets a cached compilation unit or creates and caches a new one.
65
- */
66
- getOrParse(templateString, captures, imports, dependencies) {
67
- return __awaiter(this, void 0, void 0, function* () {
68
- const key = this.generateKey(templateString, captures, imports, dependencies);
69
- let cu = this.cache.get(key);
70
- if (cu) {
71
- return cu;
72
- }
73
- // Create workspace if dependencies are provided
74
- // DependencyWorkspace has its own cache, so multiple templates with
75
- // the same dependencies will automatically share the same workspace
76
- let workspaceDir;
77
- if (dependencies && Object.keys(dependencies).length > 0) {
78
- workspaceDir = yield dependency_workspace_1.DependencyWorkspace.getOrCreateWorkspace(dependencies);
79
- }
80
- // Prepend imports for type attribution context
81
- const fullTemplateString = imports.length > 0
82
- ? imports.join('\n') + '\n' + templateString
83
- : templateString;
84
- // Parse and cache (workspace only needed during parsing)
85
- const parser = new parser_1.JavaScriptParser({ relativeTo: workspaceDir });
86
- const parseGenerator = parser.parse({ text: fullTemplateString, sourcePath: 'template.ts' });
87
- cu = (yield parseGenerator.next()).value;
88
- this.cache.set(key, cu);
89
- return cu;
90
- });
91
- }
92
- /**
93
- * Clears the cache.
94
- */
95
- clear() {
96
- this.cache.clear();
97
- }
98
- }
99
- // Global cache instance
100
- const templateCache = new TemplateCache();
101
- /**
102
- * Marker that stores capture metadata on pattern AST nodes.
103
- * This avoids the need to parse capture names from identifiers during matching.
104
- */
105
- class CaptureMarker {
106
- constructor(captureName) {
107
- this.captureName = captureName;
108
- this.kind = 'org.openrewrite.javascript.CaptureMarker';
109
- this.id = (0, uuid_1.randomId)();
110
- }
111
- }
112
- class CaptureImpl {
113
- constructor(name) {
114
- this.name = name;
115
- }
116
- }
117
- /**
118
- * Creates a capture specification for use in template patterns.
119
- *
120
- * @param name Optional name for the capture. If not provided, an auto-generated name is used.
121
- * @returns A Capture object
122
- *
123
- * @example
124
- * // Named inline captures
125
- * const pattern = pattern`${capture('left')} + ${capture('right')}`;
126
- *
127
- * // Unnamed captures
128
- * const {left, right} = {left: capture(), right: capture()};
129
- * const pattern = pattern`${left} + ${right}`;
130
- *
131
- * // Repeated patterns using the same capture
132
- * const expr = capture('expr');
133
- * const redundantOr = pattern`${expr} || ${expr}`;
134
- */
135
- function capture(name) {
136
- if (name) {
137
- return new CaptureImpl(name);
138
- }
139
- return new CaptureImpl(`unnamed_${capture.nextUnnamedId++}`);
140
- }
141
- // Static counter for generating unique IDs for unnamed captures
142
- capture.nextUnnamedId = 1;
143
- /**
144
- * Concise alias for `capture`. Works well for inline captures in patterns and templates.
145
- *
146
- * @param name Optional name for the capture. If not provided, an auto-generated name is used.
147
- * @returns A Capture object
148
- *
149
- * @example
150
- * // Inline captures with _ alias
151
- * pattern`isDate(${_('dateArg')})`
152
- * template`${_('dateArg')} instanceof Date`
153
- */
154
- exports._ = capture;
155
- /**
156
- * Represents a pattern that can be matched against AST nodes.
157
- */
158
- class Pattern {
159
- /**
160
- * Gets the configuration options for this pattern.
161
- * @readonly
162
- */
163
- get options() {
164
- return this._options;
165
- }
166
- /**
167
- * Creates a new pattern from template parts and captures.
168
- *
169
- * @param templateParts The string parts of the template
170
- * @param captures The captures between the string parts
171
- */
172
- constructor(templateParts, captures) {
173
- this.templateParts = templateParts;
174
- this.captures = captures;
175
- this._options = {};
176
- }
177
- /**
178
- * Configures this pattern with additional options.
179
- *
180
- * @param options Configuration options
181
- * @returns This pattern for method chaining
182
- *
183
- * @example
184
- * pattern`isDate(${capture('date')})`
185
- * .configure({
186
- * imports: ['import { isDate } from "util"'],
187
- * dependencies: { 'util': '^1.0.0' }
188
- * })
189
- */
190
- configure(options) {
191
- this._options = Object.assign(Object.assign({}, this._options), options);
192
- return this;
193
- }
194
- /**
195
- * Creates a matcher for this pattern against a specific AST node.
196
- *
197
- * @param ast The AST node to match against
198
- * @returns A Matcher object
199
- */
200
- match(ast) {
201
- return __awaiter(this, void 0, void 0, function* () {
202
- const matcher = new Matcher(this, ast);
203
- const success = yield matcher.matches();
204
- return success ? new MatchResult(matcher.getAll()) : undefined;
205
- });
206
- }
207
- }
208
- exports.Pattern = Pattern;
209
- class MatchResult {
210
- constructor(bindings = new Map()) {
211
- this.bindings = bindings;
212
- }
213
- get(capture) {
214
- const name = typeof capture === "string" ? capture : capture.name;
215
- return this.bindings.get(name);
216
- }
217
- }
218
- exports.MatchResult = MatchResult;
219
- /**
220
- * A comparator visitor that checks semantic equality including type attribution.
221
- * This ensures that patterns only match code with compatible types, not just
222
- * structurally similar code.
223
- */
224
- class JavaScriptTemplateSemanticallyEqualVisitor extends comparator_1.JavaScriptComparatorVisitor {
225
- /**
226
- * Checks if two types are semantically equal.
227
- * For method types, this checks that the declaring type and method name match.
228
- */
229
- isOfType(target, source) {
230
- if (!target || !source) {
231
- return target === source;
232
- }
233
- if (target.kind !== source.kind) {
234
- return false;
235
- }
236
- // For method types, check declaring type
237
- // Note: We don't check the name field because it might not be fully resolved in patterns
238
- // The method invocation visitor already checks that simple names match
239
- if (target.kind === java_1.Type.Kind.Method && source.kind === java_1.Type.Kind.Method) {
240
- const targetMethod = target;
241
- const sourceMethod = source;
242
- // Only check that declaring types match
243
- return this.isOfType(targetMethod.declaringType, sourceMethod.declaringType);
244
- }
245
- // For fully qualified types, check the fully qualified name
246
- if (java_1.Type.isFullyQualified(target) && java_1.Type.isFullyQualified(source)) {
247
- return java_1.Type.FullyQualified.getFullyQualifiedName(target) ===
248
- java_1.Type.FullyQualified.getFullyQualifiedName(source);
249
- }
250
- // Default: types are equal if they're the same kind
251
- return true;
252
- }
253
- /**
254
- * Override method invocation comparison to include type attribution checking.
255
- * When types match semantically, we allow matching even if one has a receiver
256
- * and the other doesn't (e.g., `isDate(x)` vs `util.isDate(x)`).
257
- */
258
- visitMethodInvocation(method, other) {
259
- const _super = Object.create(null, {
260
- visitMethodInvocation: { get: () => super.visitMethodInvocation }
261
- });
262
- return __awaiter(this, void 0, void 0, function* () {
263
- if (other.kind !== java_1.J.Kind.MethodInvocation) {
264
- return method;
265
- }
266
- const otherMethod = other;
267
- // Check basic structural equality first
268
- if (method.name.simpleName !== otherMethod.name.simpleName ||
269
- method.arguments.elements.length !== otherMethod.arguments.elements.length) {
270
- this.abort();
271
- return method;
272
- }
273
- // Check type attribution
274
- // Both must have method types for semantic equality
275
- if (!method.methodType || !otherMethod.methodType) {
276
- // If template has type but target doesn't, they don't match
277
- if (method.methodType || otherMethod.methodType) {
278
- this.abort();
279
- return method;
280
- }
281
- // If neither has type, fall through to structural comparison
282
- return _super.visitMethodInvocation.call(this, method, other);
283
- }
284
- // Both have types - check they match semantically
285
- const typesMatch = this.isOfType(method.methodType, otherMethod.methodType);
286
- if (!typesMatch) {
287
- // Types don't match - abort comparison
288
- this.abort();
289
- return method;
290
- }
291
- // Types match! Now we can ignore receiver differences and just compare arguments.
292
- // This allows pattern `isDate(x)` to match both `isDate(x)` and `util.isDate(x)`
293
- // when they have the same type attribution.
294
- // Compare type parameters
295
- if ((method.typeParameters === undefined) !== (otherMethod.typeParameters === undefined)) {
296
- this.abort();
297
- return method;
298
- }
299
- if (method.typeParameters && otherMethod.typeParameters) {
300
- if (method.typeParameters.elements.length !== otherMethod.typeParameters.elements.length) {
301
- this.abort();
302
- return method;
303
- }
304
- for (let i = 0; i < method.typeParameters.elements.length; i++) {
305
- yield this.visit(method.typeParameters.elements[i].element, otherMethod.typeParameters.elements[i].element);
306
- if (!this.match)
307
- return method;
308
- }
309
- }
310
- // Compare name (already checked simpleName above, but visit for markers/prefix)
311
- yield this.visit(method.name, otherMethod.name);
312
- if (!this.match)
313
- return method;
314
- // Compare arguments
315
- for (let i = 0; i < method.arguments.elements.length; i++) {
316
- yield this.visit(method.arguments.elements[i].element, otherMethod.arguments.elements[i].element);
317
- if (!this.match)
318
- return method;
319
- }
320
- return method;
321
- });
322
- }
323
- /**
324
- * Override identifier comparison to include type checking for field access.
325
- */
326
- visitIdentifier(identifier, other) {
327
- const _super = Object.create(null, {
328
- visitIdentifier: { get: () => super.visitIdentifier }
329
- });
330
- return __awaiter(this, void 0, void 0, function* () {
331
- if (other.kind !== java_1.J.Kind.Identifier) {
332
- return identifier;
333
- }
334
- const otherIdentifier = other;
335
- // Check name matches
336
- if (identifier.simpleName !== otherIdentifier.simpleName) {
337
- return identifier;
338
- }
339
- // For identifiers with field types, check type attribution
340
- if (identifier.fieldType && otherIdentifier.fieldType) {
341
- if (!this.isOfType(identifier.fieldType, otherIdentifier.fieldType)) {
342
- this.abort();
343
- return identifier;
344
- }
345
- }
346
- else if (identifier.fieldType || otherIdentifier.fieldType) {
347
- // If only one has a type, they don't match
348
- this.abort();
349
- return identifier;
350
- }
351
- return _super.visitIdentifier.call(this, identifier, other);
352
- });
353
- }
354
- }
355
- /**
356
- * Matcher for checking if a pattern matches an AST node and extracting captured nodes.
357
- */
358
- class Matcher {
359
- /**
360
- * Creates a new matcher for a pattern against an AST node.
361
- *
362
- * @param pattern The pattern to match
363
- * @param ast The AST node to match against
364
- */
365
- constructor(pattern, ast) {
366
- this.pattern = pattern;
367
- this.ast = ast;
368
- this.bindings = new Map();
369
- }
370
- /**
371
- * Checks if the pattern matches the AST node.
372
- *
373
- * @returns true if the pattern matches, false otherwise
374
- */
375
- matches() {
376
- return __awaiter(this, void 0, void 0, function* () {
377
- if (!this.patternAst) {
378
- const templateProcessor = new TemplateProcessor(this.pattern.templateParts, this.pattern.captures, this.pattern.options.imports || [], this.pattern.options.dependencies || {});
379
- this.patternAst = yield templateProcessor.toAstPattern();
380
- }
381
- return this.matchNode(this.patternAst, this.ast);
382
- });
383
- }
384
- /**
385
- * Gets all captured nodes.
386
- *
387
- * @returns A map of capture names to captured nodes
388
- */
389
- getAll() {
390
- return new Map(this.bindings);
391
- }
392
- /**
393
- * Matches a pattern node against a target node.
394
- *
395
- * @param pattern The pattern node
396
- * @param target The target node
397
- * @returns true if the pattern matches the target, false otherwise
398
- */
399
- matchNode(pattern, target) {
400
- return __awaiter(this, void 0, void 0, function* () {
401
- // Check if pattern is a capture placeholder
402
- if (PlaceholderUtils.isCapture(pattern)) {
403
- return this.handleCapture(pattern, target);
404
- }
405
- // Check if nodes have the same kind
406
- if (pattern.kind !== target.kind) {
407
- return false;
408
- }
409
- const matcher = this;
410
- return yield ((new class extends JavaScriptTemplateSemanticallyEqualVisitor {
411
- hasSameKind(j, other) {
412
- return super.hasSameKind(j, other) || j.kind == java_1.J.Kind.Identifier && this.matchesParameter(j, other);
413
- }
414
- visitIdentifier(identifier, other) {
415
- const _super = Object.create(null, {
416
- visitIdentifier: { get: () => super.visitIdentifier }
417
- });
418
- return __awaiter(this, void 0, void 0, function* () {
419
- return this.matchesParameter(identifier, other) ? identifier : yield _super.visitIdentifier.call(this, identifier, other);
420
- });
421
- }
422
- matchesParameter(identifier, other) {
423
- return PlaceholderUtils.isCapture(identifier) && matcher.handleCapture(identifier, other);
424
- }
425
- }).compare(pattern, target));
426
- });
427
- }
428
- /**
429
- * Handles a capture placeholder.
430
- *
431
- * @param pattern The pattern node
432
- * @param target The target node
433
- * @returns true if the capture is successful, false otherwise
434
- */
435
- handleCapture(pattern, target) {
436
- const captureName = PlaceholderUtils.getCaptureName(pattern);
437
- if (!captureName) {
438
- return false;
439
- }
440
- // Store the binding
441
- this.bindings.set(captureName, target);
442
- return true;
443
- }
444
- }
445
- /**
446
- * Tagged template function for creating patterns.
447
- *
448
- * @param strings The string parts of the template
449
- * @param captures The captures between the string parts
450
- * @returns A Pattern object
451
- *
452
- * @example
453
- * // Using the same capture multiple times for repeated patterns
454
- * const expr = capture('expr');
455
- * const redundantOr = pattern`${expr} || ${expr}`;
456
- */
457
- function pattern(strings, ...captures) {
458
- const capturesByName = captures.reduce((map, c) => {
459
- const capture = typeof c === "string" ? new CaptureImpl(c) : c;
460
- return map.set(capture.name, capture);
461
- }, new Map());
462
- return new Pattern(strings, captures.map(c => capturesByName.get(typeof c === "string" ? c : c.name)));
463
- }
464
- var JavaCoordinates;
465
- (function (JavaCoordinates) {
466
- let Mode;
467
- (function (Mode) {
468
- Mode[Mode["Before"] = 0] = "Before";
469
- Mode[Mode["After"] = 1] = "After";
470
- Mode[Mode["Replace"] = 2] = "Replace";
471
- })(Mode = JavaCoordinates.Mode || (JavaCoordinates.Mode = {}));
472
- })(JavaCoordinates || (JavaCoordinates = {}));
473
- /**
474
- * Template for creating AST nodes.
475
- *
476
- * This class provides the public API for template generation.
477
- * The actual templating logic is handled by the internal TemplateEngine.
478
- *
479
- * @example
480
- * // Generate a literal AST node
481
- * const result = template`2`.apply(cursor, coordinates);
482
- *
483
- * @example
484
- * // Generate an AST node with a parameter
485
- * const result = template`${capture()}`.apply(cursor, coordinates);
486
- */
487
- class Template {
488
- /**
489
- * Creates a new template.
490
- *
491
- * @param templateParts The string parts of the template
492
- * @param parameters The parameters between the string parts
493
- */
494
- constructor(templateParts, parameters) {
495
- this.templateParts = templateParts;
496
- this.parameters = parameters;
497
- this.options = {};
498
- }
499
- /**
500
- * Configures this template with additional options.
501
- *
502
- * @param options Configuration options
503
- * @returns This template for method chaining
504
- *
505
- * @example
506
- * template`isDate(${capture('date')})`
507
- * .configure({
508
- * imports: ['import { isDate } from "util"'],
509
- * dependencies: { 'util': '^1.0.0' }
510
- * })
511
- */
512
- configure(options) {
513
- this.options = Object.assign(Object.assign({}, this.options), options);
514
- return this;
515
- }
516
- /**
517
- * Applies this template and returns the resulting tree.
518
- *
519
- * @param cursor The cursor pointing to the current location in the AST
520
- * @param tree Input tree
521
- * @param values values for parameters in template
522
- * @returns A Promise resolving to the generated AST node
523
- */
524
- apply(cursor, tree, values) {
525
- return __awaiter(this, void 0, void 0, function* () {
526
- return TemplateEngine.applyTemplate(this.templateParts, this.parameters, cursor, {
527
- tree,
528
- mode: JavaCoordinates.Mode.Replace
529
- }, values, this.options.imports || [], this.options.dependencies || {});
530
- });
531
- }
532
- }
533
- exports.Template = Template;
534
- function template(strings, ...parameters) {
535
- // Convert parameters to Parameter objects (no longer need to check for mutable tree property)
536
- const processedParameters = parameters.map(param => {
537
- // Just wrap each parameter value in a Parameter object
538
- return { value: param };
539
- });
540
- return new Template(strings, processedParameters);
541
- }
542
- /**
543
- * Internal template engine - handles the core templating logic.
544
- * Not exported, so only visible within this module.
545
- */
546
- class TemplateEngine {
547
- /**
548
- * Applies a template with optional match results from pattern matching.
549
- *
550
- * @param templateParts The string parts of the template
551
- * @param parameters The parameters between the string parts
552
- * @param cursor The cursor pointing to the current location in the AST
553
- * @param coordinates The coordinates specifying where and how to insert the generated AST
554
- * @param values Map of capture names to values to replace the parameters with
555
- * @param imports Import statements to prepend for type attribution
556
- * @param dependencies NPM dependencies for type attribution
557
- * @returns A Promise resolving to the generated AST node
558
- */
559
- static applyTemplate(templateParts_1, parameters_1, cursor_1, coordinates_1) {
560
- return __awaiter(this, arguments, void 0, function* (templateParts, parameters, cursor, coordinates, values = new Map(), imports = [], dependencies = {}) {
561
- // Build the template string with parameter placeholders
562
- const templateString = TemplateEngine.buildTemplateString(templateParts, parameters);
563
- // If the template string is empty, return undefined
564
- if (!templateString.trim()) {
565
- return undefined;
566
- }
567
- // Use cache to get or parse the compilation unit
568
- // For templates, we don't have captures, so use empty array
569
- const cu = yield templateCache.getOrParse(templateString, [], // templates don't have captures in the cache key
570
- imports, dependencies);
571
- // Check if there are any statements
572
- if (!cu.statements || cu.statements.length === 0) {
573
- return undefined;
574
- }
575
- // Skip import statements to get to the actual template code
576
- const templateStatementIndex = imports.length;
577
- if (templateStatementIndex >= cu.statements.length) {
578
- return undefined;
579
- }
580
- // Extract the relevant part of the AST
581
- const firstStatement = cu.statements[templateStatementIndex].element;
582
- let extracted = firstStatement.kind === _1.JS.Kind.ExpressionStatement ?
583
- firstStatement.expression :
584
- firstStatement;
585
- // Create a copy to avoid sharing cached AST instances
586
- const ast = (0, immer_1.produce)(extracted, draft => { });
587
- // Create substitutions map for placeholders
588
- const substitutions = new Map();
589
- for (let i = 0; i < parameters.length; i++) {
590
- const placeholder = `${PlaceholderUtils.PLACEHOLDER_PREFIX}${i}__`;
591
- substitutions.set(placeholder, parameters[i]);
592
- }
593
- // Unsubstitute placeholders with actual parameter values and match results
594
- const visitor = new PlaceholderReplacementVisitor(substitutions, values);
595
- const unsubstitutedAst = (yield visitor.visit(ast, null));
596
- // Apply the template to the current AST
597
- return new TemplateApplier(cursor, coordinates, unsubstitutedAst, parameters).apply();
598
- });
599
- }
600
- /**
601
- * Builds a template string with parameter placeholders.
602
- *
603
- * @param templateParts The string parts of the template
604
- * @param parameters The parameters between the string parts
605
- * @returns The template string
606
- */
607
- static buildTemplateString(templateParts, parameters) {
608
- let result = '';
609
- for (let i = 0; i < templateParts.length; i++) {
610
- result += templateParts[i];
611
- if (i < parameters.length) {
612
- const param = parameters[i].value;
613
- // Use a placeholder for Captures and Tree nodes
614
- // Inline everything else (strings, numbers, booleans) directly
615
- if (param instanceof CaptureImpl || (0, __1.isTree)(param)) {
616
- const placeholder = `${PlaceholderUtils.PLACEHOLDER_PREFIX}${i}__`;
617
- result += placeholder;
618
- }
619
- else {
620
- result += param;
621
- }
622
- }
623
- }
624
- return result;
625
- }
626
- }
627
- /**
628
- * Utility class for managing placeholder naming and parsing.
629
- * Centralizes all logic related to capture placeholders.
630
- */
631
- class PlaceholderUtils {
632
- /**
633
- * Checks if a node is a capture placeholder.
634
- *
635
- * @param node The node to check
636
- * @returns true if the node is a capture placeholder, false otherwise
637
- */
638
- static isCapture(node) {
639
- // Check for CaptureMarker first (efficient)
640
- for (const marker of node.markers.markers) {
641
- if (marker instanceof CaptureMarker) {
642
- return true;
643
- }
644
- }
645
- return false;
646
- }
647
- /**
648
- * Gets the capture name from a node with a CaptureMarker.
649
- *
650
- * @param node The node to extract capture name from
651
- * @returns The capture name, or null if not a capture
652
- */
653
- static getCaptureName(node) {
654
- // Check for CaptureMarker
655
- for (const marker of node.markers.markers) {
656
- if (marker instanceof CaptureMarker) {
657
- return marker.captureName;
658
- }
659
- }
660
- return undefined;
661
- }
662
- /**
663
- * Parses a capture placeholder to extract name and type constraint.
664
- *
665
- * @param identifier The identifier string to parse
666
- * @returns Object with name and optional type constraint, or null if not a valid capture
667
- */
668
- static parseCapture(identifier) {
669
- if (!identifier.startsWith(this.CAPTURE_PREFIX)) {
670
- return null;
671
- }
672
- // Handle unnamed captures: "__capture_unnamed_N__"
673
- if (identifier.startsWith(`${this.CAPTURE_PREFIX}unnamed_`)) {
674
- const match = identifier.match(/__capture_(unnamed_\d+)__/);
675
- return match ? { name: match[1] } : null;
676
- }
677
- // Handle named captures: "__capture_name__" or "__capture_name_type__"
678
- const match = identifier.match(/__capture_([^_]+)(?:_([^_]+))?__/);
679
- if (!match) {
680
- return null;
681
- }
682
- return {
683
- name: match[1],
684
- typeConstraint: match[2]
685
- };
686
- }
687
- /**
688
- * Creates a capture placeholder string.
689
- *
690
- * @param name The capture name
691
- * @param typeConstraint Optional type constraint
692
- * @returns The formatted placeholder string
693
- */
694
- static createCapture(name, typeConstraint) {
695
- return typeConstraint
696
- ? `${this.CAPTURE_PREFIX}${name}_${typeConstraint}__`
697
- : `${this.CAPTURE_PREFIX}${name}__`;
698
- }
699
- }
700
- PlaceholderUtils.CAPTURE_PREFIX = '__capture_';
701
- PlaceholderUtils.PLACEHOLDER_PREFIX = '__PLACEHOLDER_';
702
- /**
703
- * Visitor that replaces placeholder nodes with actual parameter values.
704
- */
705
- class PlaceholderReplacementVisitor extends visitor_1.JavaScriptVisitor {
706
- constructor(substitutions, values = new Map()) {
707
- super();
708
- this.substitutions = substitutions;
709
- this.values = values;
710
- }
711
- visit(tree, p, parent) {
712
- const _super = Object.create(null, {
713
- visit: { get: () => super.visit }
714
- });
715
- return __awaiter(this, void 0, void 0, function* () {
716
- // Check if this node is a placeholder
717
- if (this.isPlaceholder(tree)) {
718
- const replacement = this.replacePlaceholder(tree);
719
- if (replacement !== tree) {
720
- return replacement;
721
- }
722
- }
723
- // Continue with normal traversal
724
- return _super.visit.call(this, tree, p, parent);
725
- });
726
- }
727
- /**
728
- * Checks if a node is a placeholder.
729
- *
730
- * @param node The node to check
731
- * @returns True if the node is a placeholder
732
- */
733
- isPlaceholder(node) {
734
- var _a;
735
- if (node.kind === java_1.J.Kind.Identifier) {
736
- const identifier = node;
737
- return identifier.simpleName.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX);
738
- }
739
- else if (node.kind === java_1.J.Kind.Literal) {
740
- const literal = node;
741
- return ((_a = literal.valueSource) === null || _a === void 0 ? void 0 : _a.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX)) || false;
742
- }
743
- return false;
744
- }
745
- /**
746
- * Replaces a placeholder node with the actual parameter value.
747
- *
748
- * @param placeholder The placeholder node
749
- * @returns The replacement node or the original if not a placeholder
750
- */
751
- replacePlaceholder(placeholder) {
752
- const placeholderText = this.getPlaceholderText(placeholder);
753
- if (!placeholderText || !placeholderText.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX)) {
754
- return placeholder;
755
- }
756
- // Find the corresponding parameter
757
- const param = this.substitutions.get(placeholderText);
758
- if (!param || param.value === undefined) {
759
- return placeholder;
760
- }
761
- // If the parameter value is a Capture, look up the matched result
762
- if (param.value instanceof CaptureImpl) {
763
- const matchedNode = this.values.get(param.value.name);
764
- if (matchedNode) {
765
- return (0, immer_1.produce)(matchedNode, draft => {
766
- draft.markers = placeholder.markers;
767
- draft.prefix = placeholder.prefix;
768
- });
769
- }
770
- // If no match found, return placeholder unchanged
771
- return placeholder;
772
- }
773
- // If the parameter value is an AST node, use it directly
774
- if ((0, __1.isTree)(param.value)) {
775
- // Return the AST node, preserving the original prefix
776
- return (0, immer_1.produce)(param.value, draft => {
777
- draft.markers = placeholder.markers;
778
- draft.prefix = placeholder.prefix;
779
- });
780
- }
781
- return placeholder;
782
- }
783
- /**
784
- * Gets the placeholder text from a node.
785
- *
786
- * @param node The node to get placeholder text from
787
- * @returns The placeholder text or null
788
- */
789
- getPlaceholderText(node) {
790
- if (node.kind === java_1.J.Kind.Identifier) {
791
- return node.simpleName;
792
- }
793
- else if (node.kind === java_1.J.Kind.Literal) {
794
- return node.valueSource || null;
795
- }
796
- return null;
797
- }
798
- }
799
- /**
800
- * Helper class for applying a template to an AST.
801
- */
802
- class TemplateApplier {
803
- constructor(cursor, coordinates, ast, parameters = []) {
804
- this.cursor = cursor;
805
- this.coordinates = coordinates;
806
- this.ast = ast;
807
- this.parameters = parameters;
808
- }
809
- /**
810
- * Applies the template to the current AST.
811
- *
812
- * @returns A Promise resolving to the modified AST
813
- */
814
- apply() {
815
- return __awaiter(this, void 0, void 0, function* () {
816
- const { loc } = this.coordinates;
817
- // Apply the template based on the location and mode
818
- switch (loc || 'EXPRESSION_PREFIX') {
819
- case 'EXPRESSION_PREFIX':
820
- return this.applyToExpression();
821
- case 'STATEMENT_PREFIX':
822
- return this.applyToStatement();
823
- case 'BLOCK_END':
824
- return this.applyToBlock();
825
- default:
826
- throw new Error(`Unsupported location: ${loc}`);
827
- }
828
- });
829
- }
830
- /**
831
- * Applies the template to an expression.
832
- *
833
- * @returns A Promise resolving to the modified AST
834
- */
835
- applyToExpression() {
836
- return __awaiter(this, void 0, void 0, function* () {
837
- const { tree } = this.coordinates;
838
- // Create a copy of the AST with the prefix from the target
839
- return tree ? (0, immer_1.produce)(this.ast, draft => {
840
- draft.prefix = tree.prefix;
841
- }) : this.ast;
842
- });
843
- }
844
- /**
845
- * Applies the template to a statement.
846
- *
847
- * @returns A Promise resolving to the modified AST
848
- */
849
- applyToStatement() {
850
- return __awaiter(this, void 0, void 0, function* () {
851
- const { tree } = this.coordinates;
852
- // Create a copy of the AST with the prefix from the target
853
- return (0, immer_1.produce)(this.ast, draft => {
854
- draft.prefix = tree.prefix;
855
- });
856
- });
857
- }
858
- /**
859
- * Applies the template to a block.
860
- *
861
- * @returns A Promise resolving to the modified AST
862
- */
863
- applyToBlock() {
864
- return __awaiter(this, void 0, void 0, function* () {
865
- const { tree } = this.coordinates;
866
- // Create a copy of the AST with the prefix from the target
867
- return (0, immer_1.produce)(this.ast, draft => {
868
- draft.prefix = tree.prefix;
869
- });
870
- });
871
- }
872
- }
873
- /**
874
- * Processor for template strings.
875
- * Converts a template string with captures into an AST pattern.
876
- */
877
- class TemplateProcessor {
878
- /**
879
- * Creates a new template processor.
880
- *
881
- * @param templateParts The string parts of the template
882
- * @param captures The captures between the string parts
883
- * @param imports Import statements to prepend for type attribution
884
- * @param dependencies NPM dependencies for type attribution
885
- */
886
- constructor(templateParts, captures, imports = [], dependencies = {}) {
887
- this.templateParts = templateParts;
888
- this.captures = captures;
889
- this.imports = imports;
890
- this.dependencies = dependencies;
891
- }
892
- /**
893
- * Converts the template to an AST pattern.
894
- *
895
- * @returns A Promise resolving to the AST pattern
896
- */
897
- toAstPattern() {
898
- return __awaiter(this, void 0, void 0, function* () {
899
- // Combine template parts and placeholders
900
- const templateString = this.buildTemplateString();
901
- // Use cache to get or parse the compilation unit
902
- const cu = yield templateCache.getOrParse(templateString, this.captures, this.imports, this.dependencies);
903
- // Extract the relevant part of the AST
904
- return this.extractPatternFromAst(cu);
905
- });
906
- }
907
- /**
908
- * Builds a template string with placeholders for captures.
909
- *
910
- * @returns The template string
911
- */
912
- buildTemplateString() {
913
- let result = '';
914
- for (let i = 0; i < this.templateParts.length; i++) {
915
- result += this.templateParts[i];
916
- if (i < this.captures.length) {
917
- const capture = this.captures[i];
918
- result += PlaceholderUtils.createCapture(capture.name);
919
- }
920
- }
921
- return result;
922
- }
923
- /**
924
- * Extracts the pattern from the parsed AST.
925
- *
926
- * @param cu The compilation unit
927
- * @returns The extracted pattern
928
- */
929
- extractPatternFromAst(cu) {
930
- // Skip import statements to get to the actual pattern code
931
- const patternStatementIndex = this.imports.length;
932
- // Extract the relevant part of the AST based on the template content
933
- const firstStatement = cu.statements[patternStatementIndex].element;
934
- let extracted;
935
- // If the first statement is an expression statement, extract the expression
936
- if (firstStatement.kind === _1.JS.Kind.ExpressionStatement) {
937
- extracted = firstStatement.expression;
938
- }
939
- else {
940
- // Otherwise, return the statement itself
941
- extracted = firstStatement;
942
- }
943
- // Attach CaptureMarkers to capture identifiers
944
- return this.attachCaptureMarkers(extracted);
945
- }
946
- /**
947
- * Attaches CaptureMarkers to capture identifiers in the AST.
948
- * This allows efficient capture detection without string parsing.
949
- *
950
- * @param ast The AST to process
951
- * @returns The AST with CaptureMarkers attached
952
- */
953
- attachCaptureMarkers(ast) {
954
- const visited = new Set();
955
- return (0, immer_1.produce)(ast, draft => {
956
- this.visitAndAttachMarkers(draft, visited);
957
- });
958
- }
959
- /**
960
- * Recursively visits AST nodes and attaches CaptureMarkers to capture identifiers.
961
- *
962
- * @param node The node to visit
963
- * @param visited Set of already visited nodes to avoid cycles
964
- */
965
- visitAndAttachMarkers(node, visited) {
966
- var _a;
967
- if (!node || typeof node !== 'object' || visited.has(node)) {
968
- return;
969
- }
970
- // Mark as visited to avoid cycles
971
- visited.add(node);
972
- // If this is an identifier that looks like a capture, attach a marker
973
- if (node.kind === java_1.J.Kind.Identifier && ((_a = node.simpleName) === null || _a === void 0 ? void 0 : _a.startsWith(PlaceholderUtils.CAPTURE_PREFIX))) {
974
- const captureInfo = PlaceholderUtils.parseCapture(node.simpleName);
975
- if (captureInfo) {
976
- // Initialize markers if needed
977
- if (!node.markers) {
978
- node.markers = { kind: 'org.openrewrite.marker.Markers', id: (0, uuid_1.randomId)(), markers: [] };
979
- }
980
- if (!node.markers.markers) {
981
- node.markers.markers = [];
982
- }
983
- // Add CaptureMarker
984
- node.markers.markers.push(new CaptureMarker(captureInfo.name));
985
- }
986
- }
987
- // Recursively visit all properties
988
- for (const key in node) {
989
- if (node.hasOwnProperty(key)) {
990
- const value = node[key];
991
- if (Array.isArray(value)) {
992
- value.forEach(item => this.visitAndAttachMarkers(item, visited));
993
- }
994
- else if (typeof value === 'object' && value !== null) {
995
- this.visitAndAttachMarkers(value, visited);
996
- }
997
- }
998
- }
999
- }
1000
- }
1001
- /**
1002
- * Implementation of a replacement rule.
1003
- */
1004
- class RewriteRuleImpl {
1005
- constructor(before, after) {
1006
- this.before = before;
1007
- this.after = after;
1008
- }
1009
- tryOn(cursor, node) {
1010
- return __awaiter(this, void 0, void 0, function* () {
1011
- for (const pattern of this.before) {
1012
- const match = yield pattern.match(node);
1013
- if (match) {
1014
- const result = yield this.after.apply(cursor, node, match);
1015
- if (result) {
1016
- return result;
1017
- }
1018
- }
1019
- }
1020
- // Return undefined if no patterns match
1021
- return undefined;
1022
- });
1023
- }
1024
- }
1025
- /**
1026
- * Creates a replacement rule using a capture context and configuration.
1027
- *
1028
- * @param builderFn Function that takes a capture context and returns before/after configuration
1029
- * @returns A replacement rule that can be applied to AST nodes
1030
- *
1031
- * @example
1032
- * // Single pattern
1033
- * const swapOperands = rewrite(() => ({
1034
- * before: pattern`${"left"} + ${"right"}`,
1035
- * after: template`${"right"} + ${"left"}`
1036
- * }));
1037
- *
1038
- * @example
1039
- * // Multiple patterns
1040
- * const normalizeComparisons = rewrite(() => ({
1041
- * before: [
1042
- * pattern`${"left"} == ${"right"}`,
1043
- * pattern`${"left"} === ${"right"}`
1044
- * ],
1045
- * after: template`${"left"} === ${"right"}`
1046
- * }));
1047
- *
1048
- * @example
1049
- * // Using in a visitor - IMPORTANT: use `|| node` to handle undefined when no match
1050
- * class MyVisitor extends JavaScriptVisitor<any> {
1051
- * override async visitBinary(binary: J.Binary, p: any): Promise<J | undefined> {
1052
- * const rule = rewrite(() => ({
1053
- * before: pattern`${capture('a')} + ${capture('b')}`,
1054
- * after: template`${capture('b')} + ${capture('a')}`
1055
- * }));
1056
- * // tryOn() returns undefined if no pattern matches, so always use || node
1057
- * return await rule.tryOn(this.cursor, binary) || binary;
1058
- * }
1059
- * }
1060
- */
1061
- function rewrite(builderFn) {
1062
- const config = builderFn();
1063
- // Ensure we have valid before and after properties
1064
- if (!config.before || !config.after) {
1065
- throw new Error('Builder function must return an object with before and after properties');
1066
- }
1067
- return new RewriteRuleImpl(Array.isArray(config.before) ? config.before : [config.before], config.after);
1068
- }
1069
- //# sourceMappingURL=templating.js.map