@openrewrite/rewrite 8.67.0-20251106-160325 → 8.67.0-20251107-071946
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 +2 -0
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +5 -1
- package/dist/java/tree.js.map +1 -1
- package/dist/javascript/assertions.js +2 -2
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/format.js +1 -1
- package/dist/javascript/format.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +34 -8
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +317 -60
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +11 -0
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +36 -295
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/template.d.ts +9 -2
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +37 -0
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +26 -11
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +41 -22
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +111 -76
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/version.txt +1 -1
- package/package.json +3 -1
- package/src/java/tree.ts +2 -0
- package/src/javascript/assertions.ts +1 -1
- package/src/javascript/format.ts +1 -1
- package/src/javascript/templating/engine.ts +376 -54
- package/src/javascript/templating/pattern.ts +55 -322
- package/src/javascript/templating/placeholder-replacement.ts +1 -1
- package/src/javascript/templating/template.ts +57 -3
- package/src/javascript/templating/types.ts +27 -11
- package/src/javascript/templating/utils.ts +113 -81
|
@@ -14,16 +14,13 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import {Cursor} from '../..';
|
|
17
|
-
import {J
|
|
18
|
-
import {JS} from '../index';
|
|
19
|
-
import {JavaScriptVisitor} from '../visitor';
|
|
20
|
-
import {produceAsync} from '../../visitor';
|
|
21
|
-
import {updateIfChanged} from '../../util';
|
|
17
|
+
import {J} from '../../java';
|
|
22
18
|
import {Any, Capture, PatternOptions} from './types';
|
|
23
|
-
import {CAPTURE_CAPTURING_SYMBOL, CAPTURE_NAME_SYMBOL,
|
|
19
|
+
import {CAPTURE_CAPTURING_SYMBOL, CAPTURE_NAME_SYMBOL, CaptureImpl} from './capture';
|
|
24
20
|
import {PatternMatchingComparator} from './comparator';
|
|
25
|
-
import {CaptureMarker, CaptureStorageValue,
|
|
26
|
-
import {
|
|
21
|
+
import {CaptureMarker, CaptureStorageValue, generateCacheKey, globalAstCache, WRAPPERS_MAP_SYMBOL} from './utils';
|
|
22
|
+
import {TemplateEngine} from './engine';
|
|
23
|
+
|
|
27
24
|
|
|
28
25
|
/**
|
|
29
26
|
* Builder for creating patterns programmatically.
|
|
@@ -121,6 +118,7 @@ export class PatternBuilder {
|
|
|
121
118
|
*/
|
|
122
119
|
export class Pattern {
|
|
123
120
|
private _options: PatternOptions = {};
|
|
121
|
+
private _cachedAstPattern?: J;
|
|
124
122
|
|
|
125
123
|
/**
|
|
126
124
|
* Gets the configuration options for this pattern.
|
|
@@ -175,9 +173,57 @@ export class Pattern {
|
|
|
175
173
|
*/
|
|
176
174
|
configure(options: PatternOptions): Pattern {
|
|
177
175
|
this._options = {...this._options, ...options};
|
|
176
|
+
// Invalidate cache when configuration changes
|
|
177
|
+
this._cachedAstPattern = undefined;
|
|
178
178
|
return this;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Gets the AST pattern for this pattern, using two-level caching:
|
|
183
|
+
* 1. Instance-level cache (fastest - this pattern instance)
|
|
184
|
+
* 2. Global LRU cache (fast - shared across pattern instances with same code)
|
|
185
|
+
* 3. Compute via TemplateProcessor (slow - parse and process)
|
|
186
|
+
*
|
|
187
|
+
* @returns The cached or newly computed pattern AST
|
|
188
|
+
* @internal
|
|
189
|
+
*/
|
|
190
|
+
async getAstPattern(): Promise<J> {
|
|
191
|
+
// Level 1: Instance cache (fastest path)
|
|
192
|
+
if (this._cachedAstPattern) {
|
|
193
|
+
return this._cachedAstPattern;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Generate cache key for global lookup
|
|
197
|
+
const contextStatements = this._options.context || this._options.imports || [];
|
|
198
|
+
const cacheKey = generateCacheKey(
|
|
199
|
+
this.templateParts,
|
|
200
|
+
this.captures.map(c => c.getName()).join(','),
|
|
201
|
+
contextStatements,
|
|
202
|
+
this._options.dependencies || {}
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Level 2: Global cache (fast path - shared with Template)
|
|
206
|
+
const cached = globalAstCache.get(cacheKey);
|
|
207
|
+
if (cached) {
|
|
208
|
+
this._cachedAstPattern = cached;
|
|
209
|
+
return cached;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Level 3: Compute via TemplateEngine (slow path)
|
|
213
|
+
const result = await TemplateEngine.getPatternTree(
|
|
214
|
+
this.templateParts,
|
|
215
|
+
this.captures,
|
|
216
|
+
contextStatements,
|
|
217
|
+
this._options.dependencies || {}
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Cache in both levels
|
|
221
|
+
globalAstCache.set(cacheKey, result);
|
|
222
|
+
this._cachedAstPattern = result;
|
|
223
|
+
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
181
227
|
/**
|
|
182
228
|
* Creates a matcher for this pattern against a specific AST node.
|
|
183
229
|
*
|
|
@@ -325,15 +371,7 @@ class Matcher {
|
|
|
325
371
|
*/
|
|
326
372
|
async matches(): Promise<boolean> {
|
|
327
373
|
if (!this.patternAst) {
|
|
328
|
-
|
|
329
|
-
const contextStatements = this.pattern.options.context || this.pattern.options.imports || [];
|
|
330
|
-
const templateProcessor = new TemplateProcessor(
|
|
331
|
-
this.pattern.templateParts,
|
|
332
|
-
this.pattern.captures,
|
|
333
|
-
contextStatements,
|
|
334
|
-
this.pattern.options.dependencies || {}
|
|
335
|
-
);
|
|
336
|
-
this.patternAst = await templateProcessor.toAstPattern();
|
|
374
|
+
this.patternAst = await this.pattern.getAstPattern();
|
|
337
375
|
}
|
|
338
376
|
|
|
339
377
|
return this.matchNode(this.patternAst, this.ast);
|
|
@@ -484,311 +522,6 @@ class Matcher {
|
|
|
484
522
|
}
|
|
485
523
|
}
|
|
486
524
|
|
|
487
|
-
/**
|
|
488
|
-
* Visitor that attaches CaptureMarkers to capture identifiers in the AST.
|
|
489
|
-
* Markers are attached to Identifiers, then moved up to wrappers (RightPadded, ExpressionStatement).
|
|
490
|
-
* Uses JavaScriptVisitor to properly handle AST traversal and avoid cycles in Type objects.
|
|
491
|
-
*/
|
|
492
|
-
class MarkerAttachmentVisitor extends JavaScriptVisitor<undefined> {
|
|
493
|
-
constructor(private readonly captures: (Capture | Any<any>)[]) {
|
|
494
|
-
super();
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Attaches CaptureMarker to capture identifiers.
|
|
499
|
-
*/
|
|
500
|
-
protected override async visitIdentifier(ident: J.Identifier, p: undefined): Promise<J | undefined> {
|
|
501
|
-
// First call parent to handle standard visitation
|
|
502
|
-
const visited = await super.visitIdentifier(ident, p);
|
|
503
|
-
if (!visited || visited.kind !== J.Kind.Identifier) {
|
|
504
|
-
return visited;
|
|
505
|
-
}
|
|
506
|
-
ident = visited as J.Identifier;
|
|
507
|
-
|
|
508
|
-
// Check if this is a capture placeholder
|
|
509
|
-
if (ident.simpleName?.startsWith(PlaceholderUtils.CAPTURE_PREFIX)) {
|
|
510
|
-
const captureInfo = PlaceholderUtils.parseCapture(ident.simpleName);
|
|
511
|
-
if (captureInfo) {
|
|
512
|
-
// Find the original capture object to get variadic options and constraint
|
|
513
|
-
const captureObj = this.captures.find(c => c.getName() === captureInfo.name);
|
|
514
|
-
const variadicOptions = captureObj?.getVariadicOptions();
|
|
515
|
-
const constraint = captureObj?.getConstraint?.();
|
|
516
|
-
|
|
517
|
-
// Add CaptureMarker to the Identifier with constraint
|
|
518
|
-
const marker = new CaptureMarker(captureInfo.name, variadicOptions, constraint);
|
|
519
|
-
return updateIfChanged(ident, {
|
|
520
|
-
markers: {
|
|
521
|
-
...ident.markers,
|
|
522
|
-
markers: [...ident.markers.markers, marker]
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return ident;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* Propagates markers from element to RightPadded wrapper.
|
|
533
|
-
*/
|
|
534
|
-
public override async visitRightPadded<T extends J | boolean>(right: J.RightPadded<T>, p: undefined): Promise<J.RightPadded<T>> {
|
|
535
|
-
if (!isTree(right.element)) {
|
|
536
|
-
return right;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
const visitedElement = await this.visit(right.element as J, p);
|
|
540
|
-
if (visitedElement && visitedElement !== right.element as Tree) {
|
|
541
|
-
return produceAsync<J.RightPadded<T>>(right, async (draft: any) => {
|
|
542
|
-
// Visit element first
|
|
543
|
-
if (right.element && (right.element as any).kind) {
|
|
544
|
-
// Check if element has a CaptureMarker
|
|
545
|
-
const elementMarker = PlaceholderUtils.getCaptureMarker(visitedElement);
|
|
546
|
-
if (elementMarker) {
|
|
547
|
-
draft.markers.markers.push(elementMarker);
|
|
548
|
-
} else {
|
|
549
|
-
draft.element = visitedElement;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
return right;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Propagates markers from expression to ExpressionStatement.
|
|
560
|
-
*/
|
|
561
|
-
protected override async visitExpressionStatement(expressionStatement: JS.ExpressionStatement, p: undefined): Promise<J | undefined> {
|
|
562
|
-
// Visit the expression
|
|
563
|
-
const visitedExpression = await this.visit(expressionStatement.expression, p);
|
|
564
|
-
|
|
565
|
-
// Check if expression has a CaptureMarker
|
|
566
|
-
const expressionMarker = PlaceholderUtils.getCaptureMarker(visitedExpression as any);
|
|
567
|
-
if (expressionMarker) {
|
|
568
|
-
return updateIfChanged(expressionStatement, {
|
|
569
|
-
markers: {
|
|
570
|
-
...expressionStatement.markers,
|
|
571
|
-
markers: [...expressionStatement.markers.markers, expressionMarker]
|
|
572
|
-
},
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// No marker to move, just update with visited expression
|
|
577
|
-
return updateIfChanged(expressionStatement, {
|
|
578
|
-
expression: visitedExpression
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* Processor for template strings.
|
|
585
|
-
* Converts a template string with captures into an AST pattern.
|
|
586
|
-
*/
|
|
587
|
-
class TemplateProcessor {
|
|
588
|
-
/**
|
|
589
|
-
* Creates a new template processor.
|
|
590
|
-
*
|
|
591
|
-
* @param templateParts The string parts of the template
|
|
592
|
-
* @param captures The captures between the string parts (can be Capture or Any)
|
|
593
|
-
* @param contextStatements Context declarations (imports, types, etc.) to prepend for type attribution
|
|
594
|
-
* @param dependencies NPM dependencies for type attribution
|
|
595
|
-
*/
|
|
596
|
-
constructor(
|
|
597
|
-
private readonly templateParts: TemplateStringsArray,
|
|
598
|
-
private readonly captures: (Capture | Any<any>)[],
|
|
599
|
-
private readonly contextStatements: string[] = [],
|
|
600
|
-
private readonly dependencies: Record<string, string> = {}
|
|
601
|
-
) {
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* Converts the template to an AST pattern.
|
|
606
|
-
*
|
|
607
|
-
* @returns A Promise resolving to the AST pattern
|
|
608
|
-
*/
|
|
609
|
-
async toAstPattern(): Promise<J> {
|
|
610
|
-
// Generate type preamble for captures with types
|
|
611
|
-
const preamble = this.generateTypePreamble();
|
|
612
|
-
|
|
613
|
-
// Combine template parts and placeholders
|
|
614
|
-
const templateString = this.buildTemplateString();
|
|
615
|
-
|
|
616
|
-
// Add preamble to context statements (so they're skipped during extraction)
|
|
617
|
-
const contextWithPreamble = preamble.length > 0
|
|
618
|
-
? [...this.contextStatements, ...preamble]
|
|
619
|
-
: this.contextStatements;
|
|
620
|
-
|
|
621
|
-
// Use cache to get or parse the compilation unit
|
|
622
|
-
const cu = await templateCache.getOrParse(
|
|
623
|
-
templateString,
|
|
624
|
-
this.captures,
|
|
625
|
-
contextWithPreamble,
|
|
626
|
-
this.dependencies
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
// Extract the relevant part of the AST
|
|
630
|
-
// The pattern code is always the last statement (after context + preamble)
|
|
631
|
-
return await this.extractPatternFromAst(cu);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Generates type preamble declarations for captures with type annotations.
|
|
636
|
-
*
|
|
637
|
-
* @returns Array of preamble statements
|
|
638
|
-
*/
|
|
639
|
-
private generateTypePreamble(): string[] {
|
|
640
|
-
const preamble: string[] = [];
|
|
641
|
-
for (const capture of this.captures) {
|
|
642
|
-
const captureName = (capture as any)[CAPTURE_NAME_SYMBOL] || capture.getName();
|
|
643
|
-
const captureType = (capture as any)[CAPTURE_TYPE_SYMBOL];
|
|
644
|
-
if (captureType) {
|
|
645
|
-
// Convert Type to string if needed
|
|
646
|
-
const typeString = typeof captureType === 'string'
|
|
647
|
-
? captureType
|
|
648
|
-
: this.typeToString(captureType);
|
|
649
|
-
const placeholder = PlaceholderUtils.createCapture(captureName, undefined);
|
|
650
|
-
preamble.push(`let ${placeholder}: ${typeString};`);
|
|
651
|
-
} else {
|
|
652
|
-
const placeholder = PlaceholderUtils.createCapture(captureName, undefined);
|
|
653
|
-
preamble.push(`let ${placeholder};`);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
return preamble;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
/**
|
|
660
|
-
* Builds a template string with placeholders for captures.
|
|
661
|
-
* If the template looks like a block pattern, wraps it in a function.
|
|
662
|
-
*
|
|
663
|
-
* @returns The template string
|
|
664
|
-
*/
|
|
665
|
-
private buildTemplateString(): string {
|
|
666
|
-
let result = '';
|
|
667
|
-
for (let i = 0; i < this.templateParts.length; i++) {
|
|
668
|
-
result += this.templateParts[i];
|
|
669
|
-
if (i < this.captures.length) {
|
|
670
|
-
const capture = this.captures[i];
|
|
671
|
-
// Use symbol to access capture name without triggering Proxy
|
|
672
|
-
const captureName = (capture as any)[CAPTURE_NAME_SYMBOL] || capture.getName();
|
|
673
|
-
result += PlaceholderUtils.createCapture(captureName, undefined);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Check if this looks like a block pattern (starts with { and contains statement keywords)
|
|
678
|
-
const trimmed = result.trim();
|
|
679
|
-
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
680
|
-
// Check for statement keywords that indicate this is a block, not an object literal
|
|
681
|
-
const hasStatementKeywords = /\b(return|if|for|while|do|switch|try|throw|break|continue|const|let|var|function|class)\b/.test(result);
|
|
682
|
-
if (hasStatementKeywords) {
|
|
683
|
-
// Wrap in a function to ensure it parses as a block
|
|
684
|
-
return `function __PATTERN__() ${result}`;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
return result;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* Converts a Type instance to a TypeScript type string.
|
|
693
|
-
*
|
|
694
|
-
* @param type The Type instance
|
|
695
|
-
* @returns A TypeScript type string
|
|
696
|
-
*/
|
|
697
|
-
private typeToString(type: Type): string {
|
|
698
|
-
// Handle Type.Class and Type.ShallowClass - return their fully qualified names
|
|
699
|
-
if (type.kind === Type.Kind.Class || type.kind === Type.Kind.ShallowClass) {
|
|
700
|
-
const classType = type as Type.Class;
|
|
701
|
-
return classType.fullyQualifiedName;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// Handle Type.Primitive - map to TypeScript primitive types
|
|
705
|
-
if (type.kind === Type.Kind.Primitive) {
|
|
706
|
-
const primitiveType = type as Type.Primitive;
|
|
707
|
-
switch (primitiveType.keyword) {
|
|
708
|
-
case 'String':
|
|
709
|
-
return 'string';
|
|
710
|
-
case 'boolean':
|
|
711
|
-
return 'boolean';
|
|
712
|
-
case 'double':
|
|
713
|
-
case 'float':
|
|
714
|
-
case 'int':
|
|
715
|
-
case 'long':
|
|
716
|
-
case 'short':
|
|
717
|
-
case 'byte':
|
|
718
|
-
return 'number';
|
|
719
|
-
case 'void':
|
|
720
|
-
return 'void';
|
|
721
|
-
default:
|
|
722
|
-
return 'any';
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// Handle Type.Array - render component type plus []
|
|
727
|
-
if (type.kind === Type.Kind.Array) {
|
|
728
|
-
const arrayType = type as Type.Array;
|
|
729
|
-
const componentTypeString = this.typeToString(arrayType.elemType);
|
|
730
|
-
return `${componentTypeString}[]`;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// For other types, return 'any' as a fallback
|
|
734
|
-
// TODO: Implement proper Type to string conversion for other Type.Kind values
|
|
735
|
-
return 'any';
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
/**
|
|
739
|
-
* Extracts the pattern from the parsed AST.
|
|
740
|
-
* The pattern code is always the last statement in the compilation unit
|
|
741
|
-
* (after all context statements and type preamble declarations).
|
|
742
|
-
*
|
|
743
|
-
* @param cu The compilation unit
|
|
744
|
-
* @returns The extracted pattern
|
|
745
|
-
*/
|
|
746
|
-
private async extractPatternFromAst(cu: JS.CompilationUnit): Promise<J> {
|
|
747
|
-
// Check if we have any statements
|
|
748
|
-
if (!cu.statements || cu.statements.length === 0) {
|
|
749
|
-
throw new Error(`No statements found in compilation unit`);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// The pattern code is always the last statement
|
|
753
|
-
const lastStatement = cu.statements[cu.statements.length - 1].element;
|
|
754
|
-
|
|
755
|
-
let extracted: J;
|
|
756
|
-
|
|
757
|
-
// Check if this is our wrapper function for block patterns
|
|
758
|
-
if (lastStatement.kind === J.Kind.MethodDeclaration) {
|
|
759
|
-
const method = lastStatement as J.MethodDeclaration;
|
|
760
|
-
if (method.name?.simpleName === '__PATTERN__' && method.body) {
|
|
761
|
-
// Extract the block from the wrapper function
|
|
762
|
-
extracted = method.body;
|
|
763
|
-
} else {
|
|
764
|
-
extracted = lastStatement;
|
|
765
|
-
}
|
|
766
|
-
} else if (lastStatement.kind === JS.Kind.ExpressionStatement) {
|
|
767
|
-
// If the statement is an expression statement, extract the expression
|
|
768
|
-
extracted = (lastStatement as JS.ExpressionStatement).expression;
|
|
769
|
-
} else {
|
|
770
|
-
// Otherwise, return the statement itself
|
|
771
|
-
extracted = lastStatement;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// Attach CaptureMarkers to capture identifiers
|
|
775
|
-
return await this.attachCaptureMarkers(extracted);
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* Attaches CaptureMarkers to capture identifiers in the AST.
|
|
780
|
-
* This allows efficient capture detection without string parsing.
|
|
781
|
-
* Uses JavaScriptVisitor to properly handle AST traversal and avoid cycles in Type objects.
|
|
782
|
-
*
|
|
783
|
-
* @param ast The AST to process
|
|
784
|
-
* @returns The AST with CaptureMarkers attached
|
|
785
|
-
*/
|
|
786
|
-
private async attachCaptureMarkers(ast: J): Promise<J> {
|
|
787
|
-
const visitor = new MarkerAttachmentVisitor(this.captures);
|
|
788
|
-
return (await visitor.visit(ast, undefined))!;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
525
|
/**
|
|
793
526
|
* Tagged template function for creating patterns.
|
|
794
527
|
*
|
|
@@ -20,7 +20,7 @@ import {JavaScriptVisitor} from '../visitor';
|
|
|
20
20
|
import {produce} from 'immer';
|
|
21
21
|
import {PlaceholderUtils} from './utils';
|
|
22
22
|
import {CaptureImpl, TemplateParamImpl, CaptureValue, CAPTURE_NAME_SYMBOL} from './capture';
|
|
23
|
-
import {Parameter} from './
|
|
23
|
+
import {Parameter} from './types';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Visitor that replaces placeholder nodes with actual parameter values.
|
|
@@ -15,11 +15,11 @@
|
|
|
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';
|
|
20
|
+
import {generateCacheKey, globalAstCache, WRAPPERS_MAP_SYMBOL} from './utils';
|
|
21
21
|
import {CAPTURE_NAME_SYMBOL} from './capture';
|
|
22
|
-
import {TemplateEngine
|
|
22
|
+
import {TemplateEngine} from './engine';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Coordinates for template application.
|
|
@@ -171,6 +171,7 @@ export class TemplateBuilder {
|
|
|
171
171
|
*/
|
|
172
172
|
export class Template {
|
|
173
173
|
private options: TemplateOptions = {};
|
|
174
|
+
private _cachedTemplate?: J;
|
|
174
175
|
|
|
175
176
|
/**
|
|
176
177
|
* Creates a new builder for constructing templates programmatically.
|
|
@@ -216,9 +217,55 @@ export class Template {
|
|
|
216
217
|
*/
|
|
217
218
|
configure(options: TemplateOptions): Template {
|
|
218
219
|
this.options = { ...this.options, ...options };
|
|
220
|
+
// Invalidate cache when configuration changes
|
|
221
|
+
this._cachedTemplate = undefined;
|
|
219
222
|
return this;
|
|
220
223
|
}
|
|
221
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Gets the cached template tree or computes it.
|
|
227
|
+
* Uses two-level caching: instance cache → global cache → compute.
|
|
228
|
+
*
|
|
229
|
+
* @returns A Promise resolving to the template AST tree
|
|
230
|
+
*/
|
|
231
|
+
private async getTemplate(): Promise<J> {
|
|
232
|
+
// Level 1: Instance cache (fastest path)
|
|
233
|
+
if (this._cachedTemplate) {
|
|
234
|
+
return this._cachedTemplate;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Generate cache key for global lookup
|
|
238
|
+
const contextStatements = this.options.context || this.options.imports || [];
|
|
239
|
+
const paramNames = this.parameters.map((p, i) => `param${i}`).join(',');
|
|
240
|
+
const cacheKey = generateCacheKey(
|
|
241
|
+
this.templateParts,
|
|
242
|
+
paramNames,
|
|
243
|
+
contextStatements,
|
|
244
|
+
this.options.dependencies || {}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Level 2: Global cache (fast path - shared with Pattern)
|
|
248
|
+
const cached = globalAstCache.get(cacheKey);
|
|
249
|
+
if (cached) {
|
|
250
|
+
this._cachedTemplate = cached;
|
|
251
|
+
return cached;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Level 3: Compute via TemplateEngine (slow path)
|
|
255
|
+
const result = await TemplateEngine.getTemplateTree(
|
|
256
|
+
this.templateParts,
|
|
257
|
+
this.parameters,
|
|
258
|
+
contextStatements,
|
|
259
|
+
this.options.dependencies || {}
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Cache in both levels
|
|
263
|
+
globalAstCache.set(cacheKey, result);
|
|
264
|
+
this._cachedTemplate = result;
|
|
265
|
+
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
|
|
222
269
|
/**
|
|
223
270
|
* Applies this template and returns the resulting tree.
|
|
224
271
|
*
|
|
@@ -263,8 +310,15 @@ export class Template {
|
|
|
263
310
|
}
|
|
264
311
|
}
|
|
265
312
|
|
|
313
|
+
// Get the cached template tree (uses two-level caching)
|
|
314
|
+
const templateTree = await this.getTemplate();
|
|
315
|
+
|
|
266
316
|
// Prefer 'context' over deprecated 'imports'
|
|
267
317
|
const contextStatements = this.options.context || this.options.imports || [];
|
|
318
|
+
|
|
319
|
+
// Apply template with value substitution using TemplateEngine
|
|
320
|
+
// Note: TemplateEngine.applyTemplate will call getTemplateTree again,
|
|
321
|
+
// but that's okay because it hits the templateCache which is fast
|
|
268
322
|
return TemplateEngine.applyTemplate(
|
|
269
323
|
this.templateParts,
|
|
270
324
|
this.parameters,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import {Cursor, Tree} from '../..';
|
|
17
17
|
import {J, Type} from '../../java';
|
|
18
|
-
import type {
|
|
18
|
+
import type {MatchResult, Pattern} from "./pattern";
|
|
19
19
|
import type {Template} from "./template";
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -100,14 +100,14 @@ export interface CaptureOptions<T = any> {
|
|
|
100
100
|
* but does NOT enforce any runtime constraints on what the capture will match.
|
|
101
101
|
*
|
|
102
102
|
* **Pattern Matching Behavior:**
|
|
103
|
-
* - A bare `pattern`${capture(
|
|
104
|
-
* - Pattern structure determines matching: `pattern`foo(${capture(
|
|
103
|
+
* - A bare `pattern`${capture()}`` will structurally match ANY expression
|
|
104
|
+
* - Pattern structure determines matching: `pattern`foo(${capture()})`` only matches `foo()` calls with one arg
|
|
105
105
|
* - Use structural patterns to narrow matching scope before applying semantic validation
|
|
106
106
|
*
|
|
107
107
|
* **Variadic Captures:**
|
|
108
108
|
* Use `{ variadic: true }` to match zero or more nodes in a sequence:
|
|
109
109
|
* ```typescript
|
|
110
|
-
* const args = capture(
|
|
110
|
+
* const args = capture({ variadic: true });
|
|
111
111
|
* pattern`foo(${args})` // Matches: foo(), foo(a), foo(a, b, c)
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
@@ -168,8 +168,9 @@ export interface Capture<T = any> {
|
|
|
168
168
|
*
|
|
169
169
|
* @example
|
|
170
170
|
* // Variadic any - match zero or more without capturing
|
|
171
|
+
* const first = any();
|
|
171
172
|
* const rest = any({ variadic: true });
|
|
172
|
-
* const pat = pattern`bar(${
|
|
173
|
+
* const pat = pattern`bar(${first}, ${rest})`
|
|
173
174
|
*
|
|
174
175
|
* @example
|
|
175
176
|
* // With constraints - validate but don't capture
|
|
@@ -279,6 +280,18 @@ export interface PatternOptions {
|
|
|
279
280
|
*/
|
|
280
281
|
export type TemplateParameter = Capture | any | TemplateParam | Tree | Tree[] | string | number | boolean;
|
|
281
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Parameter specification for template generation (internal).
|
|
285
|
+
* Represents a placeholder in a template that will be replaced with a parameter value.
|
|
286
|
+
* This is the internal wrapper used by the template engine.
|
|
287
|
+
*/
|
|
288
|
+
export interface Parameter {
|
|
289
|
+
/**
|
|
290
|
+
* The value to substitute into the template.
|
|
291
|
+
*/
|
|
292
|
+
value: any;
|
|
293
|
+
}
|
|
294
|
+
|
|
282
295
|
/**
|
|
283
296
|
* Configuration options for templates.
|
|
284
297
|
*/
|
|
@@ -347,18 +360,21 @@ export interface RewriteRule {
|
|
|
347
360
|
*
|
|
348
361
|
* @example
|
|
349
362
|
* ```typescript
|
|
350
|
-
* const rule1 = rewrite(() =>
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
* }
|
|
363
|
+
* const rule1 = rewrite(() => {
|
|
364
|
+
* const { a, b } = { a: capture(), b: capture() };
|
|
365
|
+
* return {
|
|
366
|
+
* before: pattern`${a} + ${b}`,
|
|
367
|
+
* after: template`${b} + ${a}`
|
|
368
|
+
* };
|
|
369
|
+
* });
|
|
354
370
|
*
|
|
355
371
|
* const rule2 = rewrite(() => ({
|
|
356
372
|
* before: pattern`${capture('x')} + 1`,
|
|
357
|
-
* after: template`${capture('x')}
|
|
373
|
+
* after: template`${capture('x')}++`
|
|
358
374
|
* }));
|
|
359
375
|
*
|
|
360
376
|
* const combined = rule1.andThen(rule2);
|
|
361
|
-
* // Will first swap operands, then if result matches "x + 1", change to "x
|
|
377
|
+
* // Will first swap operands, then if result matches "x + 1", change to "x++"
|
|
362
378
|
* ```
|
|
363
379
|
*/
|
|
364
380
|
andThen(next: RewriteRule): RewriteRule;
|