@openrewrite/rewrite 8.67.0-20251106-160325 → 8.67.0-20251107-103550

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 (44) hide show
  1. package/dist/java/tree.d.ts +2 -0
  2. package/dist/java/tree.d.ts.map +1 -1
  3. package/dist/java/tree.js +5 -1
  4. package/dist/java/tree.js.map +1 -1
  5. package/dist/javascript/assertions.js +2 -2
  6. package/dist/javascript/assertions.js.map +1 -1
  7. package/dist/javascript/format.js +1 -1
  8. package/dist/javascript/format.js.map +1 -1
  9. package/dist/javascript/templating/engine.d.ts +41 -25
  10. package/dist/javascript/templating/engine.d.ts.map +1 -1
  11. package/dist/javascript/templating/engine.js +378 -92
  12. package/dist/javascript/templating/engine.js.map +1 -1
  13. package/dist/javascript/templating/pattern.d.ts +11 -0
  14. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  15. package/dist/javascript/templating/pattern.js +36 -295
  16. package/dist/javascript/templating/pattern.js.map +1 -1
  17. package/dist/javascript/templating/placeholder-replacement.d.ts +1 -1
  18. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  19. package/dist/javascript/templating/rewrite.d.ts +17 -11
  20. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  21. package/dist/javascript/templating/rewrite.js +17 -11
  22. package/dist/javascript/templating/rewrite.js.map +1 -1
  23. package/dist/javascript/templating/template.d.ts +17 -3
  24. package/dist/javascript/templating/template.d.ts.map +1 -1
  25. package/dist/javascript/templating/template.js +45 -5
  26. package/dist/javascript/templating/template.js.map +1 -1
  27. package/dist/javascript/templating/types.d.ts +36 -13
  28. package/dist/javascript/templating/types.d.ts.map +1 -1
  29. package/dist/javascript/templating/utils.d.ts +41 -22
  30. package/dist/javascript/templating/utils.d.ts.map +1 -1
  31. package/dist/javascript/templating/utils.js +111 -76
  32. package/dist/javascript/templating/utils.js.map +1 -1
  33. package/dist/version.txt +1 -1
  34. package/package.json +3 -1
  35. package/src/java/tree.ts +2 -0
  36. package/src/javascript/assertions.ts +1 -1
  37. package/src/javascript/format.ts +1 -1
  38. package/src/javascript/templating/engine.ts +439 -105
  39. package/src/javascript/templating/pattern.ts +55 -322
  40. package/src/javascript/templating/placeholder-replacement.ts +1 -1
  41. package/src/javascript/templating/rewrite.ts +17 -11
  42. package/src/javascript/templating/template.ts +66 -11
  43. package/src/javascript/templating/types.ts +37 -13
  44. package/src/javascript/templating/utils.ts +114 -81
@@ -14,16 +14,13 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import {Cursor} from '../..';
17
- import {J, Type} from '../../java';
18
- import {JS} from '../index';
19
- import {JavaScriptVisitor} from '../visitor';
20
- import {produceAsync} from '../../visitor';
21
- import {updateIfChanged} from '../../util';
17
+ import {J} from '../../java';
22
18
  import {Any, Capture, PatternOptions} from './types';
23
- import {CAPTURE_CAPTURING_SYMBOL, CAPTURE_NAME_SYMBOL, CAPTURE_TYPE_SYMBOL, CaptureImpl} from './capture';
19
+ import {CAPTURE_CAPTURING_SYMBOL, CAPTURE_NAME_SYMBOL, CaptureImpl} from './capture';
24
20
  import {PatternMatchingComparator} from './comparator';
25
- import {CaptureMarker, CaptureStorageValue, PlaceholderUtils, templateCache, WRAPPERS_MAP_SYMBOL} from './utils';
26
- import {isTree, Tree} from "../../tree";
21
+ import {CaptureMarker, CaptureStorageValue, generateCacheKey, globalAstCache, WRAPPERS_MAP_SYMBOL} from './utils';
22
+ import {TemplateEngine} from './engine';
23
+
27
24
 
28
25
  /**
29
26
  * Builder for creating patterns programmatically.
@@ -121,6 +118,7 @@ export class PatternBuilder {
121
118
  */
