@openrewrite/rewrite 8.67.0-20251106-140126 → 8.67.0-20251107-071946

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 (39) 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 +34 -8
  10. package/dist/javascript/templating/engine.d.ts.map +1 -1
  11. package/dist/javascript/templating/engine.js +317 -60
  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/template.d.ts +9 -2
  20. package/dist/javascript/templating/template.d.ts.map +1 -1
  21. package/dist/javascript/templating/template.js +37 -0
  22. package/dist/javascript/templating/template.js.map +1 -1
  23. package/dist/javascript/templating/types.d.ts +26 -11
  24. package/dist/javascript/templating/types.d.ts.map +1 -1
  25. package/dist/javascript/templating/utils.d.ts +41 -22
  26. package/dist/javascript/templating/utils.d.ts.map +1 -1
  27. package/dist/javascript/templating/utils.js +111 -76
  28. package/dist/javascript/templating/utils.js.map +1 -1
  29. package/dist/version.txt +1 -1
  30. package/package.json +3 -1
  31. package/src/java/tree.ts +2 -0
  32. package/src/javascript/assertions.ts +1 -1
  33. package/src/javascript/format.ts +1 -1
  34. package/src/javascript/templating/engine.ts +376 -54
  35. package/src/javascript/templating/pattern.ts +55 -322
  36. package/src/javascript/templating/placeholder-replacement.ts +1 -1
  37. package/src/javascript/templating/template.ts +57 -3
  38. package/src/javascript/templating/types.ts +27 -11
  39. package/src/javascript/templating/utils.ts +113 -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.
@@ -15,11 +15,11 @@
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
23
 
24
24
  /**
25
25
  * Coordinates for template application.
@@ -171,6 +171,7 @@ export class TemplateBuilder {
171
171
  */
