@openrewrite/rewrite 8.92.17 → 8.92.18
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/README.md +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/java/formatting-utils.d.ts +2 -1
- package/dist/java/formatting-utils.d.ts.map +1 -1
- package/dist/java/formatting-utils.js +4 -3
- package/dist/java/formatting-utils.js.map +1 -1
- package/dist/java/rpc.d.ts.map +1 -1
- package/dist/java/rpc.js +2 -0
- package/dist/java/rpc.js.map +1 -1
- package/dist/java/tree.d.ts +8 -0
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +11 -1
- package/dist/java/tree.js.map +1 -1
- package/dist/java/type.d.ts +7 -1
- package/dist/java/type.d.ts.map +1 -1
- package/dist/java/type.js +16 -1
- package/dist/java/type.js.map +1 -1
- package/dist/javascript/add-import.d.ts +27 -21
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +303 -143
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts.map +1 -1
- package/dist/javascript/amd.js +25 -10
- package/dist/javascript/amd.js.map +1 -1
- package/dist/javascript/autodetect.js +36 -45
- package/dist/javascript/autodetect.js.map +1 -1
- package/dist/javascript/binding.d.ts +6 -4
- package/dist/javascript/binding.d.ts.map +1 -1
- package/dist/javascript/binding.js +54 -18
- package/dist/javascript/binding.js.map +1 -1
- package/dist/javascript/comparator.d.ts +22 -5
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +116 -38
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts +2 -0
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +11 -1
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/index.d.ts +4 -2
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +10 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/markers.d.ts.map +1 -1
- package/dist/javascript/markers.js +64 -0
- package/dist/javascript/markers.js.map +1 -1
- package/dist/javascript/method-matcher.d.ts +13 -2
- package/dist/javascript/method-matcher.d.ts.map +1 -1
- package/dist/javascript/method-matcher.js +32 -19
- package/dist/javascript/method-matcher.js.map +1 -1
- package/dist/javascript/package-name.d.ts +6 -0
- package/dist/javascript/package-name.d.ts.map +1 -0
- package/dist/javascript/package-name.js +47 -0
- package/dist/javascript/package-name.js.map +1 -0
- package/dist/javascript/preconditions.d.ts +4 -1
- package/dist/javascript/preconditions.d.ts.map +1 -1
- package/dist/javascript/preconditions.js +7 -4
- package/dist/javascript/preconditions.js.map +1 -1
- package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
- package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
- package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
- package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
- package/dist/javascript/recipes/index.d.ts +1 -0
- package/dist/javascript/recipes/index.d.ts.map +1 -1
- package/dist/javascript/recipes/index.js +1 -0
- package/dist/javascript/recipes/index.js.map +1 -1
- package/dist/javascript/scope.d.ts +60 -16
- package/dist/javascript/scope.d.ts.map +1 -1
- package/dist/javascript/scope.js +596 -131
- package/dist/javascript/scope.js.map +1 -1
- package/dist/javascript/search/uses-method.d.ts +1 -1
- package/dist/javascript/search/uses-method.d.ts.map +1 -1
- package/dist/javascript/search/uses-method.js +2 -2
- package/dist/javascript/search/uses-method.js.map +1 -1
- package/dist/javascript/search/uses-type.d.ts +14 -1
- package/dist/javascript/search/uses-type.d.ts.map +1 -1
- package/dist/javascript/search/uses-type.js +24 -9
- package/dist/javascript/search/uses-type.js.map +1 -1
- package/dist/javascript/templating/bindings.d.ts +1 -1
- package/dist/javascript/templating/bindings.d.ts.map +1 -1
- package/dist/javascript/templating/bindings.js +10 -50
- package/dist/javascript/templating/bindings.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +8 -11
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +12 -110
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +34 -24
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +200 -112
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +11 -5
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +86 -20
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +126 -60
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/precedence.d.ts +3 -1
- package/dist/javascript/templating/precedence.d.ts.map +1 -1
- package/dist/javascript/templating/precedence.js +28 -8
- package/dist/javascript/templating/precedence.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +7 -6
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +31 -28
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +6 -3
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +10 -1
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +22 -3
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/tree-debug.d.ts +2 -0
- package/dist/javascript/tree-debug.d.ts.map +1 -1
- package/dist/javascript/tree-debug.js +6 -0
- package/dist/javascript/tree-debug.js.map +1 -1
- package/dist/javascript/tsconfig.d.ts.map +1 -1
- package/dist/javascript/tsconfig.js +7 -17
- package/dist/javascript/tsconfig.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +34 -16
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +253 -249
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/javascript/type-report-cli.d.ts +7 -0
- package/dist/javascript/type-report-cli.d.ts.map +1 -0
- package/dist/javascript/type-report-cli.js +129 -0
- package/dist/javascript/type-report-cli.js.map +1 -0
- package/dist/javascript/type-report.d.ts +61 -0
- package/dist/javascript/type-report.d.ts.map +1 -0
- package/dist/javascript/type-report.js +488 -0
- package/dist/javascript/type-report.js.map +1 -0
- package/dist/markers.d.ts +4 -0
- package/dist/markers.d.ts.map +1 -1
- package/dist/markers.js +8 -0
- package/dist/markers.js.map +1 -1
- package/dist/recipe.d.ts +5 -0
- package/dist/recipe.d.ts.map +1 -1
- package/dist/recipe.js +5 -0
- package/dist/recipe.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/queue.d.ts +16 -0
- package/dist/rpc/queue.d.ts.map +1 -1
- package/dist/rpc/queue.js +27 -9
- package/dist/rpc/queue.js.map +1 -1
- package/dist/rpc/request/prepare-recipe.d.ts +3 -1
- package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
- package/dist/rpc/request/prepare-recipe.js +13 -6
- package/dist/rpc/request/prepare-recipe.js.map +1 -1
- package/dist/search/is-source-file.d.ts +1 -0
- package/dist/search/is-source-file.d.ts.map +1 -1
- package/dist/search/is-source-file.js +7 -2
- package/dist/search/is-source-file.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +5 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +14 -3
- package/dist/test/rewrite-test.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +3 -1
- package/src/java/formatting-utils.ts +5 -4
- package/src/java/rpc.ts +2 -0
- package/src/java/tree.ts +15 -0
- package/src/java/type.ts +19 -2
- package/src/javascript/add-import.ts +338 -152
- package/src/javascript/amd.ts +28 -12
- package/src/javascript/autodetect.ts +32 -41
- package/src/javascript/binding.ts +64 -22
- package/src/javascript/comparator.ts +119 -43
- package/src/javascript/format/format.ts +9 -0
- package/src/javascript/index.ts +6 -3
- package/src/javascript/markers.ts +43 -1
- package/src/javascript/method-matcher.ts +34 -22
- package/src/javascript/package-name.ts +45 -0
- package/src/javascript/preconditions.ts +7 -4
- package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
- package/src/javascript/recipes/index.ts +1 -0
- package/src/javascript/scope.ts +705 -169
- package/src/javascript/search/uses-method.ts +2 -2
- package/src/javascript/search/uses-type.ts +30 -9
- package/src/javascript/templating/bindings.ts +13 -38
- package/src/javascript/templating/comparator.ts +16 -127
- package/src/javascript/templating/engine.ts +229 -127
- package/src/javascript/templating/pattern.ts +107 -28
- package/src/javascript/templating/placeholder-replacement.ts +154 -62
- package/src/javascript/templating/precedence.ts +33 -11
- package/src/javascript/templating/template.ts +35 -30
- package/src/javascript/templating/types.ts +6 -3
- package/src/javascript/templating/utils.ts +24 -2
- package/src/javascript/tree-debug.ts +9 -0
- package/src/javascript/tsconfig.ts +7 -17
- package/src/javascript/type-mapping.ts +258 -252
- package/src/javascript/type-report-cli.ts +96 -0
- package/src/javascript/type-report.ts +575 -0
- package/src/markers.ts +8 -0
- package/src/recipe.ts +6 -0
- package/src/rpc/queue.ts +44 -5
- package/src/rpc/request/prepare-recipe.ts +16 -6
- package/src/search/is-source-file.ts +8 -3
- package/src/test/rewrite-test.ts +14 -3
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import {Cursor} from '../..';
|
|
17
|
-
import {J} from '../../java';
|
|
17
|
+
import {isIdentifier, J} from '../../java';
|
|
18
18
|
import {
|
|
19
19
|
Any,
|
|
20
20
|
Capture,
|
|
@@ -28,10 +28,20 @@ import {
|
|
|
28
28
|
} from './types';
|
|
29
29
|
import {CAPTURE_CAPTURING_SYMBOL, CAPTURE_NAME_SYMBOL, CaptureImpl, RAW_CODE_SYMBOL, RawCode} from './capture';
|
|
30
30
|
import {DebugPatternMatchingComparator, MatcherCallbacks, MatcherState, PatternMatchingComparator} from './comparator';
|
|
31
|
-
import {
|
|
32
|
-
|
|
31
|
+
import {
|
|
32
|
+
CaptureMarker,
|
|
33
|
+
CaptureStorageValue,
|
|
34
|
+
generateCacheKey,
|
|
35
|
+
globalAstCache,
|
|
36
|
+
PATTERN_PREFIXES_SYMBOL,
|
|
37
|
+
PlaceholderUtils,
|
|
38
|
+
WRAPPERS_MAP_SYMBOL
|
|
39
|
+
} from './utils';
|
|
40
|
+
import {walk} from '../scope';
|
|
41
|
+
import {opensWithBrace, TemplateEngine} from './engine';
|
|
33
42
|
import {TreePrinters} from '../../print';
|
|
34
43
|
import {JS} from '../index';
|
|
44
|
+
import {JavaScriptSemanticComparatorVisitor} from '../comparator';
|
|
35
45
|
|
|
36
46
|
|
|
37
47
|
/**
|
|
@@ -130,7 +140,8 @@ export class PatternBuilder {
|
|
|
130
140
|
*/
|
|
131
141
|
export class Pattern {
|
|
132
142
|
private _options: PatternOptions = {};
|
|
133
|
-
|
|
143
|
+
/** The pattern tree, keyed by whether its code was parsed as an expression. */
|
|
144
|
+
private _cachedAstPatterns = new Map<boolean, J>();
|
|
134
145
|
private static nextPatternId = 1;
|
|
135
146
|
private readonly patternId: number;
|
|
136
147
|
private readonly unnamedCaptureMapping = new Map<string, string>();
|
|
@@ -202,23 +213,25 @@ export class Pattern {
|
|
|
202
213
|
configure(options: PatternOptions): Pattern {
|
|
203
214
|
this._options = { ...this._options, ...options };
|
|
204
215
|
// Invalidate cache when configuration changes
|
|
205
|
-
this.
|
|
216
|
+
this._cachedAstPatterns.clear();
|
|
206
217
|
return this;
|
|
207
218
|
}
|
|
208
219
|
|
|
209
220
|
/**
|
|
210
|
-
* Gets the AST pattern for this pattern, using
|
|
221
|
+
* Gets the AST pattern for this pattern, using three-level caching:
|
|
211
222
|
* 1. Instance-level cache (fastest - this pattern instance)
|
|
212
223
|
* 2. Global LRU cache (fast - shared across pattern instances with same code)
|
|
213
224
|
* 3. Compute via TemplateProcessor (slow - parse and process)
|
|
214
225
|
*
|
|
226
|
+
* @param expression Whether to parse the code as an expression, with statements as the fallback
|
|
215
227
|
* @returns The cached or newly computed pattern AST
|
|
216
228
|
* @internal
|
|
217
229
|
*/
|
|
218
|
-
async getAstPattern(): Promise<J> {
|
|
230
|
+
async getAstPattern(expression: boolean = false): Promise<J> {
|
|
219
231
|
// Level 1: Instance cache (fastest path)
|
|
220
|
-
|
|
221
|
-
|
|
232
|
+
const instanceCached = this._cachedAstPatterns.get(expression);
|
|
233
|
+
if (instanceCached) {
|
|
234
|
+
return instanceCached;
|
|
222
235
|
}
|
|
223
236
|
|
|
224
237
|
// Generate cache key for global lookup
|
|
@@ -229,7 +242,7 @@ export class Pattern {
|
|
|
229
242
|
return `raw:${(c as RawCode).code}`;
|
|
230
243
|
}
|
|
231
244
|
return c.getName();
|
|
232
|
-
}).join(',');
|
|
245
|
+
}).join(',') + (expression ? '::expression' : '');
|
|
233
246
|
const cacheKey = generateCacheKey(
|
|
234
247
|
this.templateParts,
|
|
235
248
|
capturesKey,
|
|
@@ -244,20 +257,31 @@ export class Pattern {
|
|
|
244
257
|
let tree = globalAstCache.get(cacheKey);
|
|
245
258
|
if (!tree) {
|
|
246
259
|
// Level 3: Compute via TemplateEngine (slow path)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
260
|
+
try {
|
|
261
|
+
tree = await TemplateEngine.getPatternTree(
|
|
262
|
+
this.templateParts,
|
|
263
|
+
this.captures,
|
|
264
|
+
contextStatements,
|
|
265
|
+
this._options.dependencies || {},
|
|
266
|
+
this._options.types,
|
|
267
|
+
expression
|
|
268
|
+
);
|
|
269
|
+
} catch (e) {
|
|
270
|
+
// A block matches only a block, which the statement parse is for
|
|
271
|
+
if (!expression) {
|
|
272
|
+
throw e;
|
|
273
|
+
}
|
|
274
|
+
const statementPattern = await this.getAstPattern(false);
|
|
275
|
+
this._cachedAstPatterns.set(expression, statementPattern);
|
|
276
|
+
return statementPattern;
|
|
277
|
+
}
|
|
254
278
|
globalAstCache.set(cacheKey, tree);
|
|
255
279
|
}
|
|
256
280
|
|
|
257
281
|
// The key names captures but says nothing about their constraints, so two patterns of the
|
|
258
282
|
// same shape share an entry; markers are attached per instance to keep them apart.
|
|
259
283
|
const result = await TemplateEngine.attachCaptureMarkers(tree, this.captures);
|
|
260
|
-
this.
|
|
284
|
+
this._cachedAstPatterns.set(expression, result);
|
|
261
285
|
|
|
262
286
|
return result;
|
|
263
287
|
}
|
|
@@ -310,7 +334,7 @@ export class Pattern {
|
|
|
310
334
|
}
|
|
311
335
|
// Create MatchResult with unified storage
|
|
312
336
|
const storage = (matcher as any).storage;
|
|
313
|
-
return new MatchResult(new Map(storage));
|
|
337
|
+
return new MatchResult(new Map(storage), () => matcher.patternPrefixes());
|
|
314
338
|
}
|
|
315
339
|
|
|
316
340
|
/**
|
|
@@ -556,7 +580,7 @@ export class Pattern {
|
|
|
556
580
|
if (success) {
|
|
557
581
|
// Match succeeded - return MatchResult with debug info
|
|
558
582
|
const storage = (matcher as any).storage;
|
|
559
|
-
const matchResult = new MatchResult(new Map(storage));
|
|
583
|
+
const matchResult = new MatchResult(new Map(storage), () => matcher.patternPrefixes());
|
|
560
584
|
return {
|
|
561
585
|
matched: true,
|
|
562
586
|
result: matchResult,
|
|
@@ -600,10 +624,13 @@ export class Pattern {
|
|
|
600
624
|
*/
|
|
601
625
|
export class MatchResult implements IMatchResult {
|
|
602
626
|
constructor(
|
|
603
|
-
private readonly storage: Map<string, CaptureStorageValue> = new Map()
|
|
627
|
+
private readonly storage: Map<string, CaptureStorageValue> = new Map(),
|
|
628
|
+
private readonly computePatternPrefixes: () => Map<string, J.Space> = () => new Map()
|
|
604
629
|
) {
|
|
605
630
|
}
|
|
606
631
|
|
|
632
|
+
private patternPrefixes?: Map<string, J.Space>;
|
|
633
|
+
|
|
607
634
|
// Overload: get with Capture returns value
|
|
608
635
|
get<T>(capture: Capture<T>): T | undefined;
|
|
609
636
|
// Overload: get with string returns value
|
|
@@ -655,6 +682,11 @@ export class MatchResult implements IMatchResult {
|
|
|
655
682
|
return value as J;
|
|
656
683
|
}
|
|
657
684
|
|
|
685
|
+
/** @internal */
|
|
686
|
+
[PATTERN_PREFIXES_SYMBOL](): Map<string, J.Space> {
|
|
687
|
+
return this.patternPrefixes ??= this.computePatternPrefixes();
|
|
688
|
+
}
|
|
689
|
+
|
|
658
690
|
/**
|
|
659
691
|
* Internal method to get wrappers (used by template expansion).
|
|
660
692
|
* Returns both scalar and variadic wrappers.
|
|
@@ -705,9 +737,13 @@ class Matcher {
|
|
|
705
737
|
) {
|
|
706
738
|
this.cursor = cursor;
|
|
707
739
|
this.debugOptions = debugOptions ?? {};
|
|
740
|
+
this.lenientTypeMatching = pattern.options.lenientTypeMatching ?? true;
|
|
741
|
+
this.sameCodeComparator = new JavaScriptSemanticComparatorVisitor(this.lenientTypeMatching);
|
|
708
742
|
}
|
|
709
743
|
|
|
710
744
|
private readonly cursor: Cursor;
|
|
745
|
+
private readonly lenientTypeMatching: boolean;
|
|
746
|
+
private readonly sameCodeComparator: JavaScriptSemanticComparatorVisitor;
|
|
711
747
|
|
|
712
748
|
/**
|
|
713
749
|
* Checks if the pattern matches the AST node.
|
|
@@ -716,12 +752,34 @@ class Matcher {
|
|
|
716
752
|
*/
|
|
717
753
|
async matches(): Promise<boolean> {
|
|
718
754
|
if (!this.patternAst) {
|
|
719
|
-
this.patternAst = await this.pattern.getAstPattern(
|
|
755
|
+
this.patternAst = await this.pattern.getAstPattern(
|
|
756
|
+
opensWithBrace(this.pattern.templateParts) && this.ast.kind !== J.Kind.Block);
|
|
720
757
|
}
|
|
721
758
|
|
|
722
759
|
return this.matchNode(this.patternAst, this.ast);
|
|
723
760
|
}
|
|
724
761
|
|
|
762
|
+
/**
|
|
763
|
+
* The prefix the pattern writes before each capture, by capture name. A capture of the whole
|
|
764
|
+
* match has none, since the prefix of what it holds lies outside the match.
|
|
765
|
+
*/
|
|
766
|
+
patternPrefixes(): Map<string, J.Space> {
|
|
767
|
+
const prefixes = new Map<string, J.Space>();
|
|
768
|
+
walk(this.patternAst, node => {
|
|
769
|
+
const capture = isIdentifier(node) ? PlaceholderUtils.parseCapture(node.simpleName) : null;
|
|
770
|
+
if (capture && !prefixes.has(capture.name) && !this.capturesWholeMatch(capture.name)) {
|
|
771
|
+
prefixes.set(capture.name, (node as J.Identifier).prefix);
|
|
772
|
+
}
|
|
773
|
+
return true;
|
|
774
|
+
});
|
|
775
|
+
return prefixes;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
private capturesWholeMatch(name: string): boolean {
|
|
779
|
+
const bound = this.storage.get(name);
|
|
780
|
+
return bound !== undefined && !Array.isArray(bound) && this.extractElements(bound) === this.ast;
|
|
781
|
+
}
|
|
782
|
+
|
|
725
783
|
/**
|
|
726
784
|
* Gets all captured nodes (projected view: extracts elements from wrappers).
|
|
727
785
|
*
|
|
@@ -850,8 +908,6 @@ class Matcher {
|
|
|
850
908
|
// - Kind checking
|
|
851
909
|
// - Deep structural comparison
|
|
852
910
|
// This centralizes all matching logic in one place
|
|
853
|
-
const lenientTypeMatching = this.pattern.options.lenientTypeMatching ?? true;
|
|
854
|
-
|
|
855
911
|
// Factory pattern: instantiate debug or production comparator
|
|
856
912
|
// Zero cost in production - DebugPatternMatchingComparator is never instantiated
|
|
857
913
|
const matcherCallbacks: MatcherCallbacks = {
|
|
@@ -872,8 +928,8 @@ class Matcher {
|
|
|
872
928
|
};
|
|
873
929
|
|
|
874
930
|
const comparator = this.debugOptions.enabled
|
|
875
|
-
? new DebugPatternMatchingComparator(matcherCallbacks, lenientTypeMatching)
|
|
876
|
-
: new PatternMatchingComparator(matcherCallbacks, lenientTypeMatching);
|
|
931
|
+
? new DebugPatternMatchingComparator(matcherCallbacks, this.lenientTypeMatching)
|
|
932
|
+
: new PatternMatchingComparator(matcherCallbacks, this.lenientTypeMatching);
|
|
877
933
|
// Pass cursors to allow constraints to navigate to root
|
|
878
934
|
// Pattern cursor is undefined (pattern is the root), target cursor is provided by user
|
|
879
935
|
const result = await comparator.compare(pattern, target, undefined, this.cursor);
|
|
@@ -942,7 +998,7 @@ class Matcher {
|
|
|
942
998
|
* @param wrapper Optional wrapper containing the target (for preserving markers)
|
|
943
999
|
* @returns true if the capture is successful, false otherwise
|
|
944
1000
|
*/
|
|
945
|
-
private handleCapture(capture: CaptureMarker, target: J, wrapper?: J.RightPadded<J>): boolean {
|
|
1001
|
+
private async handleCapture(capture: CaptureMarker, target: J, wrapper?: J.RightPadded<J>): Promise<boolean> {
|
|
946
1002
|
const captureName = capture.captureName;
|
|
947
1003
|
|
|
948
1004
|
if (!captureName) {
|
|
@@ -960,6 +1016,10 @@ class Matcher {
|
|
|
960
1016
|
// Only store the binding if this is a capturing placeholder
|
|
961
1017
|
const capturing = (captureObj as any)?.[CAPTURE_CAPTURING_SYMBOL] ?? true;
|
|
962
1018
|
if (capturing) {
|
|
1019
|
+
const bound = this.storage.get(captureName);
|
|
1020
|
+
if (bound !== undefined) {
|
|
1021
|
+
return this.bindsSameCode(bound, [target]);
|
|
1022
|
+
}
|
|
963
1023
|
// Store wrapper if available (preserves markers), otherwise store element
|
|
964
1024
|
this.storage.set(captureName, wrapper ?? target);
|
|
965
1025
|
}
|
|
@@ -967,6 +1027,21 @@ class Matcher {
|
|
|
967
1027
|
return true;
|
|
968
1028
|
}
|
|
969
1029
|
|
|
1030
|
+
/** A capture named twice in a pattern binds the same code at both places. */
|
|
1031
|
+
private async bindsSameCode(bound: CaptureStorageValue, targets: J[]): Promise<boolean> {
|
|
1032
|
+
const boundElements = (Array.isArray(bound) ? bound : [bound]).map(b =>
|
|
1033
|
+
b.kind === J.Kind.RightPadded ? (b as J.RightPadded<J>).element : b as J);
|
|
1034
|
+
if (boundElements.length !== targets.length) {
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
for (let i = 0; i < targets.length; i++) {
|
|
1038
|
+
if (!await this.sameCodeComparator.compare(boundElements[i], targets[i])) {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return true;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
970
1045
|
/**
|
|
971
1046
|
* Handles a variadic capture placeholder.
|
|
972
1047
|
*
|
|
@@ -975,7 +1050,7 @@ class Matcher {
|
|
|
975
1050
|
* @param wrappers Optional wrappers to preserve markers
|
|
976
1051
|
* @returns true if the capture is successful, false otherwise
|
|
977
1052
|
*/
|
|
978
|
-
private handleVariadicCapture(capture: CaptureMarker, targets: J[], wrappers?: J.RightPadded<J>[]): boolean {
|
|
1053
|
+
private async handleVariadicCapture(capture: CaptureMarker, targets: J[], wrappers?: J.RightPadded<J>[]): Promise<boolean> {
|
|
979
1054
|
const captureName = capture.captureName;
|
|
980
1055
|
|
|
981
1056
|
if (!captureName) {
|
|
@@ -993,6 +1068,10 @@ class Matcher {
|
|
|
993
1068
|
// Only store the binding if this is a capturing placeholder
|
|
994
1069
|
const capturing = (captureObj as any)?.[CAPTURE_CAPTURING_SYMBOL] ?? true;
|
|
995
1070
|
if (capturing) {
|
|
1071
|
+
const bound = this.storage.get(captureName);
|
|
1072
|
+
if (bound !== undefined) {
|
|
1073
|
+
return this.bindsSameCode(bound, targets);
|
|
1074
|
+
}
|
|
996
1075
|
// Store the richest representation: wrappers if available, otherwise elements
|
|
997
1076
|
if (wrappers && wrappers.length > 0) {
|
|
998
1077
|
this.storage.set(captureName, wrappers);
|
|
@@ -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 {Cursor, isTree, Markers} from '../..';
|
|
17
|
-
import {J} from '../../java';
|
|
16
|
+
import {Cursor, isTree, Marker, Markers} from '../..';
|
|
17
|
+
import {Expression, J} from '../../java';
|
|
18
18
|
import {JS} from '..';
|
|
19
19
|
import {JavaScriptVisitor} from '../visitor';
|
|
20
20
|
import {create as produce} from 'mutative';
|
|
@@ -22,15 +22,37 @@ import {PlaceholderUtils} from './utils';
|
|
|
22
22
|
import {CaptureImpl, TemplateParamImpl, CaptureValue, CAPTURE_NAME_SYMBOL} from './capture';
|
|
23
23
|
import {enclosingTree, maybeParenthesize} from './precedence';
|
|
24
24
|
import {Parameter} from './types';
|
|
25
|
+
import {isExpression} from '../parser-utils';
|
|
26
|
+
import {randomId} from '../../uuid';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Marks a value the template substituted, whose layout is the source's rather than the template's.
|
|
30
|
+
* `ownPrefix` says whether the whitespace before it is the source's too.
|
|
31
|
+
*/
|
|
32
|
+
export class SubstitutedValue implements Marker {
|
|
33
|
+
static readonly KIND = 'org.openrewrite.javascript.templating.SubstitutedValue';
|
|
34
|
+
readonly kind = SubstitutedValue.KIND;
|
|
35
|
+
readonly id = randomId();
|
|
36
|
+
|
|
37
|
+
constructor(readonly ownPrefix: boolean) {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Chooses a substituted value's prefix, and says whether it is the source's own. */
|
|
42
|
+
type MergePrefix = (sourcePrefix: J.Space, templatePrefix: J.Space) => [J.Space, boolean];
|
|
25
43
|
|
|
26
44
|
/**
|
|
27
45
|
* Visitor that replaces placeholder nodes with actual parameter values.
|
|
28
46
|
*/
|
|
29
47
|
export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
48
|
+
/** Whether any value was substituted, and so marked as a {@link SubstitutedValue}. */
|
|
49
|
+
substituted = false;
|
|
50
|
+
|
|
30
51
|
constructor(
|
|
31
52
|
private readonly substitutions: Map<string, Parameter>,
|
|
32
53
|
private readonly values: Pick<Map<string, J | J[]>, 'get'> = new Map(),
|
|
33
|
-
private readonly wrappersMap: Pick<Map<string, J.RightPadded<J> | J.RightPadded<J>[]>, 'get'> = new Map()
|
|
54
|
+
private readonly wrappersMap: Pick<Map<string, J.RightPadded<J> | J.RightPadded<J>[]>, 'get'> = new Map(),
|
|
55
|
+
private readonly patternPrefixes: Pick<Map<string, J.Space>, 'get'> = new Map()
|
|
34
56
|
) {
|
|
35
57
|
super();
|
|
36
58
|
}
|
|
@@ -70,6 +92,71 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
70
92
|
return bindingElement;
|
|
71
93
|
}
|
|
72
94
|
|
|
95
|
+
/** A statement substituted into an expression statement's placeholder is the statement itself. */
|
|
96
|
+
override async visitExpressionStatement(expressionStatement: JS.ExpressionStatement, p: any): Promise<J | undefined> {
|
|
97
|
+
const visited = await super.visitExpressionStatement(expressionStatement, p);
|
|
98
|
+
if (visited?.kind !== JS.Kind.ExpressionStatement) {
|
|
99
|
+
return visited;
|
|
100
|
+
}
|
|
101
|
+
const statement = visited as JS.ExpressionStatement;
|
|
102
|
+
const expression: J = statement.expression;
|
|
103
|
+
if (isExpression(expression)) {
|
|
104
|
+
return statement;
|
|
105
|
+
}
|
|
106
|
+
return {...expression, prefix: this.concatPrefix(statement.prefix, expression.prefix)};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A declaration substituted for the name of a declaration is the declaration itself: `(${p})` and
|
|
111
|
+
* `(...${p})` with `p` bound to the parameter `props: Props` give `(props: Props)` and `(...props: Props)`.
|
|
112
|
+
*/
|
|
113
|
+
override async visitVariableDeclarations(variableDeclarations: J.VariableDeclarations, p: any): Promise<J | undefined> {
|
|
114
|
+
const visited = await super.visitVariableDeclarations(variableDeclarations, p);
|
|
115
|
+
if (visited?.kind !== J.Kind.VariableDeclarations) {
|
|
116
|
+
return visited;
|
|
117
|
+
}
|
|
118
|
+
const outer = visited as J.VariableDeclarations;
|
|
119
|
+
if (outer.variables.length !== 1) {
|
|
120
|
+
return outer;
|
|
121
|
+
}
|
|
122
|
+
const variable = outer.variables[0].element;
|
|
123
|
+
const spread = variable.name.kind === JS.Kind.Spread ? variable.name as JS.Spread : undefined;
|
|
124
|
+
const declared: J = spread ? spread.expression : variable.name;
|
|
125
|
+
if (declared.kind !== J.Kind.VariableDeclarations) {
|
|
126
|
+
return outer;
|
|
127
|
+
}
|
|
128
|
+
const inner = declared as J.VariableDeclarations;
|
|
129
|
+
const prefix = this.concatPrefix(this.concatPrefix(outer.prefix, variable.prefix), inner.prefix);
|
|
130
|
+
if (!spread) {
|
|
131
|
+
return {...inner, prefix};
|
|
132
|
+
} else if (inner.variables.length !== 1) {
|
|
133
|
+
return outer;
|
|
134
|
+
}
|
|
135
|
+
const innerVariable = inner.variables[0];
|
|
136
|
+
const spreadDeclaration: J.VariableDeclarations = {
|
|
137
|
+
...inner,
|
|
138
|
+
prefix,
|
|
139
|
+
variables: [{
|
|
140
|
+
...innerVariable,
|
|
141
|
+
element: {...innerVariable.element, name: {...spread, expression: innerVariable.element.name as Expression} as JS.Spread}
|
|
142
|
+
}]
|
|
143
|
+
};
|
|
144
|
+
return spreadDeclaration;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private concatPrefix(outer: J.Space, inner: J.Space): J.Space {
|
|
148
|
+
if (outer.whitespace === '' && outer.comments.length === 0) {
|
|
149
|
+
return inner;
|
|
150
|
+
} else if (outer.comments.length === 0) {
|
|
151
|
+
return {...inner, whitespace: outer.whitespace + inner.whitespace};
|
|
152
|
+
}
|
|
153
|
+
const last = outer.comments[outer.comments.length - 1];
|
|
154
|
+
return {
|
|
155
|
+
...outer,
|
|
156
|
+
comments: [...outer.comments.slice(0, -1), {...last, suffix: last.suffix + inner.whitespace}, ...inner.comments]
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
73
160
|
/**
|
|
74
161
|
* Override visitContainer to handle variadic expansion for containers.
|
|
75
162
|
* This handles J.Container instances anywhere in the AST (method arguments, etc.).
|
|
@@ -105,28 +192,18 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
105
192
|
* array-level access for variadic expansion.
|
|
106
193
|
*/
|
|
107
194
|
override async visitBlock(block: J.Block, p: any): Promise<J | undefined> {
|
|
108
|
-
|
|
109
|
-
const stmtElement = stmt.element;
|
|
110
|
-
// Check if it's an ExpressionStatement containing a placeholder
|
|
111
|
-
if (stmtElement.kind === JS.Kind.ExpressionStatement) {
|
|
112
|
-
const exprStmt = stmtElement as JS.ExpressionStatement;
|
|
113
|
-
return this.isPlaceholder(exprStmt.expression);
|
|
114
|
-
}
|
|
115
|
-
return this.isPlaceholder(stmtElement);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
if (!hasPlaceholder) {
|
|
119
|
-
return super.visitBlock(block, p);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Unwrap function to extract placeholder from ExpressionStatement
|
|
195
|
+
// An object literal's body is a block too, whose placeholders are shorthand properties
|
|
123
196
|
const unwrapStatement = (element: J): J => {
|
|
124
197
|
if (element.kind === JS.Kind.ExpressionStatement) {
|
|
125
198
|
return (element as JS.ExpressionStatement).expression;
|
|
126
199
|
}
|
|
127
|
-
return element;
|
|
200
|
+
return PlaceholderUtils.shorthandPropertyName(element) ?? element;
|
|
128
201
|
};
|
|
129
202
|
|
|
203
|
+
if (!block.statements.some(stmt => this.isPlaceholder(unwrapStatement(stmt.element)))) {
|
|
204
|
+
return super.visitBlock(block, p);
|
|
205
|
+
}
|
|
206
|
+
|
|
130
207
|
const newStatements = await this.expandVariadicElements(block.statements, unwrapStatement, p);
|
|
131
208
|
|
|
132
209
|
return produce(block, draft => {
|
|
@@ -175,10 +252,31 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
175
252
|
};
|
|
176
253
|
}
|
|
177
254
|
|
|
255
|
+
private readonly templatePrefix: MergePrefix = (source, template) => [this.mergePrefix(source, template), false];
|
|
256
|
+
|
|
178
257
|
/** As `mergePrefix`, but a line break the source wrote and the template did not is the source's layout. */
|
|
179
|
-
private
|
|
180
|
-
|
|
181
|
-
|
|
258
|
+
private readonly elementPrefix: MergePrefix = (source, template) =>
|
|
259
|
+
source.whitespace.includes('\n') && !template.whitespace.includes('\n') ?
|
|
260
|
+
[source, true] : this.templatePrefix(source, template);
|
|
261
|
+
|
|
262
|
+
private readonly sourcePrefix: MergePrefix = source => [source, true];
|
|
263
|
+
|
|
264
|
+
/** Whether the template writes the same prefix before capture `name` as the pattern it matched with. */
|
|
265
|
+
private spacedAsInPattern(name: string, placeholder: J): boolean {
|
|
266
|
+
const pattern = this.patternPrefixes.get(name);
|
|
267
|
+
return pattern !== undefined && pattern.comments.length === 0 && placeholder.prefix.comments.length === 0 &&
|
|
268
|
+
pattern.whitespace === placeholder.prefix.whitespace;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** `value` substituted for `placeholder` under the prefix `merge` chooses, marked as a {@link SubstitutedValue}. */
|
|
272
|
+
private substitute(value: J, placeholder: J, merge: MergePrefix): J {
|
|
273
|
+
this.substituted = true;
|
|
274
|
+
const [prefix, ownPrefix] = merge(value.prefix, placeholder.prefix);
|
|
275
|
+
const markers = this.mergeMarkers(value.markers, placeholder.markers);
|
|
276
|
+
return produce(value, draft => {
|
|
277
|
+
draft.prefix = prefix;
|
|
278
|
+
draft.markers = {...markers, markers: [...markers.markers, new SubstitutedValue(ownPrefix)]};
|
|
279
|
+
});
|
|
182
280
|
}
|
|
183
281
|
|
|
184
282
|
/** As `mergePrefix`, for markers: everything the value was matched with, plus the kinds only the slot wrote. */
|
|
@@ -206,6 +304,9 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
206
304
|
ownLayout: boolean = false
|
|
207
305
|
): Promise<J.RightPadded<J>[]> {
|
|
208
306
|
const newElements: J.RightPadded<J>[] = [];
|
|
307
|
+
const merge = ownLayout ? this.elementPrefix : this.templatePrefix;
|
|
308
|
+
// Past the first item, an item's prefix is the source's separator from the one before it
|
|
309
|
+
const keep: MergePrefix = source => [source, ownLayout];
|
|
209
310
|
|
|
210
311
|
for (const wrapped of elements) {
|
|
211
312
|
const element = wrapped.element;
|
|
@@ -276,29 +377,18 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
276
377
|
|
|
277
378
|
if (isWrapper) {
|
|
278
379
|
// Item is a JRightPadded wrapper - use it directly to preserve markers
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
this.mergePrefix(draft.element.prefix, element.prefix);
|
|
286
|
-
}
|
|
287
|
-
// Keep all other wrapper properties (including markers with Semicolon)
|
|
288
|
-
}));
|
|
380
|
+
const wrapper = item as J.RightPadded<J>;
|
|
381
|
+
// Keep all other wrapper properties (including markers with Semicolon)
|
|
382
|
+
newElements.push({
|
|
383
|
+
...wrapper,
|
|
384
|
+
element: this.substitute(wrapper.element, element, i > 0 ? keep : merge)
|
|
385
|
+
});
|
|
289
386
|
} else if (item) {
|
|
290
387
|
// Item is just an element (not a wrapper) - wrap it (backward compatibility)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
itemDraft.prefix = ownLayout ?
|
|
296
|
-
this.mergeElementPrefix(elem.prefix, element.prefix) :
|
|
297
|
-
this.mergePrefix(elem.prefix, element.prefix);
|
|
298
|
-
}
|
|
299
|
-
// For i > 0, prefix is already correct, no changes needed
|
|
300
|
-
});
|
|
301
|
-
}));
|
|
388
|
+
newElements.push({
|
|
389
|
+
...wrapped,
|
|
390
|
+
element: this.substitute(item as J, element, i > 0 ? keep : merge)
|
|
391
|
+
});
|
|
302
392
|
}
|
|
303
393
|
}
|
|
304
394
|
continue; // Skip adding the placeholder itself
|
|
@@ -312,14 +402,18 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
312
402
|
}
|
|
313
403
|
|
|
314
404
|
// Not a placeholder (or expansion failed) - process normally
|
|
315
|
-
const
|
|
405
|
+
const slot = ownLayout && element.kind !== JS.Kind.BindingElement && this.isPlaceholder(element);
|
|
406
|
+
const replacedElement = slot ?
|
|
407
|
+
this.replaceElement(element, merge) :
|
|
408
|
+
await this.visit(element, p);
|
|
316
409
|
if (replacedElement) {
|
|
317
410
|
// Check if the replacement came from a capture with a wrapper (to preserve markers)
|
|
318
411
|
const placeholderNode = unwrapElement(element);
|
|
319
412
|
const placeholderText = this.getPlaceholderText(placeholderNode);
|
|
320
413
|
let wrapperToUse = wrapped;
|
|
321
414
|
|
|
322
|
-
|
|
415
|
+
// A shorthand property keeps its own wrapper, since a captured one belongs to the name alone
|
|
416
|
+
if (placeholderText && this.isPlaceholder(placeholderNode) && element.kind !== JS.Kind.PropertyAssignment) {
|
|
323
417
|
const param = this.substitutions.get(placeholderText);
|
|
324
418
|
if (param) {
|
|
325
419
|
const isCapture = param.value instanceof CaptureImpl ||
|
|
@@ -344,6 +438,13 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
344
438
|
return newElements;
|
|
345
439
|
}
|
|
346
440
|
|
|
441
|
+
/** As `visit` on a placeholder, with `merge` choosing the replacement's prefix. */
|
|
442
|
+
private replaceElement(placeholder: J, merge: MergePrefix): J {
|
|
443
|
+
const replacement = this.replacePlaceholder(placeholder, merge);
|
|
444
|
+
return replacement === placeholder ? placeholder :
|
|
445
|
+
maybeParenthesize(enclosingTree(this.cursor), placeholder.id, replacement, placeholder.markers);
|
|
446
|
+
}
|
|
447
|
+
|
|
347
448
|
/**
|
|
348
449
|
* Checks if a node is a placeholder.
|
|
349
450
|
*
|
|
@@ -369,9 +470,10 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
369
470
|
* Replaces a placeholder node with the actual parameter value.
|
|
370
471
|
*
|
|
371
472
|
* @param placeholder The placeholder node
|
|
473
|
+
* @param merge Chooses the replacement's prefix from its own and the placeholder's
|
|
372
474
|
* @returns The replacement node or the original if not a placeholder
|
|
373
475
|
*/
|
|
374
|
-
private replacePlaceholder(placeholder: J): J {
|
|
476
|
+
private replacePlaceholder(placeholder: J, merge: MergePrefix = this.templatePrefix): J {
|
|
375
477
|
const placeholderText = this.getPlaceholderText(placeholder);
|
|
376
478
|
|
|
377
479
|
if (!placeholderText || !placeholderText.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX)) {
|
|
@@ -395,10 +497,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
395
497
|
// If the property value is already a J node, use it
|
|
396
498
|
if (isTree(propertyValue)) {
|
|
397
499
|
const propValueAsJ = propertyValue as J;
|
|
398
|
-
return
|
|
399
|
-
draft.markers = this.mergeMarkers(propValueAsJ.markers, placeholder.markers);
|
|
400
|
-
draft.prefix = this.mergePrefix(propValueAsJ.prefix, placeholder.prefix);
|
|
401
|
-
});
|
|
500
|
+
return this.substitute(propValueAsJ, placeholder, merge);
|
|
402
501
|
}
|
|
403
502
|
// If it's a primitive value and placeholder is an identifier, update the simpleName
|
|
404
503
|
if (typeof propertyValue === 'string' && placeholder.kind === J.Kind.Identifier) {
|
|
@@ -430,10 +529,8 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
430
529
|
(param.value[CAPTURE_NAME_SYMBOL] || param.value.name);
|
|
431
530
|
const matchedNode = this.values.get(name);
|
|
432
531
|
if (matchedNode && !Array.isArray(matchedNode)) {
|
|
433
|
-
return
|
|
434
|
-
|
|
435
|
-
draft.prefix = this.mergePrefix(matchedNode.prefix, placeholder.prefix);
|
|
436
|
-
});
|
|
532
|
+
return this.substitute(matchedNode, placeholder,
|
|
533
|
+
this.spacedAsInPattern(name, placeholder) ? this.sourcePrefix : merge);
|
|
437
534
|
}
|
|
438
535
|
|
|
439
536
|
// If no match found, return placeholder unchanged
|
|
@@ -447,10 +544,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
447
544
|
if (isRightPadded) {
|
|
448
545
|
// Extract the element from the J.RightPadded wrapper
|
|
449
546
|
const element = param.value.element as J;
|
|
450
|
-
return
|
|
451
|
-
draft.markers = this.mergeMarkers(element.markers, placeholder.markers);
|
|
452
|
-
draft.prefix = this.mergePrefix(element.prefix, placeholder.prefix);
|
|
453
|
-
});
|
|
547
|
+
return this.substitute(element, placeholder, merge);
|
|
454
548
|
}
|
|
455
549
|
|
|
456
550
|
// Check if the parameter value is a J.Container
|
|
@@ -467,10 +561,8 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
467
561
|
// If the parameter value is an AST node, use it directly
|
|
468
562
|
if (isTree(param.value)) {
|
|
469
563
|
// Return the AST node, preserving comments from the source
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
draft.prefix = this.mergePrefix(param.value.prefix, placeholder.prefix);
|
|
473
|
-
});
|
|
564
|
+
const value = param.value as J;
|
|
565
|
+
return this.substitute(value, placeholder, merge);
|
|
474
566
|
}
|
|
475
567
|
|
|
476
568
|
return placeholder;
|