@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.
- package/dist/javascript/comparator.d.ts +67 -4
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +523 -2794
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format.d.ts.map +1 -1
- package/dist/javascript/format.js +4 -3
- package/dist/javascript/format.js.map +1 -1
- package/dist/javascript/index.d.ts +1 -1
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +1 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +18 -16
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/templating/capture.d.ts +226 -0
- package/dist/javascript/templating/capture.d.ts.map +1 -0
- package/dist/javascript/templating/capture.js +371 -0
- package/dist/javascript/templating/capture.js.map +1 -0
- package/dist/javascript/templating/comparator.d.ts +61 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -0
- package/dist/javascript/templating/comparator.js +393 -0
- package/dist/javascript/templating/comparator.js.map +1 -0
- package/dist/javascript/templating/engine.d.ts +75 -0
- package/dist/javascript/templating/engine.d.ts.map +1 -0
- package/dist/javascript/templating/engine.js +228 -0
- package/dist/javascript/templating/engine.js.map +1 -0
- package/dist/javascript/templating/index.d.ts +6 -0
- package/dist/javascript/templating/index.d.ts.map +1 -0
- package/dist/javascript/templating/index.js +42 -0
- package/dist/javascript/templating/index.js.map +1 -0
- package/dist/javascript/templating/pattern.d.ts +171 -0
- package/dist/javascript/templating/pattern.d.ts.map +1 -0
- package/dist/javascript/templating/pattern.js +681 -0
- package/dist/javascript/templating/pattern.js.map +1 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts +58 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -0
- package/dist/javascript/templating/placeholder-replacement.js +365 -0
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -0
- package/dist/javascript/templating/rewrite.d.ts +39 -0
- package/dist/javascript/templating/rewrite.d.ts.map +1 -0
- package/dist/javascript/templating/rewrite.js +81 -0
- package/dist/javascript/templating/rewrite.js.map +1 -0
- package/dist/javascript/templating/template.d.ts +204 -0
- package/dist/javascript/templating/template.d.ts.map +1 -0
- package/dist/javascript/templating/template.js +293 -0
- package/dist/javascript/templating/template.js.map +1 -0
- package/dist/javascript/templating/types.d.ts +263 -0
- package/dist/javascript/templating/types.d.ts.map +1 -0
- package/dist/javascript/templating/types.js +3 -0
- package/dist/javascript/templating/types.js.map +1 -0
- package/dist/javascript/templating/utils.d.ts +118 -0
- package/dist/javascript/templating/utils.d.ts.map +1 -0
- package/dist/javascript/templating/utils.js +253 -0
- package/dist/javascript/templating/utils.js.map +1 -0
- package/dist/version.txt +1 -1
- package/package.json +2 -1
- package/src/javascript/comparator.ts +554 -3323
- package/src/javascript/format.ts +3 -2
- package/src/javascript/index.ts +1 -1
- package/src/javascript/parser.ts +19 -17
- package/src/javascript/templating/capture.ts +503 -0
- package/src/javascript/templating/comparator.ts +430 -0
- package/src/javascript/templating/engine.ts +252 -0
- package/src/javascript/templating/index.ts +60 -0
- package/src/javascript/templating/pattern.ts +727 -0
- package/src/javascript/templating/placeholder-replacement.ts +372 -0
- package/src/javascript/templating/rewrite.ts +95 -0
- package/src/javascript/templating/template.ts +326 -0
- package/src/javascript/templating/types.ts +300 -0
- package/src/javascript/templating/utils.ts +284 -0
- package/dist/javascript/templating.d.ts +0 -265
- package/dist/javascript/templating.d.ts.map +0 -1
- package/dist/javascript/templating.js +0 -1027
- package/dist/javascript/templating.js.map +0 -1
- package/src/javascript/templating.ts +0 -1226
package/src/javascript/format.ts
CHANGED
|
@@ -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>).
|
|
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
|
});
|
package/src/javascript/index.ts
CHANGED
|
@@ -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";
|
package/src/javascript/parser.ts
CHANGED
|
@@ -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:
|
|
618
|
+
prefix: this.prefix(heritageClause.types[0]),
|
|
620
619
|
markers: emptyMarkers,
|
|
621
|
-
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:
|
|
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:
|
|
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:
|
|
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:
|
|
2462
|
+
prefix: this.prefix(node),
|
|
2464
2463
|
markers: emptyMarkers,
|
|
2465
2464
|
statement: {
|
|
2466
2465
|
kind: J.Kind.Yield,
|
|
2467
2466
|
id: randomId(),
|
|
2468
|
-
prefix:
|
|
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:
|
|
2494
|
+
prefix: this.prefix(node),
|
|
2496
2495
|
markers: emptyMarkers,
|
|
2497
2496
|
statement: {
|
|
2498
2497
|
kind: J.Kind.ClassDeclaration,
|
|
2499
2498
|
id: randomId(),
|
|
2500
|
-
prefix:
|
|
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:
|
|
2655
|
+
prefix: e.prefix,
|
|
2656
2656
|
markers: emptyMarkers,
|
|
2657
|
-
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:
|
|
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:
|
|
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
|
|
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;
|