@openrewrite/rewrite 8.66.0 → 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 (75) hide show
  1. package/dist/javascript/comparator.d.ts +67 -4
  2. package/dist/javascript/comparator.d.ts.map +1 -1
  3. package/dist/javascript/comparator.js +523 -2794
  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 +18 -16
  14. package/dist/javascript/parser.js.map +1 -1
  15. package/dist/javascript/templating/capture.d.ts +226 -0
  16. package/dist/javascript/templating/capture.d.ts.map +1 -0
  17. package/dist/javascript/templating/capture.js +371 -0
  18. package/dist/javascript/templating/capture.js.map +1 -0
  19. package/dist/javascript/templating/comparator.d.ts +61 -0
  20. package/dist/javascript/templating/comparator.d.ts.map +1 -0
  21. package/dist/javascript/templating/comparator.js +393 -0
  22. package/dist/javascript/templating/comparator.js.map +1 -0
  23. package/dist/javascript/templating/engine.d.ts +75 -0
  24. package/dist/javascript/templating/engine.d.ts.map +1 -0
  25. package/dist/javascript/templating/engine.js +228 -0
  26. package/dist/javascript/templating/engine.js.map +1 -0
  27. package/dist/javascript/templating/index.d.ts +6 -0
  28. package/dist/javascript/templating/index.d.ts.map +1 -0
  29. package/dist/javascript/templating/index.js +42 -0
  30. package/dist/javascript/templating/index.js.map +1 -0
  31. package/dist/javascript/templating/pattern.d.ts +171 -0
  32. package/dist/javascript/templating/pattern.d.ts.map +1 -0
  33. package/dist/javascript/templating/pattern.js +681 -0
  34. package/dist/javascript/templating/pattern.js.map +1 -0
  35. package/dist/javascript/templating/placeholder-replacement.d.ts +58 -0
  36. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -0
  37. package/dist/javascript/templating/placeholder-replacement.js +365 -0
  38. package/dist/javascript/templating/placeholder-replacement.js.map +1 -0
  39. package/dist/javascript/templating/rewrite.d.ts +39 -0
  40. package/dist/javascript/templating/rewrite.d.ts.map +1 -0
  41. package/dist/javascript/templating/rewrite.js +81 -0
  42. package/dist/javascript/templating/rewrite.js.map +1 -0
  43. package/dist/javascript/templating/template.d.ts +204 -0
  44. package/dist/javascript/templating/template.d.ts.map +1 -0
  45. package/dist/javascript/templating/template.js +293 -0
  46. package/dist/javascript/templating/template.js.map +1 -0
  47. package/dist/javascript/templating/types.d.ts +263 -0
  48. package/dist/javascript/templating/types.d.ts.map +1 -0
  49. package/dist/javascript/templating/types.js +3 -0
  50. package/dist/javascript/templating/types.js.map +1 -0
  51. package/dist/javascript/templating/utils.d.ts +118 -0
  52. package/dist/javascript/templating/utils.d.ts.map +1 -0
  53. package/dist/javascript/templating/utils.js +253 -0
  54. package/dist/javascript/templating/utils.js.map +1 -0
  55. package/dist/version.txt +1 -1
  56. package/package.json +2 -1
  57. package/src/javascript/comparator.ts +554 -3323
  58. package/src/javascript/format.ts +3 -2
  59. package/src/javascript/index.ts +1 -1
  60. package/src/javascript/parser.ts +19 -17
  61. package/src/javascript/templating/capture.ts +503 -0
  62. package/src/javascript/templating/comparator.ts +430 -0
  63. package/src/javascript/templating/engine.ts +252 -0
  64. package/src/javascript/templating/index.ts +60 -0
  65. package/src/javascript/templating/pattern.ts +727 -0
  66. package/src/javascript/templating/placeholder-replacement.ts +372 -0
  67. package/src/javascript/templating/rewrite.ts +95 -0
  68. package/src/javascript/templating/template.ts +326 -0
  69. package/src/javascript/templating/types.ts +300 -0
  70. package/src/javascript/templating/utils.ts +284 -0
  71. package/dist/javascript/templating.d.ts +0 -265
  72. package/dist/javascript/templating.d.ts.map +0 -1
  73. package/dist/javascript/templating.js +0 -1027
  74. package/dist/javascript/templating.js.map +0 -1
  75. package/src/javascript/templating.ts +0 -1226