122
119
  export class Pattern {
123
120
  private _options: PatternOptions = {};
121
+ private _cachedAstPattern?: J;
124
122
 
125
123
  /**
126
124
  * Gets the configuration options for this pattern.
@@ -175,9 +173,57 @@ export class Pattern {
175
173
  */
176
174
  configure(options: PatternOptions): Pattern {
177
175
  this._options = {...this._options, ...options};
176
+ // Invalidate cache when configuration changes
177
+ this._cachedAstPattern = undefined;
178
178
  return this;
179
179
  }
180
180
 
181
+ /**
182
+ * Gets the AST pattern for this pattern, using two-level caching:
183
+ * 1. Instance-level cache (fastest - this pattern instance)
184
+ * 2. Global LRU cache (fast - shared across pattern instances with same code)
185
+ * 3. Compute via TemplateProcessor (slow - parse and process)
186
+ *
187
+ * @returns The cached or newly computed pattern AST
188
+ * @internal
189
+ */
190
+ async getAstPattern(): Promise<J> {
191
+ // Level 1: Instance cache (fastest path)
192
+ if (this._cachedAstPattern) {
193
+ return this._cachedAstPattern;
194
+ }
195
+
196
+ // Generate cache key for global lookup
197
+ const contextStatements = this._options.context || this._options.imports || [];
198
+ const cacheKey = generateCacheKey(
199
+ this.templateParts,
200
+ this.captures.map(c => c.getName()).join(','),
201
+ contextStatements,
202
+ this._options.dependencies || {}
203
+ );
204
+
205
+ // Level 2: Global cache (fast path - shared with Template)
206
+ const cached = globalAstCache.get(cacheKey);
207
+ if (cached) {
208
+ this._cachedAstPattern = cached;
209
+ return cached;
210
+ }
211
+
212
+ // Level 3: Compute via TemplateEngine (slow path)
213
+ const result = await TemplateEngine.getPatternTree(
214
+ this.templateParts,
215
+ this.captures,
216
+ contextStatements,
217
+ this._options.dependencies || {}
218
+ );
219
+
220
+ // Cache in both levels
221
+ globalAstCache.set(cacheKey, result);
222
+ this._cachedAstPattern = result;
223
+
224
+ return result;
225
+ }
226
+
181
227
  /**
182
228
  * Creates a matcher for this pattern against a specific AST node.
183
229
  *
@@ -325,15 +371,7 @@ class Matcher {
325
371
  */
326
372
  async matches(): Promise<boolean> {
327
373
  if (!this.patternAst) {
328
- // Prefer 'context' over deprecated 'imports'
329
- const contextStatements = this.pattern.options.context || this.pattern.options.imports || [];
330
- const templateProcessor = new TemplateProcessor(
331
- this.pattern.templateParts,
332
- this.pattern.captures,
333
- contextStatements,
334
- this.pattern.options.dependencies || {}
335
- );
336
- this.patternAst = await templateProcessor.toAstPattern();
374
+ this.patternAst = await this.pattern.getAstPattern();
337
375
  }
338
376
 
339
377
  return this.matchNode(this.patternAst, this.ast);
@@ -484,311 +522,6 @@ class Matcher {
484
522
  }
485
523
  }
486
524
 
487
- /**
488
- * Visitor that attaches CaptureMarkers to capture identifiers in the AST.
489
- * Markers are attached to Identifiers, then moved up to wrappers (RightPadded, ExpressionStatement).
490
- * Uses JavaScriptVisitor to properly handle AST traversal and avoid cycles in Type objects.
491
- */
492
- class MarkerAttachmentVisitor extends JavaScriptVisitor<undefined> {
493
- constructor(private readonly captures: (Capture | Any<any>)[]) {
494
- super();
495
- }
496
-
497
- /**
498
- * Attaches CaptureMarker to capture identifiers.
499
- */
500
- protected override async visitIdentifier(ident: J.Identifier, p: undefined): Promise<J | undefined> {
501
- // First call parent to handle standard visitation
502
- const visited = await super.visitIdentifier(ident, p);
503
- if (!visited || visited.kind !== J.Kind.Identifier) {
504
- return visited;
505
- }
506
- ident = visited as J.Identifier;
507
-
508
- // Check if this is a capture placeholder
509
- if (ident.simpleName?.startsWith(PlaceholderUtils.CAPTURE_PREFIX)) {
510
- const captureInfo = PlaceholderUtils.parseCapture(ident.simpleName);
511
- if (captureInfo) {
512
- // Find the original capture object to get variadic options and constraint
513
- const captureObj = this.captures.find(c => c.getName() === captureInfo.name);
514
- const variadicOptions = captureObj?.getVariadicOptions();
515
- const constraint = captureObj?.getConstraint?.();
516
-
517
- // Add CaptureMarker to the Identifier with constraint
518
- const marker = new CaptureMarker(captureInfo.name, variadicOptions, constraint);
519
- return updateIfChanged(ident, {
520
- markers: {
521
- ...ident.markers,
522
- markers: [...ident.markers.markers, marker]
523
- }
524
- });
525
- }
526
- }
527
-
528
- return ident;
529
- }
530
-
531
- /**
532
- * Propagates markers from element to RightPadded wrapper.
533
- */
534
- public override async visitRightPadded<T extends J | boolean>(right: J.RightPadded<T>, p: undefined): Promise<J.RightPadded<T>> {
535
- if (!isTree(right.element)) {
536
- return right;
537
- }
538
-
539
- const visitedElement = await this.visit(right.element as J, p);
540
- if (visitedElement && visitedElement !== right.element as Tree) {
541
- return produceAsync<J.RightPadded<T>>(right, async (draft: any) => {
542
- // Visit element first
543
- if (right.element && (right.element as any).kind) {
544
- // Check if element has a CaptureMarker
545
- const elementMarker = PlaceholderUtils.getCaptureMarker(visitedElement);
546
- if (elementMarker) {
547
- draft.markers.markers.push(elementMarker);
548
- } else {
549
- draft.element = visitedElement;
550
- }
551
- }
552
- });
553
- }
554
-
555
- return right;
556
- }
557
-
558
- /**
559
- * Propagates markers from expression to ExpressionStatement.
560
- */
561
- protected override async visitExpressionStatement(expressionStatement: JS.ExpressionStatement, p: undefined): Promise<J | undefined> {
562
- // Visit the expression
563
- const visitedExpression = await this.visit(expressionStatement.expression, p);
564
-
565
- // Check if expression has a CaptureMarker
566
- const expressionMarker = PlaceholderUtils.getCaptureMarker(visitedExpression as any);
567
- if (expressionMarker) {
568
- return updateIfChanged(expressionStatement, {
569
- markers: {
570
- ...expressionStatement.markers,
571
- markers: [...expressionStatement.markers.markers, expressionMarker]
572
- },
573
- });
574
- }
575
-
576
- // No marker to move, just update with visited expression
577
- return updateIfChanged(expressionStatement, {
578
- expression: visitedExpression
579
- });
580
- }
581
- }
582
-
583
- /**
584
- * Processor for template strings.
585
- * Converts a template string with captures into an AST pattern.
586
- */
587
- class TemplateProcessor {
588
- /**
589
- * Creates a new template processor.
590
- *
591
- * @param templateParts The string parts of the template
592
- * @param captures The captures between the string parts (can be Capture or Any)
593
- * @param contextStatements Context declarations (imports, types, etc.) to prepend for type attribution
594
- * @param dependencies NPM dependencies for type attribution
595
- */
596
- constructor(
597
- private readonly templateParts: TemplateStringsArray,
598
- private readonly captures: (Capture | Any<any>)[],
599
- private readonly contextStatements: string[] = [],
600
- private readonly dependencies: Record<string, string> = {}
601
- ) {
602
- }
603
-
604
- /**
605
- * Converts the template to an AST pattern.
606
- *
607
- * @returns A Promise resolving to the AST pattern
608
- */
609
- async toAstPattern(): Promise<J> {
610
- // Generate type preamble for captures with types
611
- const preamble = this.generateTypePreamble();
612
-
613
- // Combine template parts and placeholders
614
- const templateString = this.buildTemplateString();
615
-
616
- // Add preamble to context statements (so they're skipped during extraction)
617
- const contextWithPreamble = preamble.length > 0
618
- ? [...this.contextStatements, ...preamble]
619
- : this.contextStatements;
620
-
621
- // Use cache to get or parse the compilation unit
622
- const cu = await templateCache.getOrParse(
623
- templateString,
624
- this.captures,
625
- contextWithPreamble,
626
- this.dependencies
627
- );
628
-
629
- // Extract the relevant part of the AST
630
- // The pattern code is always the last statement (after context + preamble)
631
- return await this.extractPatternFromAst(cu);
632
- }
633
-
634
- /**
635
- * Generates type preamble declarations for captures with type annotations.
636
- *
637
- * @returns Array of preamble statements
638
- */
639
- private generateTypePreamble(): string[] {
640
- const preamble: string[] = [];
641
- for (const capture of this.captures) {
642
- const captureName = (capture as any)[CAPTURE_NAME_SYMBOL] || capture.getName();
643
- const captureType = (capture as any)[CAPTURE_TYPE_SYMBOL];
644
- if (captureType) {
645
- // Convert Type to string if needed
646
- const typeString = typeof captureType === 'string'
647
- ? captureType
648
- : this.typeToString(captureType);
649
- const placeholder = PlaceholderUtils.createCapture(captureName, undefined);
650
- preamble.push(`let ${placeholder}: ${typeString};`);
651
- } else {
652
- const placeholder = PlaceholderUtils.createCapture(captureName, undefined);
653
- preamble.push(`let ${placeholder};`);
654
- }
655
- }
656
- return preamble;
657
- }
658
-
659
- /**
660
- * Builds a template string with placeholders for captures.
661
- * If the template looks like a block pattern, wraps it in a function.
662
- *
663
- * @returns The template string
664
- */
665
- private buildTemplateString(): string {
666
- let result = '';
667
- for (let i = 0; i < this.templateParts.length; i++) {
668
- result += this.templateParts[i];
669
- if (i < this.captures.length) {
670
- const capture = this.captures[i];
671
- // Use symbol to access capture name without triggering Proxy
672
- const captureName = (capture as any)[CAPTURE_NAME_SYMBOL] || capture.getName();
673
- result += PlaceholderUtils.createCapture(captureName, undefined);
674
- }
675
- }
676
-
677
- // Check if this looks like a block pattern (starts with { and contains statement keywords)
678
- const trimmed = result.trim();
679
- if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
680
- // Check for statement keywords that indicate this is a block, not an object literal
681
- const hasStatementKeywords = /\b(return|if|for|while|do|switch|try|throw|break|continue|const|let|var|function|class)\b/.test(result);
682
- if (hasStatementKeywords) {
683
- // Wrap in a function to ensure it parses as a block
684
- return `function __PATTERN__() ${result}`;
685
- }
686
- }
687
-
688
- return result;
689
- }
690
-
691
- /**
692
- * Converts a Type instance to a TypeScript type string.
693
- *
694
- * @param type The Type instance
695
- * @returns A TypeScript type string
696
- */
697
- private typeToString(type: Type): string {
698
- // Handle Type.Class and Type.ShallowClass - return their fully qualified names
699
- if (type.kind === Type.Kind.Class || type.kind === Type.Kind.ShallowClass) {
700
- const classType = type as Type.Class;
701
- return classType.fullyQualifiedName;
702
- }
703
-
704
- // Handle Type.Primitive - map to TypeScript primitive types
705
- if (type.kind === Type.Kind.Primitive) {
706
- const primitiveType = type as Type.Primitive;
707
- switch (primitiveType.keyword) {
708
- case 'String':
709
- return 'string';
710
- case 'boolean':
711
- return 'boolean';
712
- case 'double':
713
- case 'float':
714
- case 'int':
715
- case 'long':
716
- case 'short':
717
- case 'byte':
718
- return 'number';
719
- case 'void':
720
- return 'void';
721
- default:
722
- return 'any';
723
- }
724
- }
725
-
726
- // Handle Type.Array - render component type plus []
727
- if (type.kind === Type.Kind.Array) {
728
- const arrayType = type as Type.Array;
729
- const componentTypeString = this.typeToString(arrayType.elemType);
730
- return `${componentTypeString}[]`;
731
- }
732
-
733
- // For other types, return 'any' as a fallback
734
- // TODO: Implement proper Type to string conversion for other Type.Kind values
735
- return 'any';
736
- }
737
-
738
- /**
739
- * Extracts the pattern from the parsed AST.
740
- * The pattern code is always the last statement in the compilation unit
741
- * (after all context statements and type preamble declarations).
742
- *
743
- * @param cu The compilation unit
744
- * @returns The extracted pattern
745
- */
746
- private async extractPatternFromAst(cu: JS.CompilationUnit): Promise<J> {
747
- // Check if we have any statements
748
- if (!cu.statements || cu.statements.length === 0) {
749
- throw new Error(`No statements found in compilation unit`);
750
- }
751
-
752
- // The pattern code is always the last statement
753
- const lastStatement = cu.statements[cu.statements.length - 1].element;
754
-
755
- let extracted: J;
756
-
757
- // Check if this is our wrapper function for block patterns
758
- if (lastStatement.kind === J.Kind.MethodDeclaration) {
759
- const method = lastStatement as J.MethodDeclaration;
760
- if (method.name?.simpleName === '__PATTERN__' && method.body) {
761
- // Extract the block from the wrapper function
762
- extracted = method.body;
763
- } else {
764
- extracted = lastStatement;
765
- }
766
- } else if (lastStatement.kind === JS.Kind.ExpressionStatement) {
767
- // If the statement is an expression statement, extract the expression
768
- extracted = (lastStatement as JS.ExpressionStatement).expression;
769
- } else {
770
- // Otherwise, return the statement itself
771
- extracted = lastStatement;
772
- }
773
-
774
- // Attach CaptureMarkers to capture identifiers
775
- return await this.attachCaptureMarkers(extracted);
776
- }
777
-
778
- /**
779
- * Attaches CaptureMarkers to capture identifiers in the AST.
780
- * This allows efficient capture detection without string parsing.
781
- * Uses JavaScriptVisitor to properly handle AST traversal and avoid cycles in Type objects.
782
- *
783
- * @param ast The AST to process
784
- * @returns The AST with CaptureMarkers attached
785
- */
786
- private async attachCaptureMarkers(ast: J): Promise<J> {
787
- const visitor = new MarkerAttachmentVisitor(this.captures);
788
- return (await visitor.visit(ast, undefined))!;
789
- }
790
- }
791
-
792
525
  /**
793
526
  * Tagged template function for creating patterns.
794
527
  *
@@ -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 './engine';
23
+ import {Parameter} from './types';
24
24
 
25
25
  /**
26
26
  * Visitor that replaces placeholder nodes with actual parameter values.
@@ -121,20 +121,26 @@ class RewriteRuleImpl implements RewriteRule {
121
121
  *
122
122
  * @example
123
123
  * // Single pattern
124
- * const swapOperands = rewrite(() => ({
125
- * before: pattern`${"left"} + ${"right"}`,
126
- * after: template`${"right"} + ${"left"}`
127
- * }));
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
+ * });
128
131
  *
129
132
  * @example
130
133
  * // Multiple patterns
131
- * const normalizeComparisons = rewrite(() => ({
132
- * before: [
133
- * pattern`${"left"} == ${"right"}`,
134
- * pattern`${"left"} === ${"right"}`
135
- * ],
136
- * after: template`${"left"} === ${"right"}`
137
- * }));
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
+ * });
138
144
  *
139
145
  * @example
140
146
  * // Using in a visitor - IMPORTANT: use `|| node` to handle undefined when no match
@@ -15,11 +15,12 @@
15
15
  */
