@openrewrite/rewrite 8.66.1 → 8.66.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/java/tree.d.ts +10 -1
  2. package/dist/java/tree.d.ts.map +1 -1
  3. package/dist/java/tree.js +21 -5
  4. package/dist/java/tree.js.map +1 -1
  5. package/dist/java/type-visitor.d.ts +1 -1
  6. package/dist/java/type-visitor.d.ts.map +1 -1
  7. package/dist/java/visitor.d.ts +2 -2
  8. package/dist/java/visitor.d.ts.map +1 -1
  9. package/dist/java/visitor.js +8 -2
  10. package/dist/java/visitor.js.map +1 -1
  11. package/dist/javascript/assertions.d.ts +6 -0
  12. package/dist/javascript/assertions.d.ts.map +1 -1
  13. package/dist/javascript/assertions.js +14 -6
  14. package/dist/javascript/assertions.js.map +1 -1
  15. package/dist/javascript/comparator.d.ts +154 -7
  16. package/dist/javascript/comparator.d.ts.map +1 -1
  17. package/dist/javascript/comparator.js +623 -180
  18. package/dist/javascript/comparator.js.map +1 -1
  19. package/dist/javascript/format.d.ts +5 -3
  20. package/dist/javascript/format.d.ts.map +1 -1
  21. package/dist/javascript/format.js +85 -43
  22. package/dist/javascript/format.js.map +1 -1
  23. package/dist/javascript/index.d.ts +1 -0
  24. package/dist/javascript/index.d.ts.map +1 -1
  25. package/dist/javascript/index.js +1 -0
  26. package/dist/javascript/index.js.map +1 -1
  27. package/dist/javascript/parser.d.ts +2 -1
  28. package/dist/javascript/parser.d.ts.map +1 -1
  29. package/dist/javascript/parser.js +39 -30
  30. package/dist/javascript/parser.js.map +1 -1
  31. package/dist/javascript/templating/capture.d.ts +81 -14
  32. package/dist/javascript/templating/capture.d.ts.map +1 -1
  33. package/dist/javascript/templating/capture.js +98 -8
  34. package/dist/javascript/templating/capture.js.map +1 -1
  35. package/dist/javascript/templating/comparator.d.ts +125 -15
  36. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  37. package/dist/javascript/templating/comparator.js +946 -118
  38. package/dist/javascript/templating/comparator.js.map +1 -1
  39. package/dist/javascript/templating/engine.d.ts +58 -25
  40. package/dist/javascript/templating/engine.d.ts.map +1 -1
  41. package/dist/javascript/templating/engine.js +527 -94
  42. package/dist/javascript/templating/engine.js.map +1 -1
  43. package/dist/javascript/templating/index.d.ts +3 -3
  44. package/dist/javascript/templating/index.d.ts.map +1 -1
  45. package/dist/javascript/templating/index.js +3 -1
  46. package/dist/javascript/templating/index.js.map +1 -1
  47. package/dist/javascript/templating/pattern.d.ts +121 -16
  48. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  49. package/dist/javascript/templating/pattern.js +528 -257
  50. package/dist/javascript/templating/pattern.js.map +1 -1
  51. package/dist/javascript/templating/placeholder-replacement.d.ts +30 -5
  52. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  53. package/dist/javascript/templating/placeholder-replacement.js +183 -81
  54. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  55. package/dist/javascript/templating/rewrite.d.ts +56 -11
  56. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  57. package/dist/javascript/templating/rewrite.js +143 -16
  58. package/dist/javascript/templating/rewrite.js.map +1 -1
  59. package/dist/javascript/templating/template.d.ts +31 -5
  60. package/dist/javascript/templating/template.d.ts.map +1 -1
  61. package/dist/javascript/templating/template.js +89 -15
  62. package/dist/javascript/templating/template.js.map +1 -1
  63. package/dist/javascript/templating/types.d.ts +359 -12
  64. package/dist/javascript/templating/types.d.ts.map +1 -1
  65. package/dist/javascript/templating/utils.d.ts +52 -35
  66. package/dist/javascript/templating/utils.d.ts.map +1 -1
  67. package/dist/javascript/templating/utils.js +107 -109
  68. package/dist/javascript/templating/utils.js.map +1 -1
  69. package/dist/javascript/type-mapping.d.ts.map +1 -1
  70. package/dist/javascript/type-mapping.js +21 -11
  71. package/dist/javascript/type-mapping.js.map +1 -1
  72. package/dist/json/rpc.js +2 -2
  73. package/dist/json/rpc.js.map +1 -1
  74. package/dist/recipe/order-imports.js.map +1 -1
  75. package/dist/test/rewrite-test.d.ts.map +1 -1
  76. package/dist/test/rewrite-test.js +10 -6
  77. package/dist/test/rewrite-test.js.map +1 -1
  78. package/dist/version.txt +1 -1
  79. package/dist/visitor.d.ts +4 -4
  80. package/dist/visitor.d.ts.map +1 -1
  81. package/dist/visitor.js +8 -3
  82. package/dist/visitor.js.map +1 -1
  83. package/package.json +4 -2
  84. package/src/java/tree.ts +10 -3
  85. package/src/java/type-visitor.ts +1 -1
  86. package/src/java/visitor.ts +11 -5
  87. package/src/javascript/assertions.ts +9 -3
  88. package/src/javascript/comparator.ts +676 -185
  89. package/src/javascript/format.ts +72 -34
  90. package/src/javascript/index.ts +1 -0
  91. package/src/javascript/parser.ts +51 -31
  92. package/src/javascript/templating/capture.ts +107 -15
  93. package/src/javascript/templating/comparator.ts +1087 -134
  94. package/src/javascript/templating/engine.ts +601 -103
  95. package/src/javascript/templating/index.ts +9 -2
  96. package/src/javascript/templating/pattern.ts +655 -281
  97. package/src/javascript/templating/placeholder-replacement.ts +183 -80
  98. package/src/javascript/templating/rewrite.ts +152 -18
  99. package/src/javascript/templating/template.ts +110 -22
  100. package/src/javascript/templating/types.ts +386 -12
  101. package/src/javascript/templating/utils.ts +116 -102
  102. package/src/javascript/type-mapping.ts +20 -11
  103. package/src/json/rpc.ts +2 -2
  104. package/src/recipe/order-imports.ts +1 -1
  105. package/src/test/rewrite-test.ts +12 -7
  106. package/src/visitor.ts +14 -6
