@openrewrite/rewrite 8.67.0-20251104-132125 → 8.67.0-20251104-160327
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/format.d.ts.map +1 -1
- package/dist/javascript/format.js +2 -1
- package/dist/javascript/format.js.map +1 -1
- package/dist/javascript/templating/capture.d.ts +6 -6
- package/dist/javascript/templating/capture.d.ts.map +1 -1
- package/dist/javascript/templating/capture.js +14 -2
- package/dist/javascript/templating/capture.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +14 -0
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +107 -14
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +1 -1
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +88 -22
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +11 -1
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +1 -1
- package/dist/test/rewrite-test.js.map +1 -1
- package/dist/version.txt +1 -1
- package/package.json +1 -1
- package/src/javascript/format.ts +2 -1
- package/src/javascript/templating/capture.ts +19 -4
- package/src/javascript/templating/engine.ts +119 -18
- package/src/javascript/templating/pattern.ts +97 -25
- package/src/javascript/templating/types.ts +11 -1
- package/src/test/rewrite-test.ts +1 -1
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {J} from '../../java';
|
|
17
|
-
import {Capture,
|
|
16
|
+
import {J, Type} from '../../java';
|
|
17
|
+
import {Any, Capture, CaptureOptions, TemplateParam, VariadicOptions} from './types';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Combines multiple constraints with AND logic.
|
|
@@ -70,6 +70,8 @@ export const CAPTURE_VARIADIC_SYMBOL = Symbol('captureVariadic');
|
|
|
70
70
|
export const CAPTURE_CONSTRAINT_SYMBOL = Symbol('captureConstraint');
|
|
71
71
|
// Symbol to access capturing flag without triggering Proxy
|
|
72
72
|
export const CAPTURE_CAPTURING_SYMBOL = Symbol('captureCapturing');
|
|
73
|
+
// Symbol to access type information without triggering Proxy
|
|
74
|
+
export const CAPTURE_TYPE_SYMBOL = Symbol('captureType');
|
|
73
75
|
|
|
74
76
|
export class CaptureImpl<T = any> implements Capture<T> {
|
|
75
77
|
public readonly name: string;
|
|
@@ -77,6 +79,7 @@ export class CaptureImpl<T = any> implements Capture<T> {
|
|
|
77
79
|
[CAPTURE_VARIADIC_SYMBOL]: VariadicOptions | undefined;
|
|
78
80
|
[CAPTURE_CONSTRAINT_SYMBOL]: ((node: T) => boolean) | undefined;
|
|
79
81
|
[CAPTURE_CAPTURING_SYMBOL]: boolean;
|
|
82
|
+
[CAPTURE_TYPE_SYMBOL]: string | Type | undefined;
|
|
80
83
|
|
|
81
84
|
constructor(name: string, options?: CaptureOptions<T>, capturing: boolean = true) {
|
|
82
85
|
this.name = name;
|
|
@@ -99,6 +102,11 @@ export class CaptureImpl<T = any> implements Capture<T> {
|
|
|
99
102
|
if (options?.constraint) {
|
|
100
103
|
this[CAPTURE_CONSTRAINT_SYMBOL] = options.constraint;
|
|
101
104
|
}
|
|
105
|
+
|
|
106
|
+
// Store type if provided
|
|
107
|
+
if (options?.type) {
|
|
108
|
+
this[CAPTURE_TYPE_SYMBOL] = options.type;
|
|
109
|
+
}
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
getName(): string {
|
|
@@ -120,6 +128,10 @@ export class CaptureImpl<T = any> implements Capture<T> {
|
|
|
120
128
|
isCapturing(): boolean {
|
|
121
129
|
return this[CAPTURE_CAPTURING_SYMBOL];
|
|
122
130
|
}
|
|
131
|
+
|
|
132
|
+
getType(): string | Type | undefined {
|
|
133
|
+
return this[CAPTURE_TYPE_SYMBOL];
|
|
134
|
+
}
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
export class TemplateParamImpl<T = any> implements TemplateParam<T> {
|
|
@@ -291,6 +303,9 @@ function createCaptureProxy<T>(impl: CaptureImpl<T>): any {
|
|
|
291
303
|
if (prop === CAPTURE_CAPTURING_SYMBOL) {
|
|
292
304
|
return target[CAPTURE_CAPTURING_SYMBOL];
|
|
293
305
|
}
|
|
306
|
+
if (prop === CAPTURE_TYPE_SYMBOL) {
|
|
307
|
+
return target[CAPTURE_TYPE_SYMBOL];
|
|
308
|
+
}
|
|
294
309
|
|
|
295
310
|
// Support using Capture as object key via computed properties {[x]: value}
|
|
296
311
|
if (prop === Symbol.toPrimitive || prop === 'toString' || prop === 'valueOf') {
|
|
@@ -298,7 +313,7 @@ function createCaptureProxy<T>(impl: CaptureImpl<T>): any {
|
|
|
298
313
|
}
|
|
299
314
|
|
|
300
315
|
// Allow methods to be called directly on the target
|
|
301
|
-
if (prop === 'getName' || prop === 'isVariadic' || prop === 'getVariadicOptions' || prop === 'getConstraint' || prop === 'isCapturing') {
|
|
316
|
+
if (prop === 'getName' || prop === 'isVariadic' || prop === 'getVariadicOptions' || prop === 'getConstraint' || prop === 'isCapturing' || prop === 'getType') {
|
|
302
317
|
return target[prop].bind(target);
|
|
303
318
|
}
|
|
304
319
|
|
|
@@ -335,7 +350,7 @@ function createCaptureProxy<T>(impl: CaptureImpl<T>): any {
|
|
|
335
350
|
|
|
336
351
|
// Overload 1: Options object with constraint (no variadic)
|
|
337
352
|
export function capture<T = any>(
|
|
338
|
-
options:
|
|
353
|
+
options: CaptureOptions<T> & { variadic?: never }
|
|
339
354
|
): Capture<T> & T;
|
|
340
355
|
|
|
341
356
|
// Overload 2: Options object with variadic
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import {Cursor, isTree} from '../..';
|
|
17
|
-
import {J} from '../../java';
|
|
17
|
+
import {J, Type} from '../../java';
|
|
18
18
|
import {JS} from '..';
|
|
19
19
|
import {produce} from 'immer';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
20
|
+
import {PlaceholderUtils, TemplateCache} from './utils';
|
|
21
|
+
import {CAPTURE_NAME_SYMBOL, CAPTURE_TYPE_SYMBOL, CaptureImpl, CaptureValue, TemplateParamImpl} from './capture';
|
|
22
22
|
import {PlaceholderReplacementVisitor} from './placeholder-replacement';
|
|
23
23
|
import {JavaCoordinates} from './template';
|
|
24
24
|
|
|
@@ -66,6 +66,9 @@ export class TemplateEngine {
|
|
|
66
66
|
contextStatements: string[] = [],
|
|
67
67
|
dependencies: Record<string, string> = {}
|
|
68
68
|
): Promise<J | undefined> {
|
|
69
|
+
// Generate type preamble for captures/parameters with types
|
|
70
|
+
const preamble = TemplateEngine.generateTypePreamble(parameters);
|
|
71
|
+
|
|
69
72
|
// Build the template string with parameter placeholders
|
|
70
73
|
const templateString = TemplateEngine.buildTemplateString(templateParts, parameters);
|
|
71
74
|
|
|
@@ -74,12 +77,17 @@ export class TemplateEngine {
|
|
|
74
77
|
return undefined;
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
// Add preamble to context statements (so they're skipped during extraction)
|
|
81
|
+
const contextWithPreamble = preamble.length > 0
|
|
82
|
+
? [...contextStatements, ...preamble]
|
|
83
|
+
: contextStatements;
|
|
84
|
+
|
|
77
85
|
// Use cache to get or parse the compilation unit
|
|
78
86
|
// For templates, we don't have captures, so use empty array
|
|
79
87
|
const cu = await templateCache.getOrParse(
|
|
80
88
|
templateString,
|
|
81
89
|
[], // templates don't have captures in the cache key
|
|
82
|
-
|
|
90
|
+
contextWithPreamble,
|
|
83
91
|
dependencies
|
|
84
92
|
);
|
|
85
93
|
|
|
@@ -88,31 +96,25 @@ export class TemplateEngine {
|
|
|
88
96
|
throw new Error(`Failed to parse template code (no statements):\n${templateString}`);
|
|
89
97
|
}
|
|
90
98
|
|
|
91
|
-
//
|
|
92
|
-
const
|
|
93
|
-
if (templateStatementIndex >= cu.statements.length) {
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Extract the relevant part of the AST
|
|
98
|
-
const firstStatement = cu.statements[templateStatementIndex].element;
|
|
99
|
+
// The template code is always the last statement (after context + preamble)
|
|
100
|
+
const lastStatement = cu.statements[cu.statements.length - 1].element;
|
|
99
101
|
let extracted: J;
|
|
100
102
|
|
|
101
103
|
// Check if this is a wrapped template (function __TEMPLATE__() { ... })
|
|
102
|
-
if (
|
|
103
|
-
const func =
|
|
104
|
+
if (lastStatement.kind === J.Kind.MethodDeclaration) {
|
|
105
|
+
const func = lastStatement as J.MethodDeclaration;
|
|
104
106
|
if (func.name.simpleName === '__TEMPLATE__' && func.body) {
|
|
105
107
|
// __TEMPLATE__ wrapper indicates the original template was a block.
|
|
106
108
|
// Always return the block to preserve the block structure.
|
|
107
109
|
extracted = func.body;
|
|
108
110
|
} else {
|
|
109
111
|
// Not a __TEMPLATE__ wrapper
|
|
110
|
-
extracted =
|
|
112
|
+
extracted = lastStatement;
|
|
111
113
|
}
|
|
112
|
-
} else if (
|
|
113
|
-
extracted = (
|
|
114
|
+
} else if (lastStatement.kind === JS.Kind.ExpressionStatement) {
|
|
115
|
+
extracted = (lastStatement as JS.ExpressionStatement).expression;
|
|
114
116
|
} else {
|
|
115
|
-
extracted =
|
|
117
|
+
extracted = lastStatement;
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
// Create a copy to avoid sharing cached AST instances
|
|
@@ -133,6 +135,58 @@ export class TemplateEngine {
|
|
|
133
135
|
return new TemplateApplier(cursor, coordinates, unsubstitutedAst).apply();
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Generates type preamble declarations for captures/parameters with type annotations.
|
|
140
|
+
*
|
|
141
|
+
* @param parameters The parameters
|
|
142
|
+
* @returns Array of preamble statements
|
|
143
|
+
*/
|
|
144
|
+
private static generateTypePreamble(parameters: Parameter[]): string[] {
|
|
145
|
+
const preamble: string[] = [];
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < parameters.length; i++) {
|
|
148
|
+
const param = parameters[i].value;
|
|
149
|
+
const placeholder = `${PlaceholderUtils.PLACEHOLDER_PREFIX}${i}__`;
|
|
150
|
+
|
|
151
|
+
// Check for Capture (could be a Proxy, so check for symbol property)
|
|
152
|
+
const isCapture = param instanceof CaptureImpl ||
|
|
153
|
+
(param && typeof param === 'object' && param[CAPTURE_NAME_SYMBOL]);
|
|
154
|
+
const isCaptureValue = param instanceof CaptureValue;
|
|
155
|
+
const isTreeArray = Array.isArray(param) && param.length > 0 && isTree(param[0]);
|
|
156
|
+
|
|
157
|
+
if (isCapture) {
|
|
158
|
+
const captureType = param[CAPTURE_TYPE_SYMBOL];
|
|
159
|
+
if (captureType) {
|
|
160
|
+
const typeString = typeof captureType === 'string'
|
|
161
|
+
? captureType
|
|
162
|
+
: this.typeToString(captureType);
|
|
163
|
+
preamble.push(`const ${placeholder}: ${typeString};`);
|
|
164
|
+
}
|
|
165
|
+
} else if (isCaptureValue) {
|
|
166
|
+
// For CaptureValue, check if the root capture has a type
|
|
167
|
+
const rootCapture = param.rootCapture;
|
|
168
|
+
if (rootCapture) {
|
|
169
|
+
const captureType = (rootCapture as any)[CAPTURE_TYPE_SYMBOL];
|
|
170
|
+
if (captureType) {
|
|
171
|
+
const typeString = typeof captureType === 'string'
|
|
172
|
+
? captureType
|
|
173
|
+
: this.typeToString(captureType);
|
|
174
|
+
preamble.push(`const ${placeholder}: ${typeString};`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else if (isTree(param) && !isTreeArray) {
|
|
178
|
+
// For J elements, derive type from the element's type property if it exists
|
|
179
|
+
const jElement = param as J;
|
|
180
|
+
if ((jElement as any).type) {
|
|
181
|
+
const typeString = this.typeToString((jElement as any).type);
|
|
182
|
+
preamble.push(`const ${placeholder}: ${typeString};`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return preamble;
|
|
188
|
+
}
|
|
189
|
+
|
|
136
190
|
/**
|
|
137
191
|
* Builds a template string with parameter placeholders.
|
|
138
192
|
*
|
|
@@ -174,6 +228,53 @@ export class TemplateEngine {
|
|
|
174
228
|
|
|
175
229
|
return result;
|
|
176
230
|
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Converts a Type instance to a TypeScript type string.
|
|
234
|
+
*
|
|
235
|
+
* @param type The Type instance
|
|
236
|
+
* @returns A TypeScript type string
|
|
237
|
+
*/
|
|
238
|
+
private static typeToString(type: Type): string {
|
|
239
|
+
// Handle Type.Class and Type.ShallowClass - return their fully qualified names
|
|
240
|
+
if (type.kind === Type.Kind.Class || type.kind === Type.Kind.ShallowClass) {
|
|
241
|
+
const classType = type as Type.Class;
|
|
242
|
+
return classType.fullyQualifiedName;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Handle Type.Primitive - map to TypeScript primitive types
|
|
246
|
+
if (type.kind === Type.Kind.Primitive) {
|
|
247
|
+
const primitiveType = type as Type.Primitive;
|
|
248
|
+
switch (primitiveType.keyword) {
|
|
249
|
+
case 'String':
|
|
250
|
+
return 'string';
|
|
251
|
+
case 'boolean':
|
|
252
|
+
return 'boolean';
|
|
253
|
+
case 'double':
|
|
254
|
+
case 'float':
|
|
255
|
+
case 'int':
|
|
256
|
+
case 'long':
|
|
257
|
+
case 'short':
|
|
258
|
+
case 'byte':
|
|
259
|
+
return 'number';
|
|
260
|
+
case 'void':
|
|
261
|
+
return 'void';
|
|
262
|
+
default:
|
|
263
|
+
return 'any';
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Handle Type.Array - render component type plus []
|
|
268
|
+
if (type.kind === Type.Kind.Array) {
|
|
269
|
+
const arrayType = type as Type.Array;
|
|
270
|
+
const componentTypeString = this.typeToString(arrayType.elemType);
|
|
271
|
+
return `${componentTypeString}[]`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// For other types, return 'any' as a fallback
|
|
275
|
+
// TODO: Implement proper Type to string conversion for other Type.Kind values
|
|
276
|
+
return 'any';
|
|
277
|
+
}
|
|
177
278
|
}
|
|
178
279
|
|
|
179
280
|
/**
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import {produce} from 'immer';
|
|
17
|
-
import {J} from '../../java';
|
|
17
|
+
import {J, Type} from '../../java';
|
|
18
18
|
import {JS} from '../index';
|
|
19
19
|
import {randomId} from '../../uuid';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
20
|
+
import {Any, Capture, PatternOptions} from './types';
|
|
21
|
+
import {CAPTURE_CAPTURING_SYMBOL, CAPTURE_NAME_SYMBOL, CAPTURE_TYPE_SYMBOL, CaptureImpl} from './capture';
|
|
22
22
|
import {PatternMatchingComparator} from './comparator';
|
|
23
|
-
import {
|
|
23
|
+
import {CaptureMarker, CaptureStorageValue, PlaceholderUtils, templateCache, WRAPPERS_MAP_SYMBOL} from './utils';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Builder for creating patterns programmatically.
|
|
@@ -514,21 +514,52 @@ class TemplateProcessor {
|
|
|
514
514
|
* @returns A Promise resolving to the AST pattern
|
|
515
515
|
*/
|
|
516
516
|
async toAstPattern(): Promise<J> {
|
|
517
|
+
// Generate type preamble for captures with types
|
|
518
|
+
const preamble = this.generateTypePreamble();
|
|
519
|
+
|
|
517
520
|
// Combine template parts and placeholders
|
|
518
521
|
const templateString = this.buildTemplateString();
|
|
519
522
|
|
|
523
|
+
// Add preamble to context statements (so they're skipped during extraction)
|
|
524
|
+
const contextWithPreamble = preamble.length > 0
|
|
525
|
+
? [...this.contextStatements, ...preamble]
|
|
526
|
+
: this.contextStatements;
|
|
527
|
+
|
|
520
528
|
// Use cache to get or parse the compilation unit
|
|
521
529
|
const cu = await templateCache.getOrParse(
|
|
522
530
|
templateString,
|
|
523
531
|
this.captures,
|
|
524
|
-
|
|
532
|
+
contextWithPreamble,
|
|
525
533
|
this.dependencies
|
|
526
534
|
);
|
|
527
535
|
|
|
528
536
|
// Extract the relevant part of the AST
|
|
537
|
+
// The pattern code is always the last statement (after context + preamble)
|
|
529
538
|
return this.extractPatternFromAst(cu);
|
|
530
539
|
}
|
|
531
540
|
|
|
541
|
+
/**
|
|
542
|
+
* Generates type preamble declarations for captures with type annotations.
|
|
543
|
+
*
|
|
544
|
+
* @returns Array of preamble statements
|
|
545
|
+
*/
|
|
546
|
+
private generateTypePreamble(): string[] {
|
|
547
|
+
const preamble: string[] = [];
|
|
548
|
+
for (const capture of this.captures) {
|
|
549
|
+
const captureName = (capture as any)[CAPTURE_NAME_SYMBOL] || capture.getName();
|
|
550
|
+
const captureType = (capture as any)[CAPTURE_TYPE_SYMBOL];
|
|
551
|
+
if (captureType) {
|
|
552
|
+
// Convert Type to string if needed
|
|
553
|
+
const typeString = typeof captureType === 'string'
|
|
554
|
+
? captureType
|
|
555
|
+
: this.typeToString(captureType);
|
|
556
|
+
const placeholder = PlaceholderUtils.createCapture(captureName, undefined);
|
|
557
|
+
preamble.push(`const ${placeholder}: ${typeString};`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return preamble;
|
|
561
|
+
}
|
|
562
|
+
|
|
532
563
|
/**
|
|
533
564
|
* Builds a template string with placeholders for captures.
|
|
534
565
|
* If the template looks like a block pattern, wraps it in a function.
|
|
@@ -561,46 +592,87 @@ class TemplateProcessor {
|
|
|
561
592
|
return result;
|
|
562
593
|
}
|
|
563
594
|
|
|
595
|
+
/**
|
|
596
|
+
* Converts a Type instance to a TypeScript type string.
|
|
597
|
+
*
|
|
598
|
+
* @param type The Type instance
|
|
599
|
+
* @returns A TypeScript type string
|
|
600
|
+
*/
|
|
601
|
+
private typeToString(type: Type): string {
|
|
602
|
+
// Handle Type.Class and Type.ShallowClass - return their fully qualified names
|
|
603
|
+
if (type.kind === Type.Kind.Class || type.kind === Type.Kind.ShallowClass) {
|
|
604
|
+
const classType = type as Type.Class;
|
|
605
|
+
return classType.fullyQualifiedName;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Handle Type.Primitive - map to TypeScript primitive types
|
|
609
|
+
if (type.kind === Type.Kind.Primitive) {
|
|
610
|
+
const primitiveType = type as Type.Primitive;
|
|
611
|
+
switch (primitiveType.keyword) {
|
|
612
|
+
case 'String':
|
|
613
|
+
return 'string';
|
|
614
|
+
case 'boolean':
|
|
615
|
+
return 'boolean';
|
|
616
|
+
case 'double':
|
|
617
|
+
case 'float':
|
|
618
|
+
case 'int':
|
|
619
|
+
case 'long':
|
|
620
|
+
case 'short':
|
|
621
|
+
case 'byte':
|
|
622
|
+
return 'number';
|
|
623
|
+
case 'void':
|
|
624
|
+
return 'void';
|
|
625
|
+
default:
|
|
626
|
+
return 'any';
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Handle Type.Array - render component type plus []
|
|
631
|
+
if (type.kind === Type.Kind.Array) {
|
|
632
|
+
const arrayType = type as Type.Array;
|
|
633
|
+
const componentTypeString = this.typeToString(arrayType.elemType);
|
|
634
|
+
return `${componentTypeString}[]`;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// For other types, return 'any' as a fallback
|
|
638
|
+
// TODO: Implement proper Type to string conversion for other Type.Kind values
|
|
639
|
+
return 'any';
|
|
640
|
+
}
|
|
641
|
+
|
|
564
642
|
/**
|
|
565
643
|
* Extracts the pattern from the parsed AST.
|
|
644
|
+
* The pattern code is always the last statement in the compilation unit
|
|
645
|
+
* (after all context statements and type preamble declarations).
|
|
566
646
|
*
|
|
567
647
|
* @param cu The compilation unit
|
|
568
648
|
* @returns The extracted pattern
|
|
569
649
|
*/
|
|
570
650
|
private extractPatternFromAst(cu: JS.CompilationUnit): J {
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
// Check if we have any statements at the pattern index
|
|
575
|
-
if (!cu.statements || patternStatementIndex >= cu.statements.length) {
|
|
576
|
-
// If there's no statement at the index, but we have exactly one statement
|
|
577
|
-
// and it's a block, it might be the pattern itself (e.g., pattern`{ ... }`)
|
|
578
|
-
if (cu.statements && cu.statements.length === 1 && cu.statements[0].element.kind === J.Kind.Block) {
|
|
579
|
-
return this.attachCaptureMarkers(cu.statements[0].element);
|
|
580
|
-
}
|
|
581
|
-
throw new Error(`No statement found at index ${patternStatementIndex} in compilation unit with ${cu.statements?.length || 0} statements`);
|
|
651
|
+
// Check if we have any statements
|
|
652
|
+
if (!cu.statements || cu.statements.length === 0) {
|
|
653
|
+
throw new Error(`No statements found in compilation unit`);
|
|
582
654
|
}
|
|
583
655
|
|
|
584
|
-
//
|
|
585
|
-
const
|
|
656
|
+
// The pattern code is always the last statement
|
|
657
|
+
const lastStatement = cu.statements[cu.statements.length - 1].element;
|
|
586
658
|
|
|
587
659
|
let extracted: J;
|
|
588
660
|
|
|
589
661
|
// Check if this is our wrapper function for block patterns
|
|
590
|
-
if (
|
|
591
|
-
const method =
|
|
662
|
+
if (lastStatement.kind === J.Kind.MethodDeclaration) {
|
|
663
|
+
const method = lastStatement as J.MethodDeclaration;
|
|
592
664
|
if (method.name?.simpleName === '__PATTERN__' && method.body) {
|
|
593
665
|
// Extract the block from the wrapper function
|
|
594
666
|
extracted = method.body;
|
|
595
667
|
} else {
|
|
596
|
-
extracted =
|
|
668
|
+
extracted = lastStatement;
|
|
597
669
|
}
|
|
598
|
-
} else if (
|
|
599
|
-
// If the
|
|
600
|
-
extracted = (
|
|
670
|
+
} else if (lastStatement.kind === JS.Kind.ExpressionStatement) {
|
|
671
|
+
// If the statement is an expression statement, extract the expression
|
|
672
|
+
extracted = (lastStatement as JS.ExpressionStatement).expression;
|
|
601
673
|
} else {
|
|
602
674
|
// Otherwise, return the statement itself
|
|
603
|
-
extracted =
|
|
675
|
+
extracted = lastStatement;
|
|
604
676
|
}
|
|
605
677
|
|
|
606
678
|
// Attach CaptureMarkers to capture identifiers
|
|
@@ -14,7 +14,7 @@
|
|
|
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
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Options for variadic captures that match zero or more nodes in a sequence.
|
|
@@ -43,6 +43,16 @@ export interface CaptureOptions<T = any> {
|
|
|
43
43
|
name?: string;
|
|
44
44
|
variadic?: boolean | VariadicOptions;
|
|
45
45
|
constraint?: (node: T) => boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Type annotation for this capture. When provided, the template engine will generate
|
|
48
|
+
* a preamble declaring the capture identifier with this type annotation, allowing
|
|
49
|
+
* the TypeScript parser/compiler to produce a properly type-attributed AST.
|
|
50
|
+
*
|
|
51
|
+
* Can be specified as:
|
|
52
|
+
* - A string type annotation (e.g., "boolean", "string", "number")
|
|
53
|
+
* - A Type instance from the AST (the type will be inferred from the Type)
|
|
54
|
+
*/
|
|
55
|
+
type?: string | Type;
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
/**
|
package/src/test/rewrite-test.ts
CHANGED
|
@@ -175,7 +175,7 @@ export class RecipeSpec {
|
|
|
175
175
|
(spec.after as (actual: string) => string)(actualAfter) : spec.after as string;
|
|
176
176
|
expect(actualAfter).toEqual(afterSource);
|
|
177
177
|
if (spec.afterRecipe) {
|
|
178
|
-
await spec.afterRecipe(
|
|
178
|
+
await spec.afterRecipe(after);
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|