16
16
  import {Cursor, Tree} from '../..';
17
17
  import {J} from '../../java';
18
- import {TemplateOptions, TemplateParameter, Capture} from './types';
18
+ import {Capture, Parameter, TemplateOptions, TemplateParameter} from './types';
19
19
  import {MatchResult} from './pattern';
20
- import {WRAPPERS_MAP_SYMBOL} from './utils';
20
+ import {generateCacheKey, globalAstCache, WRAPPERS_MAP_SYMBOL} from './utils';
21
21
  import {CAPTURE_NAME_SYMBOL} from './capture';
22
- import {TemplateEngine, Parameter} from './engine';
22
+ import {TemplateEngine} from './engine';
23
+ import {JS} from '..';
23
24
 
24
25
  /**
25
26
  * Coordinates for template application.
@@ -171,6 +172,7 @@ export class TemplateBuilder {
171
172
  */
172
173
  export class Template {
173
174
  private options: TemplateOptions = {};
175
+ private _cachedTemplate?: J;
174
176
 
175
177
  /**
176
178
  * Creates a new builder for constructing templates programmatically.
@@ -216,9 +218,62 @@ export class Template {
216
218
  */
217
219
  configure(options: TemplateOptions): Template {
218
220
  this.options = { ...this.options, ...options };
221
+ // Invalidate cache when configuration changes
222
+ this._cachedTemplate = undefined;
219
223
  return this;
220
224
  }
221
225
 
226
+ /**
227
+ * Gets the template tree for this template, using two-level caching:
228
+ * - Level 1: Instance cache (this._cachedTemplate) - fastest, no lookup needed
229
+ * - Level 2: Global cache (globalAstCache) - fast, shared across all templates
230
+ * - Level 3: TemplateEngine - slow, parses and processes the template
231
+ *
232
+ * Since all parameters are now placeholders (no primitives), templates with the same
233
+ * structure always parse to the same AST regardless of parameter values.
234
+ *
235
+ * @returns The cached or newly computed template tree
236
+ * @internal
237
+ */
238
+ async getTemplateTree(): Promise<JS.CompilationUnit> {
239
+ // Level 1: Instance cache (fastest path)
240
+ if (this._cachedTemplate) {
241
+ return this._cachedTemplate as JS.CompilationUnit;
242
+ }
243
+
244
+ // Generate cache key for global lookup
245
+ // Since all parameters use placeholders, we only need the template structure
246
+ const contextStatements = this.options.context || this.options.imports || [];
247
+ const parametersKey = this.parameters.length.toString(); // Just the count
248
+ const cacheKey = generateCacheKey(
249
+ this.templateParts,
250
+ parametersKey,
251
+ contextStatements,
252
+ this.options.dependencies || {}
253
+ );
254
+
255
+ // Level 2: Global cache (fast path - shared with Pattern)
256
+ const cached = globalAstCache.get(cacheKey);
257
+ if (cached) {
258
+ this._cachedTemplate = cached as JS.CompilationUnit;
259
+ return cached as JS.CompilationUnit;
260
+ }
261
+
262
+ // Level 3: Compute via TemplateEngine (slow path)
263
+ const result = await TemplateEngine.getTemplateTree(
264
+ this.templateParts,
265
+ this.parameters,
266
+ contextStatements,
267
+ this.options.dependencies || {}
268
+ ) as JS.CompilationUnit;
269
+
270
+ // Cache in both levels
271
+ globalAstCache.set(cacheKey, result);
272
+ this._cachedTemplate = result;
273
+
274
+ return result;
275
+ }
276
+
222
277
  /**
223
278
  * Applies this template and returns the resulting tree.
224
279
  *
@@ -263,10 +318,12 @@ export class Template {
263
318
  }
264
319
  }
265
320
 
266
- // Prefer 'context' over deprecated 'imports'
267
- const contextStatements = this.options.context || this.options.imports || [];
268
- return TemplateEngine.applyTemplate(
269
- this.templateParts,
321
+ // Use instance-level cache to get the template tree
322
+ const ast = await this.getTemplateTree();
323
+
324
+ // Delegate to TemplateEngine for placeholder substitution and application
325
+ return TemplateEngine.applyTemplateFromAst(
326
+ ast,
270
327
  this.parameters,
271
328
  cursor,
272
329
  {
@@ -274,9 +331,7 @@ export class Template {
274
331
  mode: JavaCoordinates.Mode.Replace
275
332
  },
276
333
  normalizedValues,
277
- wrappersMap,
278
- contextStatements,
279
- this.options.dependencies || {}
334
+ wrappersMap
280
335
  );
281
336
  }
282
337
  }
@@ -290,7 +345,7 @@ export class Template {
290
345
  * access array elements (e.g., `args.elements[0].element`).
291
346
  *
292
347
  * @param strings The string parts of the template
293
- * @param parameters The parameters between the string parts (Capture, Tree, or primitives)
348
+ * @param parameters The parameters between the string parts (Capture, CaptureValue, TemplateParam, Tree, or Tree[])
294
349
  * @returns A Template object that can be applied to generate AST nodes
295
350
  *
296
351
  * @example
@@ -15,8 +15,9 @@
15
15
  */
16
16
  import {Cursor, Tree} from '../..';
17
17
  import {J, Type} from '../../java';
18
- import type {Pattern, MatchResult} from "./pattern";
18
+ import type {MatchResult, Pattern} from "./pattern";
19
19
  import type {Template} from "./template";
20
+ import type {CaptureValue} from "./capture";
20
21
 
21
22
  /**
22
23
  * Options for variadic captures that match zero or more nodes in a sequence.
@@ -100,14 +101,14 @@ export interface CaptureOptions<T = any> {
100
101
  * but does NOT enforce any runtime constraints on what the capture will match.
101
102
  *
102
103
  * **Pattern Matching Behavior:**
103
- * - A bare `pattern`${capture('x')}`` will structurally match ANY expression
104
- * - Pattern structure determines matching: `pattern`foo(${capture('x')})`` only matches `foo()` calls
104
+ * - A bare `pattern`${capture()}`` will structurally match ANY expression
105
+ * - Pattern structure determines matching: `pattern`foo(${capture()})`` only matches `foo()` calls with one arg
105
106
  * - Use structural patterns to narrow matching scope before applying semantic validation
106
107
  *
107
108
  * **Variadic Captures:**
108
109
  * Use `{ variadic: true }` to match zero or more nodes in a sequence:
109
110
  * ```typescript
110
- * const args = capture('args', { variadic: true });
111
+ * const args = capture({ variadic: true });
111
112
  * pattern`foo(${args})` // Matches: foo(), foo(a), foo(a, b, c)
112
113
  * ```
113
114
  */
@@ -168,8 +169,9 @@ export interface Capture<T = any> {
168
169
  *
169
170
  * @example
170
171
  * // Variadic any - match zero or more without capturing
172
+ * const first = any();
171
173
  * const rest = any({ variadic: true });
172
- * const pat = pattern`bar(${capture('first')}, ${rest})`
174
+ * const pat = pattern`bar(${first}, ${rest})`
173
175
  *
174
176
  * @example
175
177
  * // With constraints - validate but don't capture
@@ -273,11 +275,30 @@ export interface PatternOptions {
273
275
  * Valid parameter types for template literals.
274
276
  * - Capture: For pattern matching and reuse
275
277
  * - CaptureValue: Result of property access or array operations on captures (e.g., capture.prop, capture[0], capture.slice(1))
278
+ * - TemplateParam: For standalone template parameters
276
279
  * - Tree: AST nodes to be inserted directly
277
280
  * - Tree[]: Arrays of AST nodes (from variadic capture operations like slice)
278
- * - Primitives: Values to be converted to literals
281
+ *
282
+ * Note: Primitive values (string, number, boolean) are NOT supported in template literals.
283
+ * Use Template.builder() API if you need to insert literal values.
279
284
  */
280
- export type TemplateParameter = Capture | any | TemplateParam | Tree | Tree[] | string | number | boolean;
285
+ export type TemplateParameter = Capture | CaptureValue | TemplateParam | Tree | Tree[];
286
+
287
+ /**
288
+ * Parameter specification for template generation (internal).
289
+ * Represents a placeholder in a template that will be replaced with a parameter value.
290
+ * This is the internal wrapper used by the template engine.
291
+ *
292
+ * Note: The value is typed as `any` rather than `TemplateParameter` to allow flexible
293
+ * internal handling without excessive type guards. The public API (template function)
294
+ * constrains inputs to `TemplateParameter`, providing type safety at the API boundary.
295
+ */
296
+ export interface Parameter {
297
+ /**
298
+ * The value to substitute into the template.
299
+ */
300
+ value: any;
301
+ }
281
302
 
282
303
  /**
283
304
  * Configuration options for templates.
@@ -347,18 +368,21 @@ export interface RewriteRule {
347
368
  *
348
369
  * @example
349
370
  * ```typescript
350
- * const rule1 = rewrite(() => ({
351
- * before: pattern`${capture('a')} + ${capture('b')}`,
352
- * after: template`${capture('b')} + ${capture('a')}`
353
- * }));
371
+ * const rule1 = rewrite(() => {
372
+ * const { a, b } = { a: capture(), b: capture() };
373
+ * return {
374
+ * before: pattern`${a} + ${b}`,
375
+ * after: template`${b} + ${a}`
376
+ * };
377
+ * });
354
378
  *
355
379
  * const rule2 = rewrite(() => ({
356
380
  * before: pattern`${capture('x')} + 1`,
357
- * after: template`${capture('x')} + 2`
381
+ * after: template`${capture('x')}++`
358
382
  * }));
359
383
  *
360
384
  * const combined = rule1.andThen(rule2);
361
- * // Will first swap operands, then if result matches "x + 1", change to "x + 2"
385
+ * // Will first swap operands, then if result matches "x + 1", change to "x++"
362
386
  * ```
363
387
  */
364
388
  andThen(next: RewriteRule): RewriteRule;