@@ -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';
21
- import {CAPTURE_NAME_SYMBOL} from './capture';
22
- import {TemplateEngine, Parameter} from './engine';
20
+ import {generateCacheKey, globalAstCache, WRAPPERS_MAP_SYMBOL} from './utils';
21
+ import {CAPTURE_NAME_SYMBOL, RAW_CODE_SYMBOL} from './capture';
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.
@@ -210,15 +212,77 @@ export class Template {
210
212
  * @example
211
213
  * template`isDate(${capture('date')})`
212
214
  * .configure({
213
- * imports: ['import { isDate } from "util"'],
215
+ * context: ['import { isDate } from "util"'],
214
216
  * dependencies: { 'util': '^1.0.0' }
215
217
  * })
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
+ * Most parameters use placeholders that are replaced during application, so templates
233
+ * with the same structure share cached ASTs. However, raw() parameters are spliced at
234
+ * construction time, so their values must be included in the cache key.
235
+ *
236
+ * @returns The cached or newly computed template tree
237
+ * @internal
238
+ */
239
+ async getTemplateTree(): Promise<JS.CompilationUnit> {
240
+ // Level 1: Instance cache (fastest path)
241
+ if (this._cachedTemplate) {
242
+ return this._cachedTemplate as JS.CompilationUnit;
243
+ }
244
+
245
+ // Generate cache key for global lookup
246
+ // For raw() parameters, we need to include their code values in the key
247
+ // since they're spliced at construction time, not application time
248
+ const contextStatements = this.options.context || this.options.imports || [];
249
+ const parametersKey = this.parameters.map((p, i) => {
250
+ const value = p.value;
251
+ // Include raw code values in the cache key using the symbol
252
+ if (value && typeof value === 'object' && value[RAW_CODE_SYMBOL]) {
253
+ return `raw:${value.code}`;
254
+ }
255
+ return i.toString();
256
+ }).join(',');
257
+ const cacheKey = generateCacheKey(
258
+ this.templateParts,
259
+ parametersKey,
260
+ contextStatements,
261
+ this.options.dependencies || {}
262
+ );
263
+
264
+ // Level 2: Global cache (fast path - shared with Pattern)
265
+ const cached = globalAstCache.get(cacheKey);
266
+ if (cached) {
267
+ this._cachedTemplate = cached as JS.CompilationUnit;
268
+ return cached as JS.CompilationUnit;
269
+ }
270
+
271
+ // Level 3: Compute via TemplateEngine (slow path)
272
+ const result = await TemplateEngine.getTemplateTree(
273
+ this.templateParts,
274
+ this.parameters,
275
+ contextStatements,
276
+ this.options.dependencies || {}
277
+ ) as JS.CompilationUnit;
278
+
279
+ // Cache in both levels
280
+ globalAstCache.set(cacheKey, result);
281
+ this._cachedTemplate = result;
282
+
283
+ return result;
284
+ }
285
+
222
286
  /**
223
287
  * Applies this template and returns the resulting tree.
224
288
  *
@@ -227,12 +291,16 @@ export class Template {
227
291
  * @param values values for parameters in template
228
292
  * @returns A Promise resolving to the generated AST node
229
293
  */
230
- async apply(cursor: Cursor, tree: J, values?: Map<Capture | string, J> | Pick<Map<string, J>, 'get'>): Promise<J | undefined> {
294
+ async apply(cursor: Cursor, tree: J, values?: Map<Capture | string, J> | Pick<Map<string, J>, 'get'> | Record<string, J>): Promise<J | undefined> {
231
295
  // Normalize the values map: convert any Capture keys to string keys
232
296
  let normalizedValues: Pick<Map<string, J>, 'get'> | undefined;
233
297
  let wrappersMap: Map<string, J.RightPadded<J> | J.RightPadded<J>[]> = new Map();
234
298
 
235
- if (values instanceof Map) {
299
+ if (values instanceof MatchResult) {
300
+ // MatchResult - extract both bindings and wrappersMap
301
+ normalizedValues = values;
302
+ wrappersMap = (values as any)[WRAPPERS_MAP_SYMBOL]();
303
+ } else if (values instanceof Map) {
236
304
  const normalized = new Map<string, J>();
237
305
  for (const [key, value] of values.entries()) {
238
306
  const stringKey = typeof key === 'string'
@@ -241,19 +309,30 @@ export class Template {
241
309
  normalized.set(stringKey, value);
242
310
  }
243
311
  normalizedValues = normalized;
244
- } else if (values instanceof MatchResult) {
245
- // MatchResult - extract both bindings and wrappersMap
246
- normalizedValues = values;
247
- wrappersMap = (values as any)[WRAPPERS_MAP_SYMBOL]();
248
- } else {
249
- // Other Pick<Map> implementation
250
- normalizedValues = values;
312
+ } else if (values && typeof values === 'object') {
313
+ // Check if it's a Map-like object with 'get' method, or a plain object literal
314
+ if ('get' in values && typeof values.get === 'function') {
315
+ // Map-like object with get method
316
+ normalizedValues = values as Pick<Map<string, J>, 'get'>;
317
+ } else {
318
+ // Plain object literal - convert to Map
319
+ // Keys may be strings or Capture objects (via computed properties {[x]: value})
320
+ const normalized = new Map<string, J>();
321
+ for (const [key, value] of Object.entries(values)) {
322
+ // If the key happens to be a stringified Capture (from computed properties),
323
+ // it's already been converted to a string by JavaScript
324
+ normalized.set(key, value);
325
+ }
326
+ normalizedValues = normalized;
327
+ }
251
328
  }
252
329
 
253
- // Prefer 'context' over deprecated 'imports'
254
- const contextStatements = this.options.context || this.options.imports || [];
255
- return TemplateEngine.applyTemplate(
256
- this.templateParts,
330
+ // Use instance-level cache to get the template tree
331
+ const ast = await this.getTemplateTree();
332
+
333
+ // Delegate to TemplateEngine for placeholder substitution and application
334
+ return TemplateEngine.applyTemplateFromAst(
335
+ ast,
257
336
  this.parameters,
258
337
  cursor,
259
338
  {
@@ -261,9 +340,7 @@ export class Template {
261
340
  mode: JavaCoordinates.Mode.Replace
262
341
  },
263
342
  normalizedValues,
264
- wrappersMap,
265
- contextStatements,
266
- this.options.dependencies || {}
343
+ wrappersMap
267
344
  );
268
345
  }
269
346
  }
@@ -276,8 +353,13 @@ export class Template {
276
353
  * to navigate properties (e.g., `method.name`) or array bracket notation to
277
354
  * access array elements (e.g., `args.elements[0].element`).
278
355
  *
356
+ * Templates can also accept AST wrapper types directly:
357
+ * - J.RightPadded<T>: The element will be extracted and inserted
358
+ * - J.RightPadded<T>[]: Elements will be expanded in place
359
+ * - J.Container<T>: Elements will be expanded in place
360
+ *
279
361
  * @param strings The string parts of the template
280
- * @param parameters The parameters between the string parts (Capture, Tree, or primitives)
362
+ * @param parameters The parameters between the string parts (Capture, CaptureValue, TemplateParam, Tree, Tree[], J.RightPadded, J.RightPadded[], or J.Container)
281
363
  * @returns A Template object that can be applied to generate AST nodes
282
364
  *
283
365
  * @example
@@ -312,6 +394,12 @@ export class Template {
312
394
  * // Array element access
313
395
  * const invocation = capture<J.MethodInvocation>('invocation');
314
396
  * template`bar(${invocation.arguments.elements[0].element})`
397
+ *
398
+ * @example
399
+ * // Using J.RightPadded and J.Container directly
400
+ * const selectExpr = method.select; // J.RightPadded<Expression>
401
+ * const args = method.arguments; // J.Container<Expression>
402
+ * template`${selectExpr}.newMethod(${args})`
315
403
  */
316
404
  export function template(strings: TemplateStringsArray, ...parameters: TemplateParameter[]): Template {
317
405
  // Convert parameters to Parameter objects (no longer need to check for mutable tree property)
@@ -14,7 +14,10 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import {Cursor, Tree} from '../..';
17
- import {J} from '../../java';
17
+ import {J, Type} from '../../java';
18
+ import type {Pattern} from "./pattern";
19
+ import type {Template} from "./template";
20
+ import type {CaptureValue, RawCode} from "./capture";
18
21
 
19
22
  /**
20
23
  * Options for variadic captures that match zero or more nodes in a sequence.
@@ -31,6 +34,19 @@ export interface VariadicOptions {
31
34
  max?: number;
32
35
  }
33
36
 
37
+ /**
38
+ * Constraint function for captures.
39
+ * The cursor parameter is always provided with a defined value, but functions can
40
+ * choose to accept it or not (TypeScript allows functions with fewer parameters).
41
+ *
42
+ * For non-variadic captures: use ConstraintFunction<T> where T is the node type
43
+ * For variadic captures: use ConstraintFunction<T[]> where T[] is the array type
44
+ *
45
+ * When used with variadic captures, the cursor points to the nearest common parent
46
+ * of the captured elements.
47
+ */
48
+ export type ConstraintFunction<T> = (node: T, cursor: Cursor) => boolean;
49
+
34
50
  /**
35
51
  * Options for the capture function.
36
52
  *
@@ -38,11 +54,53 @@ export interface VariadicOptions {
38
54
  * the capture is variadic:
39
55
  * - For regular captures: constraint receives a single node of type T
40
56
  * - For variadic captures: constraint receives an array of nodes of type T[]
57
+ *
58
+ * The constraint function can optionally receive a cursor parameter to perform
59
+ * context-aware validation during pattern matching.
41
60
  */
42
61
  export interface CaptureOptions<T = any> {
43
62
  name?: string;
44
63
  variadic?: boolean | VariadicOptions;
45
- constraint?: (node: T) => boolean;
64
+ /**
65
+ * Optional constraint function that validates whether a captured node should be accepted.
66
+ * The function always receives:
67
+ * - node: The captured node (or array of nodes for variadic captures)
68
+ * - cursor: A cursor at the captured node's position (always defined)
69
+ *
70
+ * Functions can choose to accept just the node parameter if they don't need the cursor.
71
+ *
72
+ * @param node The captured node to validate
73
+ * @param cursor Cursor at the captured node's position
74
+ * @returns true if the capture should be accepted, false otherwise
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * // Simple node validation (cursor parameter ignored)
79
+ * capture<J.Literal>('size', {
80
+ * constraint: (node) => typeof node.value === 'number' && node.value > 100
81
+ * })
82
+ *
83
+ * // Context-aware validation (using cursor)
84
+ * capture<J.MethodInvocation>('method', {
85
+ * constraint: (node, cursor) => {
86
+ * if (!node.name.simpleName.startsWith('get')) return false;
87
+ * const cls = cursor.firstEnclosing(isClassDeclaration);
88
+ * return cls?.name.simpleName === 'ApiController';
89
+ * }
90
+ * })
91
+ * ```
92
+ */
93
+ constraint?: ConstraintFunction<T>;
94
+ /**
95
+ * Type annotation for this capture. When provided, the template engine will generate
96
+ * a preamble declaring the capture identifier with this type annotation, allowing
97
+ * the TypeScript parser/compiler to produce a properly type-attributed AST.
98
+ *
99
+ * Can be specified as:
100
+ * - A string type annotation (e.g., "boolean", "string", "number")
101
+ * - A Type instance from the AST (the type will be inferred from the Type)
102
+ */
103
+ type?: string | Type;
46
104
  }
47
105
 
48
106
  /**
@@ -58,14 +116,14 @@ export interface CaptureOptions<T = any> {
58
116
  * but does NOT enforce any runtime constraints on what the capture will match.
59
117
  *
60
118
  * **Pattern Matching Behavior:**
61
- * - A bare `pattern`${capture('x')}`` will structurally match ANY expression
62
- * - Pattern structure determines matching: `pattern`foo(${capture('x')})`` only matches `foo()` calls
119
+ * - A bare `pattern`${capture()}`` will structurally match ANY expression
120
+ * - Pattern structure determines matching: `pattern`foo(${capture()})`` only matches `foo()` calls with one arg
63
121
  * - Use structural patterns to narrow matching scope before applying semantic validation
64
122
  *
65
123
  * **Variadic Captures:**
66
124
  * Use `{ variadic: true }` to match zero or more nodes in a sequence:
67
125
  * ```typescript
68
- * const args = capture('args', { variadic: true });
126
+ * const args = capture({ variadic: true });
69
127
  * pattern`foo(${args})` // Matches: foo(), foo(a), foo(a, b, c)
70
128
  * ```
71
129
  */
@@ -89,8 +147,9 @@ export interface Capture<T = any> {
89
147
  * Gets the constraint function if this capture has one.
90
148
  * For regular captures (T = Expression), constraint receives a single node.
91
149
  * For variadic captures (T = Expression[]), constraint receives an array of nodes.
150
+ * The constraint function can optionally receive a cursor for context-aware validation.
92
151
  */
93
- getConstraint?(): ((node: T) => boolean) | undefined;
152
+ getConstraint?(): ConstraintFunction<T> | undefined;
94
153
  }
95
154
 
96
155
  /**
@@ -125,8 +184,9 @@ export interface Capture<T = any> {
125
184
  *
126
185
  * @example
127
186
  * // Variadic any - match zero or more without capturing
187
+ * const first = any();
128
188
  * const rest = any({ variadic: true });
129
- * const pat = pattern`bar(${capture('first')}, ${rest})`
189
+ * const pat = pattern`bar(${first}, ${rest})`
130
190
  *
131
191
  * @example
132
192
  * // With constraints - validate but don't capture
@@ -156,7 +216,7 @@ export interface Any<T = any> {
156
216
  * For regular any (T = Expression), constraint receives a single node.
157
217
  * For variadic any (T = Expression[]), constraint receives an array of nodes.
158
218
  */
159
- getConstraint?(): ((node: T) => boolean) | undefined;
219
+ getConstraint?(): ConstraintFunction<T> | undefined;
160
220
  }
161
221
 
162
222
  /**
@@ -224,17 +284,83 @@ export interface PatternOptions {
224
284
  * @default true (lenient matching enabled for backward compatibility)
225
285
  */
226
286
  lenientTypeMatching?: boolean;
287
+
288
+ /**
289
+ * Enable debug logging for this pattern.
290
+ * When enabled, all match attempts will log detailed information to stderr,
291
+ * including the AST path traversed, mismatches encountered, and captured values.
292
+ *
293
+ * Can be overridden at the match() call level.
294
+ * Global debug can be enabled via PATTERN_DEBUG=true environment variable.
295
+ *
296
+ * Precedence: match() call > pattern configure() > PATTERN_DEBUG env var
297
+ *
298
+ * @default undefined (inherits from environment or match() call)
299
+ *
300
+ * @example
301
+ * ```typescript
302
+ * // Pattern-level debug
303
+ * const pat = pattern({ debug: true })`console.log(${value})`;
304
+ *
305
+ * // Disable debug for a noisy pattern when global debug is on
306
+ * const noisyPat = pattern({ debug: false })`import ${x} from ${y}`;
307
+ * ```
308
+ */
309
+ debug?: boolean;
310
+ }
311
+
312
+ /**
313
+ * Options for individual match() calls.
314
+ */
315
+ export interface MatchOptions {
316
+ /**
317
+ * Enable debug logging for this specific match() call.
318
+ * Overrides pattern-level debug setting and global PATTERN_DEBUG env var.
319
+ *
320
+ * @example
321
+ * ```typescript
322
+ * // Debug just this call
323
+ * const match = await pattern.match(node, cursor, { debug: true });
324
+ *
325
+ * // Disable debug for this call even if pattern or global debug is on
326
+ * const match = await pattern.match(node, cursor, { debug: false });
327
+ * ```
328
+ */
329
+ debug?: boolean;
227
330
  }
228
331
 
229
332
  /**
230
333
  * Valid parameter types for template literals.
231
334
  * - Capture: For pattern matching and reuse
232
335
  * - CaptureValue: Result of property access or array operations on captures (e.g., capture.prop, capture[0], capture.slice(1))
336
+ * - TemplateParam: For standalone template parameters
337
+ * - RawCode: For inserting literal code strings at construction time
233
338
  * - Tree: AST nodes to be inserted directly
234
339
  * - Tree[]: Arrays of AST nodes (from variadic capture operations like slice)
235
- * - Primitives: Values to be converted to literals
340
+ * - J.RightPadded<any>: Wrapper containing an element with markers (element will be extracted)
341
+ * - J.RightPadded<any>[]: Array of wrappers (elements will be expanded)
342
+ * - J.Container<any>: Container with elements (elements will be expanded)
343
+ *
344
+ * Note: Primitive values (string, number, boolean) are NOT supported in template literals.
345
+ * Use raw() for inserting code strings, or Template.builder() API for programmatic construction.
346
+ */
347
+ export type TemplateParameter = Capture | CaptureValue | TemplateParam | RawCode | Tree | Tree[] | J.RightPadded<any> | J.RightPadded<any>[] | J.Container<any>;
348
+
349
+ /**
350
+ * Parameter specification for template generation (internal).
351
+ * Represents a placeholder in a template that will be replaced with a parameter value.
352
+ * This is the internal wrapper used by the template engine.
353
+ *
354
+ * Note: The value is typed as `any` rather than `TemplateParameter` to allow flexible
355
+ * internal handling without excessive type guards. The public API (template function)
356
+ * constrains inputs to `TemplateParameter`, providing type safety at the API boundary.
236
357
  */
237
- export type TemplateParameter = Capture | any | TemplateParam | Tree | Tree[] | string | number | boolean;
358
+ export interface Parameter {
359
+ /**
360
+ * The value to substitute into the template.
361
+ */
362
+ value: any;
363
+ }
238
364
 
239
365
  /**
240
366
  * Configuration options for templates.
@@ -289,12 +415,260 @@ export interface RewriteRule {
289
415
  * node when there's no match: `return await rule.tryOn(this.cursor, node) || node;`
290
416
  */
291
417
  tryOn(cursor: Cursor, node: J): Promise<J | undefined>;
418
+
419
+ /**
420
+ * Chains this rule with another rule, creating a composite rule that applies both transformations sequentially.
421
+ *
422
+ * The resulting rule:
423
+ * 1. First applies this rule to the input node
424
+ * 2. If this rule matches and transforms the node, applies the next rule to the result
425
+ * 3. If the next rule returns undefined (no match), keeps the result from the first rule
426
+ * 4. If this rule returns undefined (no match), returns undefined without trying the next rule
427
+ *
428
+ * @param next The rule to apply after this rule
429
+ * @returns A new RewriteRule that applies both rules in sequence
430
+ *
431
+ * @example
432
+ * ```typescript
433
+ * const rule1 = rewrite(() => {
434
+ * const { a, b } = { a: capture(), b: capture() };
435
+ * return {
436
+ * before: pattern`${a} + ${b}`,
437
+ * after: template`${b} + ${a}`
438
+ * };
439
+ * });
440
+ *
441
+ * const rule2 = rewrite(() => ({
442
+ * before: pattern`${capture('x')} + 1`,
443
+ * after: template`${capture('x')}++`
444
+ * }));
445
+ *
446
+ * const combined = rule1.andThen(rule2);
447
+ * // Will first swap operands, then if result matches "x + 1", change to "x++"
448
+ * ```
449
+ */
450
+ andThen(next: RewriteRule): RewriteRule;
451
+
452
+ /**
453
+ * Creates a composite rule that tries this rule first, and if it doesn't match, tries an alternative rule.
454
+ *
455
+ * The resulting rule:
456
+ * 1. First applies this rule to the input node
457
+ * 2. If this rule matches and transforms the node, returns the result
458
+ * 3. If this rule returns undefined (no match), tries the alternative rule on the original node
459
+ *
460
+ * @param alternative The rule to try if this rule doesn't match
461
+ * @returns A new RewriteRule that tries both rules with fallback behavior
462
+ *
463
+ * @example
464
+ * ```typescript
465
+ * // Try specific pattern first, fall back to general pattern
466
+ * const specific = rewrite(() => ({
467
+ * before: pattern`foo(${capture('x')}, 0)`,
468
+ * after: template`bar(${capture('x')})`
469
+ * }));
470
+ *
471
+ * const general = rewrite(() => ({
472
+ * before: pattern`foo(${capture('x')}, ${capture('y')})`,
473
+ * after: template`baz(${capture('x')}, ${capture('y')})`
474
+ * }));
475
+ *
476
+ * const combined = specific.orElse(general);
477
+ * // Will try specific pattern first, if no match, try general pattern
478
+ * ```
479
+ */
480
+ orElse(alternative: RewriteRule): RewriteRule;
292
481
  }
293
482
 
294
483
  /**
295
484
  * Configuration for a replacement rule.
296
485
  */
297
486
  export interface RewriteConfig {
298
- before: any; // Pattern | Pattern[], but we'll import Pattern in rewrite.ts
299
- after: any; // Template, but we'll import Template in rewrite.ts
487
+ before: Pattern | Pattern[];
488
+ after: Template | ((match: MatchResult) => Template);
489
+
490
+ /**
491
+ * Optional context predicate that must evaluate to true for the transformation to be applied.
492
+ * Evaluated after the pattern matches structurally but before applying the template.
493
+ * Provides access to both the matched node and the cursor for context inspection.
494
+ *
495
+ * @param node The matched AST node
496
+ * @param cursor The cursor at the matched node, providing access to ancestors and context
497
+ * @returns true if the transformation should be applied, false otherwise
498
+ *
499
+ * @example
500
+ * ```typescript
501
+ * rewrite(() => ({
502
+ * before: pattern`await ${_('promise')}`,
503
+ * after: template`await ${_('promise')}.catch(handleError)`,
504
+ * where: (node, cursor) => {
505
+ * // Only apply inside async functions
506
+ * const method = cursor.firstEnclosing((n: any): n is J.MethodDeclaration =>
507
+ * n.kind === J.Kind.MethodDeclaration
508
+ * );
509
+ * return method?.modifiers.some(m => m.type === 'async') || false;
510
+ * }
511
+ * }));
512
+ * ```
513
+ */
514
+ where?: (node: J, cursor: Cursor) => boolean | Promise<boolean>;
515
+
516
+ /**
517
+ * Optional context predicate that must evaluate to false for the transformation to be applied.
518
+ * Evaluated after the pattern matches structurally but before applying the template.
519
+ * Provides access to both the matched node and the cursor for context inspection.
520
+ *
521
+ * @param node The matched AST node
522
+ * @param cursor The cursor at the matched node, providing access to ancestors and context
523
+ * @returns true if the transformation should NOT be applied, false if it should proceed
524
+ *
525
+ * @example
526
+ * ```typescript
527
+ * rewrite(() => ({
528
+ * before: pattern`await ${_('promise')}`,
529
+ * after: template`await ${_('promise')}.catch(handleError)`,
530
+ * whereNot: (node, cursor) => {
531
+ * // Don't apply inside try-catch blocks
532
+ * return cursor.firstEnclosing((n: any): n is J.Try =>
533
+ * n.kind === J.Kind.Try
534
+ * ) !== undefined;
535
+ * }
536
+ * }));
537
+ * ```
538
+ */
539
+ whereNot?: (node: J, cursor: Cursor) => boolean | Promise<boolean>;
540
+ }
541
+
542
+ /**
543
+ * Options for debugging pattern matching.
544
+ * Used in Layer 1 (Core Instrumentation) to control debug output.
545
+ */
546
+ export interface DebugOptions {
547
+ /**
548
+ * Enable detailed logging during pattern matching.
549
+ */
550
+ enabled?: boolean;
551
+
552
+ /**
553
+ * Log structural comparison steps.
554
+ */
555
+ logComparison?: boolean;
556
+
557
+ /**
558
+ * Log constraint evaluation.
559
+ */
560
+ logConstraints?: boolean;
561
+ }
562
+
563
+ /**
564
+ * A single debug log entry collected during pattern matching.
565
+ * Part of Layer 1 (Core Instrumentation).
566
+ */
567
+ export interface DebugLogEntry {
568
+ /**
569
+ * Severity level of the log entry.
570
+ */
571
+ level: 'trace' | 'debug' | 'info' | 'warn';
572
+
573
+ /**
574
+ * The scope/category of the log entry.
575
+ */
576
+ scope: 'matching' | 'comparison' | 'constraint';
577
+
578
+ /**
579
+ * Path in the AST where this log entry was generated.
580
+ */
581
+ path: string[];
582
+
583
+ /**
584
+ * Human-readable message.
585
+ */
586
+ message: string;
587
+
588
+ /**
589
+ * Optional data associated with this log entry.
590
+ */
591
+ data?: any;
592
+ }
593
+
594
+ /**
595
+ * Detailed explanation of why a pattern failed to match.
596
+ * Built by Layer 1 (Core Instrumentation) and exposed by Layer 2 (API).
597
+ */
598
+ export interface MatchExplanation {
599
+ /**
600
+ * The reason for the match failure.
601
+ */
602
+ reason: 'structural-mismatch' | 'constraint-failed' | 'type-mismatch' | 'kind-mismatch' | 'value-mismatch' | 'array-length-mismatch';
603
+
604
+ /**
605
+ * Path in the AST where the failure occurred (e.g., ['select', 'name']).
606
+ */
607
+ path: string[];
608
+
609
+ /**
610
+ * Human-readable description of what was expected.
611
+ */
612
+ expected: string;
613
+
614
+ /**
615
+ * Human-readable description of what was actually found.
616
+ */
617
+ actual: string;
618
+
619
+ /**
620
+ * For constraint failures, details about which constraints failed.
621
+ */
622
+ constraintFailures?: Array<{
623
+ captureName: string;
624
+ actualValue: any;
625
+ error?: string;
626
+ }>;
627
+
628
+ /**
629
+ * Additional context about the failure.
630
+ */
631
+ details?: string;
632
+ }
633
+
634
+ /**
635
+ * Interface for accessing captured nodes from a successful pattern match.
636
+ * Part of the public API.
637
+ */
638
+ export interface MatchResult {
639
+ /**
640
+ * Get a captured node by name or by Capture object.
641
+ *
642
+ * @param capture The capture name (string) or Capture object
643
+ * @returns The captured node(s), or undefined if not found
644
+ */
645
+ get(capture: string): any;
646
+ get<T>(capture: Capture<T>): T | undefined;
647
+ }
648
+
649
+ /**
650
+ * Result of a pattern match attempt with debug information.
651
+ * Part of Layer 2 (Public API).
652
+ */
653
+ export interface MatchAttemptResult {
654
+ /**
655
+ * Whether the pattern matched successfully.
656
+ */
657
+ matched: boolean;
658
+
659
+ /**
660
+ * If matched, the match result with captured nodes. Undefined if not matched.
661
+ * Use `result.get('captureName')` or `result.get(captureObject)` to access captures.
662
+ */
663
+ result?: MatchResult;
664
+
665
+ /**
666
+ * If not matched, explanation of why. Undefined if matched.
667
+ */
668
+ explanation?: MatchExplanation;
669
+
670
+ /**
671
+ * Debug log entries collected during matching (if debug was enabled).
672
+ */
673
+ debugLog?: DebugLogEntry[];
300
674
  }