@@ -826,7 +826,7 @@ export class MinimumViableSpacingVisitor<P> extends JavaScriptVisitor<P> {
826
826
  return produce(ret, draft => {
827
827
  if (draft.class) {
828
828
  if (draft.class.kind == JS.Kind.TypeTreeExpression) {
829
- this.ensureSpace((draft.class as Draft<JS.TypeTreeExpression>).expression.prefix);
829
+ this.ensureSpace((draft.class as Draft<JS.TypeTreeExpression>).prefix);
830
830
  }
831
831
  }
832
832
  });
@@ -941,7 +941,8 @@ export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
941
941
  tree = produce(tree as JS.StatementExpression, draft => {
942
942
  this.ensurePrefixHasNewLine(draft);
943
943
  });
944
- } else if (tree.kind === J.Kind.MethodDeclaration && this.cursor.value.kind != JS.Kind.StatementExpression) {
944
+ } else if (tree.kind === J.Kind.MethodDeclaration && this.cursor.value.kind != JS.Kind.StatementExpression
945
+ && (this.cursor.parent?.value.kind != JS.Kind.CompilationUnit || (this.cursor.parent?.value as JS.CompilationUnit).statements[0].element !== tree)) {
945
946
  tree = produce(tree as J.MethodDeclaration, draft => {
946
947
  this.ensurePrefixHasNewLine(draft);
947
948
  });
@@ -20,7 +20,7 @@ export * from "./parser";
20
20
  export * from "./style";
21
21
  export * from "./markers";
22
22
  export * from "./preconditions";
23
- export * from "./templating";
23
+ export * from "./templating/index";
24
24
  export * from "./method-matcher";
25
25
 
26
26
  export * from "./add-import";
@@ -612,13 +612,12 @@ export class JavaScriptParserVisitor {
612
612
  }
613
613
  for (let heritageClause of node.heritageClauses) {
614
614
  if (heritageClause.token == ts.SyntaxKind.ExtendsKeyword) {
615
- const expression = this.visit(heritageClause.types[0]);
616
615
  return this.leftPadded<TypeTree>(this.prefix(heritageClause.getFirstToken()!), {
617
616
  kind: JS.Kind.TypeTreeExpression,
618
617
  id: randomId(),
619
- prefix: emptySpace,
618
+ prefix: this.prefix(heritageClause.types[0]),
620
619
  markers: emptyMarkers,
621
- expression: expression
620
+ expression: this.visit(heritageClause.types[0])
622
621
  } satisfies JS.TypeTreeExpression as JS.TypeTreeExpression);
623
622
  }
624
623
  }
@@ -1493,7 +1492,7 @@ export class JavaScriptParserVisitor {
1493
1492
  element: {
1494
1493
  kind: J.Kind.Ternary,
1495
1494
  id: randomId(),
1496
- prefix: emptySpace,
1495
+ prefix: this.prefix(node.extendsType),
1497
1496
  markers: emptyMarkers,
1498
1497
  condition: this.convert(node.extendsType),
1499
1498
  truePart: this.leftPadded(this.suffix(node.extendsType), this.convert(node.trueType)),
@@ -1998,7 +1997,7 @@ export class JavaScriptParserVisitor {
1998
1997
  class: node.typeArguments ? {
1999
1998
  kind: J.Kind.ParameterizedType,
2000
1999
  id: randomId(),
2001
- prefix: emptySpace,
2000
+ prefix: this.prefix(node.expression),
2002
2001
  markers: emptyMarkers,
2003
2002
  class: {
2004
2003
  kind: JS.Kind.TypeTreeExpression,
@@ -2012,7 +2011,7 @@ export class JavaScriptParserVisitor {
2012
2011
  } satisfies J.ParameterizedType as J.ParameterizedType : {
2013
2012
  kind: JS.Kind.TypeTreeExpression,
2014
2013
  id: randomId(),
2015
- prefix: emptySpace,
2014
+ prefix: this.prefix(node.expression),
2016
2015
  markers: emptyMarkers,
2017
2016
  expression: this.visit(node.expression),
2018
2017
  } satisfies JS.TypeTreeExpression as JS.TypeTreeExpression,
@@ -2460,12 +2459,12 @@ export class JavaScriptParserVisitor {
2460
2459
  return {
2461
2460
  kind: JS.Kind.StatementExpression,
2462
2461
  id: randomId(),
2463
- prefix: emptySpace,
2462
+ prefix: this.prefix(node),
2464
2463
  markers: emptyMarkers,
2465
2464
  statement: {
2466
2465
  kind: J.Kind.Yield,
2467
2466
  id: randomId(),
2468
- prefix: this.prefix(node),
2467
+ prefix: emptySpace,
2469
2468
  markers: node.asteriskToken ?
2470
2469
  markers({
2471
2470
  kind: JS.Markers.DelegatedYield,
@@ -2492,12 +2491,12 @@ export class JavaScriptParserVisitor {
2492
2491
  return {
2493
2492
  kind: JS.Kind.StatementExpression,
2494
2493
  id: randomId(),
2495
- prefix: emptySpace,
2494
+ prefix: this.prefix(node),
2496
2495
  markers: emptyMarkers,
2497
2496
  statement: {
2498
2497
  kind: J.Kind.ClassDeclaration,
2499
2498
  id: randomId(),
2500
- prefix: this.prefix(node),
2499
+ prefix: emptySpace,
2501
2500
  markers: emptyMarkers,
2502
2501
  leadingAnnotations: this.mapDecorators(node),
2503
2502
  modifiers: [],
@@ -2645,16 +2644,19 @@ export class JavaScriptParserVisitor {
2645
2644
  }
2646
2645
 
2647
2646
  visitExpressionStatement(node: ts.ExpressionStatement): Statement {
2648
- const expression = this.visit(node.expression) as Expression;
2647
+ const expression: Expression = this.visit(node.expression) as Expression;
2649
2648
  if (isStatement(expression)) {
2650
2649
  return expression as Statement;
2651
2650
  }
2651
+ const e: Expression = expression;
2652
2652
  return {
2653
2653
  kind: JS.Kind.ExpressionStatement,
2654
2654
  id: randomId(),
2655
- prefix: emptySpace,
2655
+ prefix: e.prefix,
2656
2656
  markers: emptyMarkers,
2657
- expression: expression
2657
+ expression: produce(e, draft => {
2658
+ draft.prefix = emptySpace
2659
+ })
2658
2660
  } satisfies JS.ExpressionStatement as JS.ExpressionStatement;
2659
2661
  }
2660
2662
 
@@ -2767,7 +2769,7 @@ export class JavaScriptParserVisitor {
2767
2769
  update: [node.incrementor ? this.rightPadded(ts.isStatement(node.incrementor) ? this.visit(node.incrementor) : {
2768
2770
  kind: JS.Kind.ExpressionStatement,
2769
2771
  id: randomId(),
2770
- prefix: emptySpace,
2772
+ prefix: this.prefix(node.incrementor),
2771
2773
  markers: emptyMarkers,
2772
2774
  expression: this.visit(node.incrementor)
2773
2775
  }, this.suffix(node.incrementor)) :
@@ -3262,7 +3264,7 @@ export class JavaScriptParserVisitor {
3262
3264
  {
3263
3265
  kind: J.Kind.EnumValueSet,
3264
3266
  id: randomId(),
3265
- prefix: emptySpace,
3267
+ prefix: node.members[0] ? this.prefix(node.members[0]) : emptySpace,
3266
3268
  markers: emptyMarkers,
3267
3269
  enums: node.members.map(em => this.rightPadded(this.visit(em), this.suffix(em))),
3268
3270
  terminatedWithSemicolon: node.members.hasTrailingComma
@@ -3322,7 +3324,7 @@ export class JavaScriptParserVisitor {
3322
3324
  return {
3323
3325
  kind: JS.Kind.NamespaceDeclaration,
3324
3326
  id: randomId(),
3325
- prefix: node.parent.kind === ts.SyntaxKind.ModuleBlock ? this.prefix(node) : emptySpace,
3327
+ prefix: this.prefix(node),
3326
3328
  markers: emptyMarkers,
3327
3329
  modifiers: this.mapModifiers(node),
3328
3330
  keywordType: this.leftPadded(
@@ -3456,7 +3458,7 @@ export class JavaScriptParserVisitor {
3456
3458
  const typeKeyword = node.getChildren().find(n => n.kind === ts.SyntaxKind.TypeKeyword);
3457
3459
  return this.prefix(typeKeyword!);
3458
3460
  } else {
3459
- return emptySpace;
3461
+ return this.prefix(node.name);
3460
3462
  }
3461
3463
  })(),
3462
3464
  markers: emptyMarkers,
@@ -0,0 +1,503 @@
1
+ /*
2
+ * Copyright 2025 the original author or authors.
3
+ * <p>
4
+ * Licensed under the Moderne Source Available License (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ * <p>
8
+ * https://docs.moderne.io/licensing/moderne-source-available-license
9
+ * <p>
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import {J} from '../../java';
17
+ import {Capture, Any, TemplateParam, CaptureOptions, VariadicOptions} from './types';
18
+
19
+ /**
20
+ * Combines multiple constraints with AND logic.
21
+ * All constraints must return true for the combined constraint to pass.
22
+ *
23
+ * @example
24
+ * const largeEvenNumber = capture('n', {
25
+ * constraint: and(
26
+ * (node) => typeof node.value === 'number',
27
+ * (node) => node.value > 100,
28
+ * (node) => node.value % 2 === 0
29
+ * )
30
+ * });
31
+ */
32
+ export function and<T>(...constraints: ((node: T) => boolean)[]): (node: T) => boolean {
33
+ return (node: T) => constraints.every(c => c(node));
34
+ }
35
+
36
+ /**
37
+ * Combines multiple constraints with OR logic.
38
+ * At least one constraint must return true for the combined constraint to pass.
39
+ *
40
+ * @example
41
+ * const stringOrNumber = capture('value', {
42
+ * constraint: or(
43
+ * (node) => node.kind === J.Kind.Literal && typeof node.value === 'string',
44
+ * (node) => node.kind === J.Kind.Literal && typeof node.value === 'number'
45
+ * )
46
+ * });
47
+ */
48
+ export function or<T>(...constraints: ((node: T) => boolean)[]): (node: T) => boolean {
49
+ return (node: T) => constraints.some(c => c(node));
50
+ }
51
+
52
+ /**
53
+ * Negates a constraint.
54
+ * Returns true when the constraint returns false, and vice versa.
55
+ *
56
+ * @example
57
+ * const notString = capture('value', {
58
+ * constraint: not((node) => typeof node.value === 'string')
59
+ * });
60
+ */
61
+ export function not<T>(constraint: (node: T) => boolean): (node: T) => boolean {
62
+ return (node: T) => !constraint(node);
63
+ }
64
+
65
+ // Symbol to access the internal capture name without triggering Proxy
66
+ export const CAPTURE_NAME_SYMBOL = Symbol('captureName');
67
+ // Symbol to access variadic options without triggering Proxy
68
+ export const CAPTURE_VARIADIC_SYMBOL = Symbol('captureVariadic');
69
+ // Symbol to access constraint function without triggering Proxy
70
+ export const CAPTURE_CONSTRAINT_SYMBOL = Symbol('captureConstraint');
71
+ // Symbol to access capturing flag without triggering Proxy
72
+ export const CAPTURE_CAPTURING_SYMBOL = Symbol('captureCapturing');
73
+
74
+ export class CaptureImpl<T = any> implements Capture<T> {
75
+ public readonly name: string;
76
+ [CAPTURE_NAME_SYMBOL]: string;
77
+ [CAPTURE_VARIADIC_SYMBOL]: VariadicOptions | undefined;
78
+ [CAPTURE_CONSTRAINT_SYMBOL]: ((node: T) => boolean) | undefined;
79
+ [CAPTURE_CAPTURING_SYMBOL]: boolean;
80
+
81
+ constructor(name: string, options?: CaptureOptions<T>, capturing: boolean = true) {
82
+ this.name = name;
83
+ this[CAPTURE_NAME_SYMBOL] = name;
84
+ this[CAPTURE_CAPTURING_SYMBOL] = capturing;
85
+
86
+ // Normalize variadic options
87
+ if (options?.variadic) {
88
+ if (typeof options.variadic === 'boolean') {
89
+ this[CAPTURE_VARIADIC_SYMBOL] = {};
90
+ } else {
91
+ this[CAPTURE_VARIADIC_SYMBOL] = {
92
+ min: options.variadic.min,
93
+ max: options.variadic.max
94
+ };
95
+ }
96
+ }
97
+
98
+ // Store constraint if provided
99
+ if (options?.constraint) {
100
+ this[CAPTURE_CONSTRAINT_SYMBOL] = options.constraint;
101
+ }
102
+ }
103
+
104
+ getName(): string {
105
+ return this[CAPTURE_NAME_SYMBOL];
106
+ }
107
+
108
+ isVariadic(): boolean {
109
+ return this[CAPTURE_VARIADIC_SYMBOL] !== undefined;
110
+ }
111
+
112
+ getVariadicOptions(): VariadicOptions | undefined {
113
+ return this[CAPTURE_VARIADIC_SYMBOL];
114
+ }
115
+
116
+ getConstraint(): ((node: T) => boolean) | undefined {
117
+ return this[CAPTURE_CONSTRAINT_SYMBOL];
118
+ }
119
+
120
+ isCapturing(): boolean {
121
+ return this[CAPTURE_CAPTURING_SYMBOL];
122
+ }
123
+ }
124
+
125
+ export class TemplateParamImpl<T = any> implements TemplateParam<T> {
126
+ public readonly name: string;
127
+
128
+ constructor(name: string) {
129
+ this.name = name;
130
+ }
131
+
132
+ getName(): string {
133
+ return this.name;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Represents a property access on a captured value.
139
+ * When you access a property on a Capture (e.g., method.name), you get a CaptureValue
140
+ * that knows how to resolve that property from the matched values.
141
+ */
142
+ export class CaptureValue {
143
+ constructor(
144
+ public readonly rootCapture: Capture,
145
+ public readonly propertyPath: string[],
146
+ public readonly arrayOperation?: { type: 'index' | 'slice' | 'length'; args?: number[] }
147
+ ) {}
148
+
149
+ /**
150
+ * Resolves this capture value by looking up the root capture in the values map
151
+ * and navigating through the property path.
152
+ */
153
+ resolve(values: Pick<Map<string, J | J[]>, 'get'>): any {
154
+ const rootName = this.rootCapture.getName();
155
+ let current: any = values.get(rootName);
156
+
157
+ // Handle array operations on variadic captures
158
+ if (this.arrayOperation && Array.isArray(current)) {
159
+ switch (this.arrayOperation.type) {
160
+ case 'index':
161
+ current = current[this.arrayOperation.args![0]];
162
+ break;
163
+ case 'slice':
164
+ current = current.slice(...this.arrayOperation.args!);
165
+ break;
166
+ case 'length':
167
+ current = current.length;
168
+ break;
169
+ }
170
+ }
171
+
172
+ // Navigate through property path
173
+ for (const prop of this.propertyPath) {
174
+ if (current === null || current === undefined || typeof current === 'number') {
175
+ return undefined;
176
+ }
177
+ current = current[prop];
178
+ }
179
+
180
+ return current;
181
+ }
182
+
183
+ /**
184
+ * Checks if this CaptureValue will resolve to an array that should be expanded.
185
+ */
186
+ isArrayExpansion(): boolean {
187
+ // Only slice operations and the root variadic capture return arrays
188
+ return this.arrayOperation?.type === 'slice' ||
189
+ (this.arrayOperation === undefined && this.propertyPath.length === 0 &&
190
+ (this.rootCapture as any).isVariadic?.());
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Creates a Proxy around a CaptureValue that allows further property access.
196
+ * This enables chaining like method.name.simpleName
197
+ */
198
+ function createCaptureValueProxy(
199
+ rootCapture: Capture,
200
+ propertyPath: string[],
201
+ arrayOperation?: { type: 'index' | 'slice' | 'length'; args?: number[] }
202
+ ): any {
203
+ const captureValue = new CaptureValue(rootCapture, propertyPath, arrayOperation);
204
+
205
+ return new Proxy(captureValue, {
206
+ get(target: CaptureValue, prop: string | symbol): any {
207
+ // Allow access to the CaptureValue instance itself and its methods
208
+ if (prop === 'resolve' || prop === 'rootCapture' || prop === 'propertyPath' ||
209
+ prop === 'arrayOperation' || prop === 'isArrayExpansion') {
210
+ const value = target[prop as keyof CaptureValue];
211
+ return typeof value === 'function' ? value.bind(target) : value;
212
+ }
213
+
214
+ // For string property access, extend the property path
215
+ if (typeof prop === 'string') {
216
+ return createCaptureValueProxy(target.rootCapture, [...target.propertyPath, prop], target.arrayOperation);
217
+ }
218
+
219
+ return undefined;
220
+ }
221
+ });
222
+ }
223
+
224
+ /**
225
+ * Creates a capture specification for use in template patterns.
226
+ *
227
+ * @template T The expected type of the captured AST node (for TypeScript autocomplete only)
228
+ * @returns A Capture object that supports property access for use in templates
229
+ *
230
+ * @remarks
231
+ * **Pattern Matching is Structural:**
232
+ *
233
+ * What a capture matches is determined by the pattern structure, not the type parameter:
234
+ * - `pattern`${capture('x')}`` matches ANY single expression (identifier, literal, call, binary, etc.)
235
+ * - `pattern`foo(${capture('x')})`` matches only expressions inside `foo()` calls
236
+ * - `pattern`${capture('x')} + ${capture('y')}`` matches only binary `+` expressions
237
+ *
238
+ * The TypeScript type parameter `<T>` provides IDE autocomplete but doesn't enforce runtime types.
239
+ *
240
+ * @example
241
+ * // Named inline captures
242
+ * const pat = pattern`${capture('left')} + ${capture('right')}`;
243
+ * // Matches: <any expression> + <any expression>
244
+ *
245
+ * // Unnamed captures
246
+ * const {left, right} = {left: capture(), right: capture()};
247
+ * const pattern = pattern`${left} + ${right}`;
248
+ *
249
+ * @example
250
+ * // Type parameter is for IDE autocomplete only
251
+ * const method = capture<J.MethodInvocation>('method');
252
+ * const pat = pattern`foo(${method})`;
253
+ * // Matches: foo(<any expression>) - not restricted to method invocations!
254
+ * // Type <J.MethodInvocation> only helps with autocomplete in your code
255
+ *
256
+ * @example
257
+ * // Structural pattern determines what matches
258
+ * const arg = capture('arg');
259
+ * const pat = pattern`process(${arg})`;
260
+ * // Matches: process(42), process("text"), process(x + y), etc.
261
+ * // The 'arg' capture will bind to whatever expression is passed to process()
262
+ *
263
+ * @example
264
+ * // Repeated patterns using the same capture
265
+ * const expr = capture('expr');
266
+ * const redundantOr = pattern`${expr} || ${expr}`;
267
+ * // Matches expressions where both sides are identical: x || x, foo() || foo()
268
+ *
269
+ * @example
270
+ * // Property access in templates
271
+ * const method = capture<J.MethodInvocation>('method');
272
+ * template`console.log(${method.name.simpleName})` // Accesses properties of captured node
273
+ */
274
+ /**
275
+ * Creates a Proxy wrapper for CaptureImpl that intercepts property accesses.
276
+ * Shared logic between capture() and any() to avoid duplication.
277
+ */
278
+ function createCaptureProxy<T>(impl: CaptureImpl<T>): any {
279
+ return new Proxy(impl as any, {
280
+ get(target: any, prop: string | symbol): any {
281
+ // Allow access to internal symbols
282
+ if (prop === CAPTURE_NAME_SYMBOL) {
283
+ return target[CAPTURE_NAME_SYMBOL];
284
+ }
285
+ if (prop === CAPTURE_VARIADIC_SYMBOL) {
286
+ return target[CAPTURE_VARIADIC_SYMBOL];
287
+ }
288
+ if (prop === CAPTURE_CONSTRAINT_SYMBOL) {
289
+ return target[CAPTURE_CONSTRAINT_SYMBOL];
290
+ }
291
+ if (prop === CAPTURE_CAPTURING_SYMBOL) {
292
+ return target[CAPTURE_CAPTURING_SYMBOL];
293
+ }
294
+
295
+ // Allow methods to be called directly on the target
296
+ if (prop === 'getName' || prop === 'isVariadic' || prop === 'getVariadicOptions' || prop === 'getConstraint' || prop === 'isCapturing') {
297
+ return target[prop].bind(target);
298
+ }
299
+
300
+ // For variadic captures, support array-like operations
301
+ if (target.isVariadic() && typeof prop === 'string') {
302
+ // Numeric index access: args[0], args[1], etc.
303
+ const indexNum = Number(prop);
304
+ if (!isNaN(indexNum) && indexNum >= 0 && Number.isInteger(indexNum)) {
305
+ return createCaptureValueProxy(target, [], { type: 'index', args: [indexNum] });
306
+ }
307
+
308
+ // Array method: slice
309
+ if (prop === 'slice') {
310
+ return (...args: number[]) => {
311
+ return createCaptureValueProxy(target, [], { type: 'slice', args });
312
+ };
313
+ }
314
+
315
+ // Array property: length
316
+ if (prop === 'length') {
317
+ return createCaptureValueProxy(target, [], { type: 'length' });
318
+ }
319
+ }
320
+
321
+ // For string property access, create a CaptureValue with a property path
322
+ if (typeof prop === 'string') {
323
+ return createCaptureValueProxy(target, [prop]);
324
+ }
325
+
326
+ return undefined;
327
+ }
328
+ });
329
+ }
330
+
331
+ // Overload 1: Options object with constraint (no variadic)
332
+ export function capture<T = any>(
333
+ options: { name?: string; constraint: (node: T) => boolean } & { variadic?: never }
334
+ ): Capture<T> & T;
335
+
336
+ // Overload 2: Options object with variadic
337
+ export function capture<T = any>(
338
+ options: { name?: string; variadic: true | VariadicOptions; constraint?: (nodes: T[]) => boolean; min?: number; max?: number }
339
+ ): Capture<T[]> & T[];
340
+
341
+ // Overload 3: Just a string name (simple named capture)
342
+ export function capture<T = any>(name?: string): Capture<T> & T;
343
+
344
+ // Implementation
345
+ export function capture<T = any>(nameOrOptions?: string | CaptureOptions<T>): Capture<T> & T {
346
+ let name: string | undefined;
347
+ let options: CaptureOptions<T> | undefined;
348
+
349
+ if (typeof nameOrOptions === 'string') {
350
+ // Simple named capture: capture('name')
351
+ name = nameOrOptions;
352
+ options = undefined;
353
+ } else {
354
+ // Options-based API: capture({ name: 'name', ...options }) or capture()
355
+ options = nameOrOptions;
356
+ name = options?.name;
357
+ }
358
+
359
+ const captureName = name || `unnamed_${capture.nextUnnamedId++}`;
360
+ const impl = new CaptureImpl<T>(captureName, options);
361
+
362
+ return createCaptureProxy(impl);
363
+ }
364
+
365
+ // Static counter for generating unique IDs for unnamed captures
366
+ capture.nextUnnamedId = 1;
367
+
368
+ /**
369
+ * Creates a non-capturing pattern match for use in patterns.
370
+ *
371
+ * Use `any()` when you need to match AST structure without binding the matched value to a name.
372
+ * This is useful for validation patterns where you care about structure but not the specific values.
373
+ *
374
+ * **Key Differences from `capture()`:**
375
+ * - `any()` returns `Any<T>` type (not `Capture<T>`)
376
+ * - Cannot be used in templates (TypeScript compiler prevents this)
377
+ * - Does not bind matched values (more memory efficient for patterns)
378
+ * - Supports same features: constraints, variadic matching
379
+ *
380
+ * @template T The expected type of the matched AST node (for TypeScript autocomplete and constraints)
381
+ * @param options Optional configuration (variadic, constraint)
382
+ * @returns An Any object that matches patterns without capturing
383
+ *
384
+ * @example
385
+ * // Match any single argument without capturing
386
+ * const pat = pattern`foo(${any()})`;
387
+ *
388
+ * @example
389
+ * // Match with constraint validation
390
+ * const numericArg = any<J.Literal>({
391
+ * constraint: (node) => typeof node.value === 'number'
392
+ * });
393
+ * const pat = pattern`process(${numericArg})`;
394
+ *
395
+ * @example
396
+ * // Variadic any - match zero or more without capturing
397
+ * const rest = any({ variadic: true });
398
+ * const first = capture('first');
399
+ * const pat = pattern`foo(${first}, ${rest})`;
400
+ *
401
+ * @example
402
+ * // Mixed with captures - capture some, ignore others
403
+ * const important = capture('important');
404
+ * const pat = pattern`
405
+ * if (${any()}) {
406
+ * ${important}
407
+ * }
408
+ * `;
409
+ *
410
+ * @example
411
+ * // Variadic with constraints
412
+ * const numericArgs = any<J.Literal>({
413
+ * variadic: true,
414
+ * constraint: (nodes) => nodes.every(n => typeof n.value === 'number')
415
+ * });
416
+ * const pat = pattern`sum(${numericArgs})`;
417
+ */
418
+ // Overload 1: Regular any with constraint (most specific - no variadic property)
419
+ export function any<T = any>(
420
+ options: { constraint: (node: T) => boolean } & { variadic?: never }
421
+ ): Any<T> & T;
422
+
423
+ // Overload 2: Variadic any with constraint
424
+ export function any<T = any>(
425
+ options: { variadic: true | VariadicOptions; constraint?: (nodes: T[]) => boolean; min?: number; max?: number }
426
+ ): Any<T[]> & T[];
427
+
428
+ // Overload 3: Catch-all for simple any without special options
429
+ export function any<T = any>(
430
+ options?: CaptureOptions<T>
431
+ ): Any<T> & T;
432
+
433
+ // Implementation
434
+ export function any<T = any>(options?: CaptureOptions<T>): Any<T> & T {
435
+ const anonName = `anon_${any.nextAnonId++}`;
436
+ const impl = new CaptureImpl<T>(anonName, options, false); // capturing = false
437
+
438
+ return createCaptureProxy(impl);
439
+ }
440
+
441
+ // Static counter for generating unique IDs for anonymous (non-capturing) patterns
442
+ any.nextAnonId = 1;
443
+
444
+ /**
445
+ * Creates a parameter specification for use in standalone templates (not used with patterns).
446
+ *
447
+ * Use `param()` when creating templates that are not used with pattern matching.
448
+ * Use `capture()` when the template works with a pattern.
449
+ *
450
+ * @template T The expected type of the parameter value (for TypeScript autocomplete only)
451
+ * @param name Optional name for the parameter. If not provided, an auto-generated name is used.
452
+ * @returns A TemplateParam object (simpler than Capture, no property access support)
453
+ *
454
+ * @remarks
455
+ * **When to use `param()` vs `capture()`:**
456
+ *
457
+ * - Use `param()` in **standalone templates** (no pattern matching involved)
458
+ * - Use `capture()` in **patterns** and templates used with patterns
459
+ *
460
+ * **Key Differences:**
461
+ * - `TemplateParam` is simpler - no property access proxy overhead
462
+ * - `Capture` supports property access (e.g., `capture('x').name.simpleName`)
463
+ * - Both work in templates, but `param()` makes intent clearer for standalone use
464
+ *
465
+ * @example
466
+ * // ✅ GOOD: Use param() for standalone templates
467
+ * const value = param<J.Literal>('value');
468
+ * const tmpl = template`return ${value} * 2;`;
469
+ * await tmpl.apply(cursor, node, new Map([['value', someLiteral]]));
470
+ *
471
+ * @example
472
+ * // ✅ GOOD: Use capture() with patterns
473
+ * const value = capture('value');
474
+ * const pat = pattern`foo(${value})`;
475
+ * const tmpl = template`bar(${value})`; // capture() makes sense here
476
+ *
477
+ * @example
478
+ * // ⚠️ CONFUSING: Using capture() in standalone template
479
+ * const value = capture('value');
480
+ * template`return ${value} * 2;`; // "Capturing" what? There's no pattern!
481
+ *
482
+ * @example
483
+ * // ❌ WRONG: param() doesn't support property access
484
+ * const node = param<J.MethodInvocation>('invocation');
485
+ * template`console.log(${node.name})` // Error! Use capture() for property access
486
+ */
487
+ export function param<T = any>(name?: string): TemplateParam<T> {
488
+ const paramName = name || `unnamed_${capture.nextUnnamedId++}`;
489
+ return new TemplateParamImpl<T>(paramName);
490
+ }
491
+
492
+ /**
493
+ * Concise alias for `capture`. Works well for inline captures in patterns and templates.
494
+ *
495
+ * @param name Optional name for the capture. If not provided, an auto-generated name is used.
496
+ * @returns A Capture object
497
+ *
498
+ * @example
499
+ * // Inline captures with _ alias
500
+ * pattern`isDate(${_('dateArg')})`
501
+ * template`${_('dateArg')} instanceof Date`
502
+ */
503
+ export const _ = capture;