@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.
- package/dist/java/tree.d.ts +10 -1
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +21 -5
- package/dist/java/tree.js.map +1 -1
- package/dist/java/type-visitor.d.ts +1 -1
- package/dist/java/type-visitor.d.ts.map +1 -1
- package/dist/java/visitor.d.ts +2 -2
- package/dist/java/visitor.d.ts.map +1 -1
- package/dist/java/visitor.js +8 -2
- package/dist/java/visitor.js.map +1 -1
- package/dist/javascript/assertions.d.ts +6 -0
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +14 -6
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/comparator.d.ts +154 -7
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +623 -180
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format.d.ts +5 -3
- package/dist/javascript/format.d.ts.map +1 -1
- package/dist/javascript/format.js +85 -43
- package/dist/javascript/format.js.map +1 -1
- package/dist/javascript/index.d.ts +1 -0
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +1 -0
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/parser.d.ts +2 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +39 -30
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/templating/capture.d.ts +81 -14
- package/dist/javascript/templating/capture.d.ts.map +1 -1
- package/dist/javascript/templating/capture.js +98 -8
- package/dist/javascript/templating/capture.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +125 -15
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +946 -118
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +58 -25
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +527 -94
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/index.d.ts +3 -3
- package/dist/javascript/templating/index.d.ts.map +1 -1
- package/dist/javascript/templating/index.js +3 -1
- package/dist/javascript/templating/index.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +121 -16
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +528 -257
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +30 -5
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +183 -81
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/rewrite.d.ts +56 -11
- package/dist/javascript/templating/rewrite.d.ts.map +1 -1
- package/dist/javascript/templating/rewrite.js +143 -16
- package/dist/javascript/templating/rewrite.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +31 -5
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +89 -15
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +359 -12
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +52 -35
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +107 -109
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +21 -11
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/json/rpc.js +2 -2
- package/dist/json/rpc.js.map +1 -1
- package/dist/recipe/order-imports.js.map +1 -1
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +10 -6
- package/dist/test/rewrite-test.js.map +1 -1
- package/dist/version.txt +1 -1
- package/dist/visitor.d.ts +4 -4
- package/dist/visitor.d.ts.map +1 -1
- package/dist/visitor.js +8 -3
- package/dist/visitor.js.map +1 -1
- package/package.json +4 -2
- package/src/java/tree.ts +10 -3
- package/src/java/type-visitor.ts +1 -1
- package/src/java/visitor.ts +11 -5
- package/src/javascript/assertions.ts +9 -3
- package/src/javascript/comparator.ts +676 -185
- package/src/javascript/format.ts +72 -34
- package/src/javascript/index.ts +1 -0
- package/src/javascript/parser.ts +51 -31
- package/src/javascript/templating/capture.ts +107 -15
- package/src/javascript/templating/comparator.ts +1087 -134
- package/src/javascript/templating/engine.ts +601 -103
- package/src/javascript/templating/index.ts +9 -2
- package/src/javascript/templating/pattern.ts +655 -281
- package/src/javascript/templating/placeholder-replacement.ts +183 -80
- package/src/javascript/templating/rewrite.ts +152 -18
- package/src/javascript/templating/template.ts +110 -22
- package/src/javascript/templating/types.ts +386 -12
- package/src/javascript/templating/utils.ts +116 -102
- package/src/javascript/type-mapping.ts +20 -11
- package/src/json/rpc.ts +2 -2
- package/src/recipe/order-imports.ts +1 -1
- package/src/test/rewrite-test.ts +12 -7
- 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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
//
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
62
|
-
* - Pattern structure determines matching: `pattern`foo(${capture(
|
|
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(
|
|
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?():
|
|
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(${
|
|
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?():
|
|
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
|
-
* -
|
|
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
|
|
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:
|
|
299
|
-
after:
|
|
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
|
}
|