172
172
  export class Template {
173
173
  private options: TemplateOptions = {};
174
+ private _cachedTemplate?: J;
174
175
 
175
176
  /**
176
177
  * Creates a new builder for constructing templates programmatically.
@@ -216,9 +217,55 @@ export class Template {
216
217
  */
217
218
  configure(options: TemplateOptions): Template {
218
219
  this.options = { ...this.options, ...options };
220
+ // Invalidate cache when configuration changes
221
+ this._cachedTemplate = undefined;
219
222
  return this;
220
223
  }
221
224
 
225
+ /**
226
+ * Gets the cached template tree or computes it.
227
+ * Uses two-level caching: instance cache → global cache → compute.
228
+ *
229
+ * @returns A Promise resolving to the template AST tree
230
+ */
231
+ private async getTemplate(): Promise<J> {
232
+ // Level 1: Instance cache (fastest path)
233
+ if (this._cachedTemplate) {
234
+ return this._cachedTemplate;
235
+ }
236
+
237
+ // Generate cache key for global lookup
238
+ const contextStatements = this.options.context || this.options.imports || [];
239
+ const paramNames = this.parameters.map((p, i) => `param${i}`).join(',');
240
+ const cacheKey = generateCacheKey(
241
+ this.templateParts,
242
+ paramNames,
243
+ contextStatements,
244
+ this.options.dependencies || {}
245
+ );
246
+
247
+ // Level 2: Global cache (fast path - shared with Pattern)
248
+ const cached = globalAstCache.get(cacheKey);
249
+ if (cached) {
250
+ this._cachedTemplate = cached;
251
+ return cached;
252
+ }
253
+
254
+ // Level 3: Compute via TemplateEngine (slow path)
255
+ const result = await TemplateEngine.getTemplateTree(
256
+ this.templateParts,
257
+ this.parameters,
258
+ contextStatements,
259
+ this.options.dependencies || {}
260
+ );
261
+
262
+ // Cache in both levels
263
+ globalAstCache.set(cacheKey, result);
264
+ this._cachedTemplate = result;
265
+
266
+ return result;
267
+ }
268
+
222
269
  /**
223
270
  * Applies this template and returns the resulting tree.
224
271
  *
@@ -263,8 +310,15 @@ export class Template {
263
310
  }
264
311
  }
265
312
 
313
+ // Get the cached template tree (uses two-level caching)
314
+ const templateTree = await this.getTemplate();
315
+
266
316
  // Prefer 'context' over deprecated 'imports'
267
317
  const contextStatements = this.options.context || this.options.imports || [];
318
+
319
+ // Apply template with value substitution using TemplateEngine
320
+ // Note: TemplateEngine.applyTemplate will call getTemplateTree again,
321
+ // but that's okay because it hits the templateCache which is fast
268
322
  return TemplateEngine.applyTemplate(
269
323
  this.templateParts,
270
324
  this.parameters,
@@ -15,7 +15,7 @@
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
20
 
21
21
  /**
@@ -100,14 +100,14 @@ export interface CaptureOptions<T = any> {
100
100
  * but does NOT enforce any runtime constraints on what the capture will match.
101
101
  *
102
102
  * **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
103
+ * - A bare `pattern`${capture()}`` will structurally match ANY expression
104
+ * - Pattern structure determines matching: `pattern`foo(${capture()})`` only matches `foo()` calls with one arg
105
105
  * - Use structural patterns to narrow matching scope before applying semantic validation
106
106
  *
107
107
  * **Variadic Captures:**
108
108
  * Use `{ variadic: true }` to match zero or more nodes in a sequence:
109
109
  * ```typescript
110
- * const args = capture('args', { variadic: true });
110
+ * const args = capture({ variadic: true });
111
111
  * pattern`foo(${args})` // Matches: foo(), foo(a), foo(a, b, c)
112
112
  * ```
113
113
  */
@@ -168,8 +168,9 @@ export interface Capture<T = any> {
168
168
  *
169
169
  * @example
170
170
  * // Variadic any - match zero or more without capturing
171
+ * const first = any();
171
172
  * const rest = any({ variadic: true });
172
- * const pat = pattern`bar(${capture('first')}, ${rest})`
173
+ * const pat = pattern`bar(${first}, ${rest})`
173
174
  *
174
175
  * @example
175
176
  * // With constraints - validate but don't capture
@@ -279,6 +280,18 @@ export interface PatternOptions {
279
280
  */
280
281
  export type TemplateParameter = Capture | any | TemplateParam | Tree | Tree[] | string | number | boolean;
281
282
 
283
+ /**
284
+ * Parameter specification for template generation (internal).
285
+ * Represents a placeholder in a template that will be replaced with a parameter value.
286
+ * This is the internal wrapper used by the template engine.
287
+ */
288
+ export interface Parameter {
289
+ /**
290
+ * The value to substitute into the template.
291
+ */
292
+ value: any;
293
+ }
294
+
282
295
  /**
283
296
  * Configuration options for templates.
284
297
  */
@@ -347,18 +360,21 @@ export interface RewriteRule {
347
360
  *
348
361
  * @example
349
362
  * ```typescript
350
- * const rule1 = rewrite(() => ({
351
- * before: pattern`${capture('a')} + ${capture('b')}`,
352
- * after: template`${capture('b')} + ${capture('a')}`
353
- * }));
363
+ * const rule1 = rewrite(() => {
364
+ * const { a, b } = { a: capture(), b: capture() };
365
+ * return {
366
+ * before: pattern`${a} + ${b}`,
367
+ * after: template`${b} + ${a}`
368
+ * };
369
+ * });
354
370
  *
355
371
  * const rule2 = rewrite(() => ({
356
372
  * before: pattern`${capture('x')} + 1`,
357
- * after: template`${capture('x')} + 2`
373
+ * after: template`${capture('x')}++`
358
374
  * }));
359
375
  *
360
376
  * const combined = rule1.andThen(rule2);
361
- * // Will first swap operands, then if result matches "x + 1", change to "x + 2"
377
+ * // Will first swap operands, then if result matches "x + 1", change to "x++"
362
378
  * ```
363
379
  */
364
380
  andThen(next: RewriteRule): RewriteRule;