@openrewrite/rewrite 8.67.0-20251104-111121 → 8.67.0-20251104-114312
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/javascript/comparator.d.ts +67 -4
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +523 -2794
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/index.d.ts +1 -1
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +1 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/templating/capture.d.ts +226 -0
- package/dist/javascript/templating/capture.d.ts.map +1 -0
- package/dist/javascript/templating/capture.js +371 -0
- package/dist/javascript/templating/capture.js.map +1 -0
- package/dist/javascript/templating/comparator.d.ts +61 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -0
- package/dist/javascript/templating/comparator.js +393 -0
- package/dist/javascript/templating/comparator.js.map +1 -0
- package/dist/javascript/templating/engine.d.ts +75 -0
- package/dist/javascript/templating/engine.d.ts.map +1 -0
- package/dist/javascript/templating/engine.js +228 -0
- package/dist/javascript/templating/engine.js.map +1 -0
- package/dist/javascript/templating/index.d.ts +6 -0
- package/dist/javascript/templating/index.d.ts.map +1 -0
- package/dist/javascript/templating/index.js +42 -0
- package/dist/javascript/templating/index.js.map +1 -0
- package/dist/javascript/templating/pattern.d.ts +171 -0
- package/dist/javascript/templating/pattern.d.ts.map +1 -0
- package/dist/javascript/templating/pattern.js +681 -0
- package/dist/javascript/templating/pattern.js.map +1 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts +58 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -0
- package/dist/javascript/templating/placeholder-replacement.js +365 -0
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -0
- package/dist/javascript/templating/rewrite.d.ts +39 -0
- package/dist/javascript/templating/rewrite.d.ts.map +1 -0
- package/dist/javascript/templating/rewrite.js +81 -0
- package/dist/javascript/templating/rewrite.js.map +1 -0
- package/dist/javascript/templating/template.d.ts +204 -0
- package/dist/javascript/templating/template.d.ts.map +1 -0
- package/dist/javascript/templating/template.js +293 -0
- package/dist/javascript/templating/template.js.map +1 -0
- package/dist/javascript/templating/types.d.ts +263 -0
- package/dist/javascript/templating/types.d.ts.map +1 -0
- package/dist/javascript/templating/types.js +3 -0
- package/dist/javascript/templating/types.js.map +1 -0
- package/dist/javascript/templating/utils.d.ts +118 -0
- package/dist/javascript/templating/utils.d.ts.map +1 -0
- package/dist/javascript/templating/utils.js +253 -0
- package/dist/javascript/templating/utils.js.map +1 -0
- package/dist/version.txt +1 -1
- package/package.json +2 -1
- package/src/javascript/comparator.ts +554 -3323
- package/src/javascript/index.ts +1 -1
- package/src/javascript/templating/capture.ts +503 -0
- package/src/javascript/templating/comparator.ts +430 -0
- package/src/javascript/templating/engine.ts +252 -0
- package/src/javascript/templating/index.ts +60 -0
- package/src/javascript/templating/pattern.ts +727 -0
- package/src/javascript/templating/placeholder-replacement.ts +372 -0
- package/src/javascript/templating/rewrite.ts +95 -0
- package/src/javascript/templating/template.ts +326 -0
- package/src/javascript/templating/types.ts +300 -0
- package/src/javascript/templating/utils.ts +284 -0
- package/dist/javascript/templating.d.ts +0 -265
- package/dist/javascript/templating.d.ts.map +0 -1
- package/dist/javascript/templating.js +0 -1027
- package/dist/javascript/templating.js.map +0 -1
- package/src/javascript/templating.ts +0 -1226
|
@@ -1,1027 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.Template = exports.MatchResult = exports.Pattern = exports._ = void 0;
|
|
13
|
-
exports.capture = capture;
|
|
14
|
-
exports.pattern = pattern;
|
|
15
|
-
exports.template = template;
|
|
16
|
-
exports.rewrite = rewrite;
|
|
17
|
-
/*
|
|
18
|
-
* Copyright 2025 the original author or authors.
|
|
19
|
-
* <p>
|
|
20
|
-
* Licensed under the Moderne Source Available License (the "License");
|
|
21
|
-
* you may not use this file except in compliance with the License.
|
|
22
|
-
* You may obtain a copy of the License at
|
|
23
|
-
* <p>
|
|
24
|
-
* https://docs.moderne.io/licensing/moderne-source-available-license
|
|
25
|
-
* <p>
|
|
26
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
27
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
28
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
29
|
-
* See the License for the specific language governing permissions and
|
|
30
|
-
* limitations under the License.
|
|
31
|
-
*/
|
|
32
|
-
const _1 = require(".");
|
|
33
|
-
const parser_1 = require("./parser");
|
|
34
|
-
const visitor_1 = require("./visitor");
|
|
35
|
-
const __1 = require("..");
|
|
36
|
-
const java_1 = require("../java");
|
|
37
|
-
const immer_1 = require("immer");
|
|
38
|
-
const comparator_1 = require("./comparator");
|
|
39
|
-
const dependency_workspace_1 = require("./dependency-workspace");
|
|
40
|
-
const markers_1 = require("../markers");
|
|
41
|
-
const uuid_1 = require("../uuid");
|
|
42
|
-
/**
|
|
43
|
-
* Cache for compiled templates and patterns.
|
|
44
|
-
* Stores parsed ASTs to avoid expensive re-parsing and dependency resolution.
|
|
45
|
-
*/
|
|
46
|
-
class TemplateCache {
|
|
47
|
-
constructor() {
|
|
48
|
-
this.cache = new Map();
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Generates a cache key from template string, captures, and options.
|
|
52
|
-
*/
|
|
53
|
-
generateKey(templateString, captures, imports, dependencies) {
|
|
54
|
-
// Use the actual template string (with placeholders) as the primary key
|
|
55
|
-
const templateKey = templateString;
|
|
56
|
-
// Capture names
|
|
57
|
-
const capturesKey = captures.map(c => c.name).join(',');
|
|
58
|
-
// Imports
|
|
59
|
-
const importsKey = imports.join(';');
|
|
60
|
-
// Dependencies
|
|
61
|
-
const depsKey = JSON.stringify(dependencies || {});
|
|
62
|
-
return `${templateKey}::${capturesKey}::${importsKey}::${depsKey}`;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Gets a cached compilation unit or creates and caches a new one.
|
|
66
|
-
*/
|
|
67
|
-
getOrParse(templateString, captures, imports, dependencies) {
|
|
68
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
-
const key = this.generateKey(templateString, captures, imports, dependencies);
|
|
70
|
-
let cu = this.cache.get(key);
|
|
71
|
-
if (cu) {
|
|
72
|
-
return cu;
|
|
73
|
-
}
|
|
74
|
-
// Create workspace if dependencies are provided
|
|
75
|
-
// DependencyWorkspace has its own cache, so multiple templates with
|
|
76
|
-
// the same dependencies will automatically share the same workspace
|
|
77
|
-
let workspaceDir;
|
|
78
|
-
if (dependencies && Object.keys(dependencies).length > 0) {
|
|
79
|
-
workspaceDir = yield dependency_workspace_1.DependencyWorkspace.getOrCreateWorkspace(dependencies);
|
|
80
|
-
}
|
|
81
|
-
// Prepend imports for type attribution context
|
|
82
|
-
const fullTemplateString = imports.length > 0
|
|
83
|
-
? imports.join('\n') + '\n' + templateString
|
|
84
|
-
: templateString;
|
|
85
|
-
// Parse and cache (workspace only needed during parsing)
|
|
86
|
-
const parser = new parser_1.JavaScriptParser({ relativeTo: workspaceDir });
|
|
87
|
-
const parseGenerator = parser.parse({ text: fullTemplateString, sourcePath: 'template.ts' });
|
|
88
|
-
cu = (yield parseGenerator.next()).value;
|
|
89
|
-
this.cache.set(key, cu);
|
|
90
|
-
return cu;
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Clears the cache.
|
|
95
|
-
*/
|
|
96
|
-
clear() {
|
|
97
|
-
this.cache.clear();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// Global cache instance
|
|
101
|
-
const templateCache = new TemplateCache();
|
|
102
|
-
/**
|
|
103
|
-
* Marker that stores capture metadata on pattern AST nodes.
|
|
104
|
-
* This avoids the need to parse capture names from identifiers during matching.
|
|
105
|
-
*/
|
|
106
|
-
class CaptureMarker {
|
|
107
|
-
constructor(captureName) {
|
|
108
|
-
this.captureName = captureName;
|
|
109
|
-
this.kind = 'org.openrewrite.javascript.CaptureMarker';
|
|
110
|
-
this.id = (0, uuid_1.randomId)();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* A comparator for pattern matching that is lenient about optional properties.
|
|
115
|
-
* Allows patterns without type annotations to match actual code with type annotations.
|
|
116
|
-
* Uses semantic comparison to match semantically equivalent code (e.g., isDate() and util.isDate()).
|
|
117
|
-
*/
|
|
118
|
-
class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparatorVisitor {
|
|
119
|
-
constructor(matcher) {
|
|
120
|
-
super();
|
|
121
|
-
this.matcher = matcher;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Creates a wildcard identifier that will match any AST node during comparison.
|
|
125
|
-
* The identifier has a CaptureMarker which causes it to match anything without storing the result.
|
|
126
|
-
*
|
|
127
|
-
* @param captureName The name for the capture marker (for debugging purposes)
|
|
128
|
-
* @returns A wildcard identifier
|
|
129
|
-
*/
|
|
130
|
-
createWildcardIdentifier(captureName) {
|
|
131
|
-
return {
|
|
132
|
-
id: (0, uuid_1.randomId)(),
|
|
133
|
-
kind: java_1.J.Kind.Identifier,
|
|
134
|
-
prefix: java_1.emptySpace,
|
|
135
|
-
markers: Object.assign(Object.assign({}, markers_1.emptyMarkers), { markers: [new CaptureMarker(captureName)] }),
|
|
136
|
-
annotations: [],
|
|
137
|
-
simpleName: '__wildcard__',
|
|
138
|
-
type: undefined,
|
|
139
|
-
fieldType: undefined
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
visit(j, p, parent) {
|
|
143
|
-
const _super = Object.create(null, {
|
|
144
|
-
visit: { get: () => super.visit }
|
|
145
|
-
});
|
|
146
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
147
|
-
// Check if the pattern node is a capture - this handles captures anywhere in the tree
|
|
148
|
-
if (PlaceholderUtils.isCapture(j)) {
|
|
149
|
-
const success = this.matcher.handleCapture(j, p);
|
|
150
|
-
if (!success) {
|
|
151
|
-
return this.abort(j);
|
|
152
|
-
}
|
|
153
|
-
return j;
|
|
154
|
-
}
|
|
155
|
-
if (!this.match) {
|
|
156
|
-
return j;
|
|
157
|
-
}
|
|
158
|
-
return _super.visit.call(this, j, p, parent);
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
visitVariableDeclarations(variableDeclarations, other) {
|
|
162
|
-
const _super = Object.create(null, {
|
|
163
|
-
visitVariableDeclarations: { get: () => super.visitVariableDeclarations }
|
|
164
|
-
});
|
|
165
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
166
|
-
if (!this.match || other.kind !== java_1.J.Kind.VariableDeclarations) {
|
|
167
|
-
return this.abort(variableDeclarations);
|
|
168
|
-
}
|
|
169
|
-
const otherVariableDeclarations = other;
|
|
170
|
-
// LENIENT: If pattern lacks typeExpression but target has one, add a wildcard capture to pattern
|
|
171
|
-
// This allows the pattern to match without requiring us to modify the target (which would corrupt captures)
|
|
172
|
-
if (!variableDeclarations.typeExpression && otherVariableDeclarations.typeExpression) {
|
|
173
|
-
variableDeclarations = (0, immer_1.produce)(variableDeclarations, draft => {
|
|
174
|
-
draft.typeExpression = this.createWildcardIdentifier('__wildcard_type__');
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
// Delegate to super implementation
|
|
178
|
-
return _super.visitVariableDeclarations.call(this, variableDeclarations, otherVariableDeclarations);
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
visitMethodDeclaration(methodDeclaration, other) {
|
|
182
|
-
const _super = Object.create(null, {
|
|
183
|
-
visitMethodDeclaration: { get: () => super.visitMethodDeclaration }
|
|
184
|
-
});
|
|
185
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
-
if (!this.match || other.kind !== java_1.J.Kind.MethodDeclaration) {
|
|
187
|
-
return this.abort(methodDeclaration);
|
|
188
|
-
}
|
|
189
|
-
const otherMethodDeclaration = other;
|
|
190
|
-
// LENIENT: If pattern lacks returnTypeExpression but target has one, add a wildcard capture to pattern
|
|
191
|
-
// This allows the pattern to match without requiring us to modify the target (which would corrupt captures)
|
|
192
|
-
if (!methodDeclaration.returnTypeExpression && otherMethodDeclaration.returnTypeExpression) {
|
|
193
|
-
methodDeclaration = (0, immer_1.produce)(methodDeclaration, draft => {
|
|
194
|
-
draft.returnTypeExpression = this.createWildcardIdentifier('__wildcard_return_type__');
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
// Delegate to super implementation
|
|
198
|
-
return _super.visitMethodDeclaration.call(this, methodDeclaration, otherMethodDeclaration);
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
hasSameKind(j, other) {
|
|
202
|
-
return super.hasSameKind(j, other) ||
|
|
203
|
-
(j.kind == java_1.J.Kind.Identifier && PlaceholderUtils.isCapture(j));
|
|
204
|
-
}
|
|
205
|
-
visitIdentifier(identifier, other) {
|
|
206
|
-
const _super = Object.create(null, {
|
|
207
|
-
visitIdentifier: { get: () => super.visitIdentifier }
|
|
208
|
-
});
|
|
209
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
210
|
-
if (PlaceholderUtils.isCapture(identifier)) {
|
|
211
|
-
const success = this.matcher.handleCapture(identifier, other);
|
|
212
|
-
return success ? identifier : this.abort(identifier);
|
|
213
|
-
}
|
|
214
|
-
return _super.visitIdentifier.call(this, identifier, other);
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
class CaptureImpl {
|
|
219
|
-
constructor(name) {
|
|
220
|
-
this.name = name;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Creates a capture specification for use in template patterns.
|
|
225
|
-
*
|
|
226
|
-
* @param name Optional name for the capture. If not provided, an auto-generated name is used.
|
|
227
|
-
* @returns A Capture object
|
|
228
|
-
*
|
|
229
|
-
* @example
|
|
230
|
-
* // Named inline captures
|
|
231
|
-
* const pattern = pattern`${capture('left')} + ${capture('right')}`;
|
|
232
|
-
*
|
|
233
|
-
* // Unnamed captures
|
|
234
|
-
* const {left, right} = {left: capture(), right: capture()};
|
|
235
|
-
* const pattern = pattern`${left} + ${right}`;
|
|
236
|
-
*
|
|
237
|
-
* // Repeated patterns using the same capture
|
|
238
|
-
* const expr = capture('expr');
|
|
239
|
-
* const redundantOr = pattern`${expr} || ${expr}`;
|
|
240
|
-
*/
|
|
241
|
-
function capture(name) {
|
|
242
|
-
if (name) {
|
|
243
|
-
return new CaptureImpl(name);
|
|
244
|
-
}
|
|
245
|
-
return new CaptureImpl(`unnamed_${capture.nextUnnamedId++}`);
|
|
246
|
-
}
|
|
247
|
-
// Static counter for generating unique IDs for unnamed captures
|
|
248
|
-
capture.nextUnnamedId = 1;
|
|
249
|
-
/**
|
|
250
|
-
* Concise alias for `capture`. Works well for inline captures in patterns and templates.
|
|
251
|
-
*
|
|
252
|
-
* @param name Optional name for the capture. If not provided, an auto-generated name is used.
|
|
253
|
-
* @returns A Capture object
|
|
254
|
-
*
|
|
255
|
-
* @example
|
|
256
|
-
* // Inline captures with _ alias
|
|
257
|
-
* pattern`isDate(${_('dateArg')})`
|
|
258
|
-
* template`${_('dateArg')} instanceof Date`
|
|
259
|
-
*/
|
|
260
|
-
exports._ = capture;
|
|
261
|
-
/**
|
|
262
|
-
* Represents a pattern that can be matched against AST nodes.
|
|
263
|
-
*/
|
|
264
|
-
class Pattern {
|
|
265
|
-
/**
|
|
266
|
-
* Gets the configuration options for this pattern.
|
|
267
|
-
* @readonly
|
|
268
|
-
*/
|
|
269
|
-
get options() {
|
|
270
|
-
return this._options;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Creates a new pattern from template parts and captures.
|
|
274
|
-
*
|
|
275
|
-
* @param templateParts The string parts of the template
|
|
276
|
-
* @param captures The captures between the string parts
|
|
277
|
-
*/
|
|
278
|
-
constructor(templateParts, captures) {
|
|
279
|
-
this.templateParts = templateParts;
|
|
280
|
-
this.captures = captures;
|
|
281
|
-
this._options = {};
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Configures this pattern with additional options.
|
|
285
|
-
*
|
|
286
|
-
* @param options Configuration options
|
|
287
|
-
* @returns This pattern for method chaining
|
|
288
|
-
*
|
|
289
|
-
* @example
|
|
290
|
-
* pattern`isDate(${capture('date')})`
|
|
291
|
-
* .configure({
|
|
292
|
-
* imports: ['import { isDate } from "util"'],
|
|
293
|
-
* dependencies: { 'util': '^1.0.0' }
|
|
294
|
-
* })
|
|
295
|
-
*/
|
|
296
|
-
configure(options) {
|
|
297
|
-
this._options = Object.assign(Object.assign({}, this._options), options);
|
|
298
|
-
return this;
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Creates a matcher for this pattern against a specific AST node.
|
|
302
|
-
*
|
|
303
|
-
* @param ast The AST node to match against
|
|
304
|
-
* @returns A Matcher object
|
|
305
|
-
*/
|
|
306
|
-
match(ast) {
|
|
307
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
308
|
-
const matcher = new Matcher(this, ast);
|
|
309
|
-
const success = yield matcher.matches();
|
|
310
|
-
return success ? new MatchResult(matcher.getAll()) : undefined;
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
exports.Pattern = Pattern;
|
|
315
|
-
class MatchResult {
|
|
316
|
-
constructor(bindings = new Map()) {
|
|
317
|
-
this.bindings = bindings;
|
|
318
|
-
}
|
|
319
|
-
get(capture) {
|
|
320
|
-
const name = typeof capture === "string" ? capture : capture.name;
|
|
321
|
-
return this.bindings.get(name);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
exports.MatchResult = MatchResult;
|
|
325
|
-
/**
|
|
326
|
-
* Matcher for checking if a pattern matches an AST node and extracting captured nodes.
|
|
327
|
-
*/
|
|
328
|
-
class Matcher {
|
|
329
|
-
/**
|
|
330
|
-
* Creates a new matcher for a pattern against an AST node.
|
|
331
|
-
*
|
|
332
|
-
* @param pattern The pattern to match
|
|
333
|
-
* @param ast The AST node to match against
|
|
334
|
-
*/
|
|
335
|
-
constructor(pattern, ast) {
|
|
336
|
-
this.pattern = pattern;
|
|
337
|
-
this.ast = ast;
|
|
338
|
-
this.bindings = new Map();
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Checks if the pattern matches the AST node.
|
|
342
|
-
*
|
|
343
|
-
* @returns true if the pattern matches, false otherwise
|
|
344
|
-
*/
|
|
345
|
-
matches() {
|
|
346
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
347
|
-
if (!this.patternAst) {
|
|
348
|
-
const templateProcessor = new TemplateProcessor(this.pattern.templateParts, this.pattern.captures, this.pattern.options.imports || [], this.pattern.options.dependencies || {});
|
|
349
|
-
this.patternAst = yield templateProcessor.toAstPattern();
|
|
350
|
-
}
|
|
351
|
-
return this.matchNode(this.patternAst, this.ast);
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Gets all captured nodes.
|
|
356
|
-
*
|
|
357
|
-
* @returns A map of capture names to captured nodes
|
|
358
|
-
*/
|
|
359
|
-
getAll() {
|
|
360
|
-
return new Map(this.bindings);
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Matches a pattern node against a target node.
|
|
364
|
-
*
|
|
365
|
-
* @param pattern The pattern node
|
|
366
|
-
* @param target The target node
|
|
367
|
-
* @returns true if the pattern matches the target, false otherwise
|
|
368
|
-
*/
|
|
369
|
-
matchNode(pattern, target) {
|
|
370
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
371
|
-
// Check if pattern is a capture placeholder
|
|
372
|
-
if (PlaceholderUtils.isCapture(pattern)) {
|
|
373
|
-
return this.handleCapture(pattern, target);
|
|
374
|
-
}
|
|
375
|
-
// Check if nodes have the same kind
|
|
376
|
-
if (pattern.kind !== target.kind) {
|
|
377
|
-
return false;
|
|
378
|
-
}
|
|
379
|
-
// Use the pattern matching comparator which is lenient about optional properties
|
|
380
|
-
const comparator = new PatternMatchingComparator({
|
|
381
|
-
handleCapture: (p, t) => this.handleCapture(p, t)
|
|
382
|
-
});
|
|
383
|
-
return yield comparator.compare(pattern, target);
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Handles a capture placeholder.
|
|
388
|
-
*
|
|
389
|
-
* @param pattern The pattern node
|
|
390
|
-
* @param target The target node
|
|
391
|
-
* @returns true if the capture is successful, false otherwise
|
|
392
|
-
*/
|
|
393
|
-
handleCapture(pattern, target) {
|
|
394
|
-
const captureName = PlaceholderUtils.getCaptureName(pattern);
|
|
395
|
-
if (!captureName) {
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
// Store the binding
|
|
399
|
-
this.bindings.set(captureName, target);
|
|
400
|
-
return true;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
/**
|
|
404
|
-
* Tagged template function for creating patterns.
|
|
405
|
-
*
|
|
406
|
-
* @param strings The string parts of the template
|
|
407
|
-
* @param captures The captures between the string parts
|
|
408
|
-
* @returns A Pattern object
|
|
409
|
-
*
|
|
410
|
-
* @example
|
|
411
|
-
* // Using the same capture multiple times for repeated patterns
|
|
412
|
-
* const expr = capture('expr');
|
|
413
|
-
* const redundantOr = pattern`${expr} || ${expr}`;
|
|
414
|
-
*/
|
|
415
|
-
function pattern(strings, ...captures) {
|
|
416
|
-
const capturesByName = captures.reduce((map, c) => {
|
|
417
|
-
const capture = typeof c === "string" ? new CaptureImpl(c) : c;
|
|
418
|
-
return map.set(capture.name, capture);
|
|
419
|
-
}, new Map());
|
|
420
|
-
return new Pattern(strings, captures.map(c => capturesByName.get(typeof c === "string" ? c : c.name)));
|
|
421
|
-
}
|
|
422
|
-
var JavaCoordinates;
|
|
423
|
-
(function (JavaCoordinates) {
|
|
424
|
-
let Mode;
|
|
425
|
-
(function (Mode) {
|
|
426
|
-
Mode[Mode["Before"] = 0] = "Before";
|
|
427
|
-
Mode[Mode["After"] = 1] = "After";
|
|
428
|
-
Mode[Mode["Replace"] = 2] = "Replace";
|
|
429
|
-
})(Mode = JavaCoordinates.Mode || (JavaCoordinates.Mode = {}));
|
|
430
|
-
})(JavaCoordinates || (JavaCoordinates = {}));
|
|
431
|
-
/**
|
|
432
|
-
* Template for creating AST nodes.
|
|
433
|
-
*
|
|
434
|
-
* This class provides the public API for template generation.
|
|
435
|
-
* The actual templating logic is handled by the internal TemplateEngine.
|
|
436
|
-
*
|
|
437
|
-
* @example
|
|
438
|
-
* // Generate a literal AST node
|
|
439
|
-
* const result = template`2`.apply(cursor, coordinates);
|
|
440
|
-
*
|
|
441
|
-
* @example
|
|
442
|
-
* // Generate an AST node with a parameter
|
|
443
|
-
* const result = template`${capture()}`.apply(cursor, coordinates);
|
|
444
|
-
*/
|
|
445
|
-
class Template {
|
|
446
|
-
/**
|
|
447
|
-
* Creates a new template.
|
|
448
|
-
*
|
|
449
|
-
* @param templateParts The string parts of the template
|
|
450
|
-
* @param parameters The parameters between the string parts
|
|
451
|
-
*/
|
|
452
|
-
constructor(templateParts, parameters) {
|
|
453
|
-
this.templateParts = templateParts;
|
|
454
|
-
this.parameters = parameters;
|
|
455
|
-
this.options = {};
|
|
456
|
-
}
|
|
457
|
-
/**
|
|
458
|
-
* Configures this template with additional options.
|
|
459
|
-
*
|
|
460
|
-
* @param options Configuration options
|
|
461
|
-
* @returns This template for method chaining
|
|
462
|
-
*
|
|
463
|
-
* @example
|
|
464
|
-
* template`isDate(${capture('date')})`
|
|
465
|
-
* .configure({
|
|
466
|
-
* imports: ['import { isDate } from "util"'],
|
|
467
|
-
* dependencies: { 'util': '^1.0.0' }
|
|
468
|
-
* })
|
|
469
|
-
*/
|
|
470
|
-
configure(options) {
|
|
471
|
-
this.options = Object.assign(Object.assign({}, this.options), options);
|
|
472
|
-
return this;
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Applies this template and returns the resulting tree.
|
|
476
|
-
*
|
|
477
|
-
* @param cursor The cursor pointing to the current location in the AST
|
|
478
|
-
* @param tree Input tree
|
|
479
|
-
* @param values values for parameters in template
|
|
480
|
-
* @returns A Promise resolving to the generated AST node
|
|
481
|
-
*/
|
|
482
|
-
apply(cursor, tree, values) {
|
|
483
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
484
|
-
return TemplateEngine.applyTemplate(this.templateParts, this.parameters, cursor, {
|
|
485
|
-
tree,
|
|
486
|
-
mode: JavaCoordinates.Mode.Replace
|
|
487
|
-
}, values, this.options.imports || [], this.options.dependencies || {});
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
exports.Template = Template;
|
|
492
|
-
function template(strings, ...parameters) {
|
|
493
|
-
// Convert parameters to Parameter objects (no longer need to check for mutable tree property)
|
|
494
|
-
const processedParameters = parameters.map(param => {
|
|
495
|
-
// Just wrap each parameter value in a Parameter object
|
|
496
|
-
return { value: param };
|
|
497
|
-
});
|
|
498
|
-
return new Template(strings, processedParameters);
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Internal template engine - handles the core templating logic.
|
|
502
|
-
* Not exported, so only visible within this module.
|
|
503
|
-
*/
|
|
504
|
-
class TemplateEngine {
|
|
505
|
-
/**
|
|
506
|
-
* Applies a template with optional match results from pattern matching.
|
|
507
|
-
*
|
|
508
|
-
* @param templateParts The string parts of the template
|
|
509
|
-
* @param parameters The parameters between the string parts
|
|
510
|
-
* @param cursor The cursor pointing to the current location in the AST
|
|
511
|
-
* @param coordinates The coordinates specifying where and how to insert the generated AST
|
|
512
|
-
* @param values Map of capture names to values to replace the parameters with
|
|
513
|
-
* @param imports Import statements to prepend for type attribution
|
|
514
|
-
* @param dependencies NPM dependencies for type attribution
|
|
515
|
-
* @returns A Promise resolving to the generated AST node
|
|
516
|
-
*/
|
|
517
|
-
static applyTemplate(templateParts_1, parameters_1, cursor_1, coordinates_1) {
|
|
518
|
-
return __awaiter(this, arguments, void 0, function* (templateParts, parameters, cursor, coordinates, values = new Map(), imports = [], dependencies = {}) {
|
|
519
|
-
// Build the template string with parameter placeholders
|
|
520
|
-
const templateString = TemplateEngine.buildTemplateString(templateParts, parameters);
|
|
521
|
-
// If the template string is empty, return undefined
|
|
522
|
-
if (!templateString.trim()) {
|
|
523
|
-
return undefined;
|
|
524
|
-
}
|
|
525
|
-
// Use cache to get or parse the compilation unit
|
|
526
|
-
// For templates, we don't have captures, so use empty array
|
|
527
|
-
const cu = yield templateCache.getOrParse(templateString, [], // templates don't have captures in the cache key
|
|
528
|
-
imports, dependencies);
|
|
529
|
-
// Check if there are any statements
|
|
530
|
-
if (!cu.statements || cu.statements.length === 0) {
|
|
531
|
-
return undefined;
|
|
532
|
-
}
|
|
533
|
-
// Skip import statements to get to the actual template code
|
|
534
|
-
const templateStatementIndex = imports.length;
|
|
535
|
-
if (templateStatementIndex >= cu.statements.length) {
|
|
536
|
-
return undefined;
|
|
537
|
-
}
|
|
538
|
-
// Extract the relevant part of the AST
|
|
539
|
-
const firstStatement = cu.statements[templateStatementIndex].element;
|
|
540
|
-
let extracted = firstStatement.kind === _1.JS.Kind.ExpressionStatement ?
|
|
541
|
-
firstStatement.expression :
|
|
542
|
-
firstStatement;
|
|
543
|
-
// Create a copy to avoid sharing cached AST instances
|
|
544
|
-
const ast = (0, immer_1.produce)(extracted, draft => { });
|
|
545
|
-
// Create substitutions map for placeholders
|
|
546
|
-
const substitutions = new Map();
|
|
547
|
-
for (let i = 0; i < parameters.length; i++) {
|
|
548
|
-
const placeholder = `${PlaceholderUtils.PLACEHOLDER_PREFIX}${i}__`;
|
|
549
|
-
substitutions.set(placeholder, parameters[i]);
|
|
550
|
-
}
|
|
551
|
-
// Unsubstitute placeholders with actual parameter values and match results
|
|
552
|
-
const visitor = new PlaceholderReplacementVisitor(substitutions, values);
|
|
553
|
-
const unsubstitutedAst = (yield visitor.visit(ast, null));
|
|
554
|
-
// Apply the template to the current AST
|
|
555
|
-
return new TemplateApplier(cursor, coordinates, unsubstitutedAst, parameters).apply();
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Builds a template string with parameter placeholders.
|
|
560
|
-
*
|
|
561
|
-
* @param templateParts The string parts of the template
|
|
562
|
-
* @param parameters The parameters between the string parts
|
|
563
|
-
* @returns The template string
|
|
564
|
-
*/
|
|
565
|
-
static buildTemplateString(templateParts, parameters) {
|
|
566
|
-
let result = '';
|
|
567
|
-
for (let i = 0; i < templateParts.length; i++) {
|
|
568
|
-
result += templateParts[i];
|
|
569
|
-
if (i < parameters.length) {
|
|
570
|
-
const param = parameters[i].value;
|
|
571
|
-
// Use a placeholder for Captures and Tree nodes
|
|
572
|
-
// Inline everything else (strings, numbers, booleans) directly
|
|
573
|
-
if (param instanceof CaptureImpl || (0, __1.isTree)(param)) {
|
|
574
|
-
const placeholder = `${PlaceholderUtils.PLACEHOLDER_PREFIX}${i}__`;
|
|
575
|
-
result += placeholder;
|
|
576
|
-
}
|
|
577
|
-
else {
|
|
578
|
-
result += param;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
return result;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Utility class for managing placeholder naming and parsing.
|
|
587
|
-
* Centralizes all logic related to capture placeholders.
|
|
588
|
-
*/
|
|
589
|
-
class PlaceholderUtils {
|
|
590
|
-
/**
|
|
591
|
-
* Checks if a node is a capture placeholder.
|
|
592
|
-
*
|
|
593
|
-
* @param node The node to check
|
|
594
|
-
* @returns true if the node is a capture placeholder, false otherwise
|
|
595
|
-
*/
|
|
596
|
-
static isCapture(node) {
|
|
597
|
-
// Check for CaptureMarker first (efficient)
|
|
598
|
-
for (const marker of node.markers.markers) {
|
|
599
|
-
if (marker instanceof CaptureMarker) {
|
|
600
|
-
return true;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
return false;
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Gets the capture name from a node with a CaptureMarker.
|
|
607
|
-
*
|
|
608
|
-
* @param node The node to extract capture name from
|
|
609
|
-
* @returns The capture name, or null if not a capture
|
|
610
|
-
*/
|
|
611
|
-
static getCaptureName(node) {
|
|
612
|
-
// Check for CaptureMarker
|
|
613
|
-
for (const marker of node.markers.markers) {
|
|
614
|
-
if (marker instanceof CaptureMarker) {
|
|
615
|
-
return marker.captureName;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return undefined;
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* Parses a capture placeholder to extract name and type constraint.
|
|
622
|
-
*
|
|
623
|
-
* @param identifier The identifier string to parse
|
|
624
|
-
* @returns Object with name and optional type constraint, or null if not a valid capture
|
|
625
|
-
*/
|
|
626
|
-
static parseCapture(identifier) {
|
|
627
|
-
if (!identifier.startsWith(this.CAPTURE_PREFIX)) {
|
|
628
|
-
return null;
|
|
629
|
-
}
|
|
630
|
-
// Handle unnamed captures: "__capture_unnamed_N__"
|
|
631
|
-
if (identifier.startsWith(`${this.CAPTURE_PREFIX}unnamed_`)) {
|
|
632
|
-
const match = identifier.match(/__capture_(unnamed_\d+)__/);
|
|
633
|
-
return match ? { name: match[1] } : null;
|
|
634
|
-
}
|
|
635
|
-
// Handle named captures: "__capture_name__" or "__capture_name_type__"
|
|
636
|
-
const match = identifier.match(/__capture_([^_]+)(?:_([^_]+))?__/);
|
|
637
|
-
if (!match) {
|
|
638
|
-
return null;
|
|
639
|
-
}
|
|
640
|
-
return {
|
|
641
|
-
name: match[1],
|
|
642
|
-
typeConstraint: match[2]
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
/**
|
|
646
|
-
* Creates a capture placeholder string.
|
|
647
|
-
*
|
|
648
|
-
* @param name The capture name
|
|
649
|
-
* @param typeConstraint Optional type constraint
|
|
650
|
-
* @returns The formatted placeholder string
|
|
651
|
-
*/
|
|
652
|
-
static createCapture(name, typeConstraint) {
|
|
653
|
-
return typeConstraint
|
|
654
|
-
? `${this.CAPTURE_PREFIX}${name}_${typeConstraint}__`
|
|
655
|
-
: `${this.CAPTURE_PREFIX}${name}__`;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
PlaceholderUtils.CAPTURE_PREFIX = '__capture_';
|
|
659
|
-
PlaceholderUtils.PLACEHOLDER_PREFIX = '__PLACEHOLDER_';
|
|
660
|
-
/**
|
|
661
|
-
* Visitor that replaces placeholder nodes with actual parameter values.
|
|
662
|
-
*/
|
|
663
|
-
class PlaceholderReplacementVisitor extends visitor_1.JavaScriptVisitor {
|
|
664
|
-
constructor(substitutions, values = new Map()) {
|
|
665
|
-
super();
|
|
666
|
-
this.substitutions = substitutions;
|
|
667
|
-
this.values = values;
|
|
668
|
-
}
|
|
669
|
-
visit(tree, p, parent) {
|
|
670
|
-
const _super = Object.create(null, {
|
|
671
|
-
visit: { get: () => super.visit }
|
|
672
|
-
});
|
|
673
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
674
|
-
// Check if this node is a placeholder
|
|
675
|
-
if (this.isPlaceholder(tree)) {
|
|
676
|
-
const replacement = this.replacePlaceholder(tree);
|
|
677
|
-
if (replacement !== tree) {
|
|
678
|
-
return replacement;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
// Continue with normal traversal
|
|
682
|
-
return _super.visit.call(this, tree, p, parent);
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Checks if a node is a placeholder.
|
|
687
|
-
*
|
|
688
|
-
* @param node The node to check
|
|
689
|
-
* @returns True if the node is a placeholder
|
|
690
|
-
*/
|
|
691
|
-
isPlaceholder(node) {
|
|
692
|
-
var _a;
|
|
693
|
-
if (node.kind === java_1.J.Kind.Identifier) {
|
|
694
|
-
const identifier = node;
|
|
695
|
-
return identifier.simpleName.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX);
|
|
696
|
-
}
|
|
697
|
-
else if (node.kind === java_1.J.Kind.Literal) {
|
|
698
|
-
const literal = node;
|
|
699
|
-
return ((_a = literal.valueSource) === null || _a === void 0 ? void 0 : _a.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX)) || false;
|
|
700
|
-
}
|
|
701
|
-
return false;
|
|
702
|
-
}
|
|
703
|
-
/**
|
|
704
|
-
* Replaces a placeholder node with the actual parameter value.
|
|
705
|
-
*
|
|
706
|
-
* @param placeholder The placeholder node
|
|
707
|
-
* @returns The replacement node or the original if not a placeholder
|
|
708
|
-
*/
|
|
709
|
-
replacePlaceholder(placeholder) {
|
|
710
|
-
const placeholderText = this.getPlaceholderText(placeholder);
|
|
711
|
-
if (!placeholderText || !placeholderText.startsWith(PlaceholderUtils.PLACEHOLDER_PREFIX)) {
|
|
712
|
-
return placeholder;
|
|
713
|
-
}
|
|
714
|
-
// Find the corresponding parameter
|
|
715
|
-
const param = this.substitutions.get(placeholderText);
|
|
716
|
-
if (!param || param.value === undefined) {
|
|
717
|
-
return placeholder;
|
|
718
|
-
}
|
|
719
|
-
// If the parameter value is a Capture, look up the matched result
|
|
720
|
-
if (param.value instanceof CaptureImpl) {
|
|
721
|
-
const matchedNode = this.values.get(param.value.name);
|
|
722
|
-
if (matchedNode) {
|
|
723
|
-
return (0, immer_1.produce)(matchedNode, draft => {
|
|
724
|
-
draft.markers = placeholder.markers;
|
|
725
|
-
draft.prefix = placeholder.prefix;
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
// If no match found, return placeholder unchanged
|
|
729
|
-
return placeholder;
|
|
730
|
-
}
|
|
731
|
-
// If the parameter value is an AST node, use it directly
|
|
732
|
-
if ((0, __1.isTree)(param.value)) {
|
|
733
|
-
// Return the AST node, preserving the original prefix
|
|
734
|
-
return (0, immer_1.produce)(param.value, draft => {
|
|
735
|
-
draft.markers = placeholder.markers;
|
|
736
|
-
draft.prefix = placeholder.prefix;
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
return placeholder;
|
|
740
|
-
}
|
|
741
|
-
/**
|
|
742
|
-
* Gets the placeholder text from a node.
|
|
743
|
-
*
|
|
744
|
-
* @param node The node to get placeholder text from
|
|
745
|
-
* @returns The placeholder text or null
|
|
746
|
-
*/
|
|
747
|
-
getPlaceholderText(node) {
|
|
748
|
-
if (node.kind === java_1.J.Kind.Identifier) {
|
|
749
|
-
return node.simpleName;
|
|
750
|
-
}
|
|
751
|
-
else if (node.kind === java_1.J.Kind.Literal) {
|
|
752
|
-
return node.valueSource || null;
|
|
753
|
-
}
|
|
754
|
-
return null;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Helper class for applying a template to an AST.
|
|
759
|
-
*/
|
|
760
|
-
class TemplateApplier {
|
|
761
|
-
constructor(cursor, coordinates, ast, parameters = []) {
|
|
762
|
-
this.cursor = cursor;
|
|
763
|
-
this.coordinates = coordinates;
|
|
764
|
-
this.ast = ast;
|
|
765
|
-
this.parameters = parameters;
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Applies the template to the current AST.
|
|
769
|
-
*
|
|
770
|
-
* @returns A Promise resolving to the modified AST
|
|
771
|
-
*/
|
|
772
|
-
apply() {
|
|
773
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
774
|
-
const { loc } = this.coordinates;
|
|
775
|
-
// Apply the template based on the location and mode
|
|
776
|
-
switch (loc || 'EXPRESSION_PREFIX') {
|
|
777
|
-
case 'EXPRESSION_PREFIX':
|
|
778
|
-
return this.applyToExpression();
|
|
779
|
-
case 'STATEMENT_PREFIX':
|
|
780
|
-
return this.applyToStatement();
|
|
781
|
-
case 'BLOCK_END':
|
|
782
|
-
return this.applyToBlock();
|
|
783
|
-
default:
|
|
784
|
-
throw new Error(`Unsupported location: ${loc}`);
|
|
785
|
-
}
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
/**
|
|
789
|
-
* Applies the template to an expression.
|
|
790
|
-
*
|
|
791
|
-
* @returns A Promise resolving to the modified AST
|
|
792
|
-
*/
|
|
793
|
-
applyToExpression() {
|
|
794
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
795
|
-
const { tree } = this.coordinates;
|
|
796
|
-
// Create a copy of the AST with the prefix from the target
|
|
797
|
-
return tree ? (0, immer_1.produce)(this.ast, draft => {
|
|
798
|
-
draft.prefix = tree.prefix;
|
|
799
|
-
}) : this.ast;
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* Applies the template to a statement.
|
|
804
|
-
*
|
|
805
|
-
* @returns A Promise resolving to the modified AST
|
|
806
|
-
*/
|
|
807
|
-
applyToStatement() {
|
|
808
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
809
|
-
const { tree } = this.coordinates;
|
|
810
|
-
// Create a copy of the AST with the prefix from the target
|
|
811
|
-
return (0, immer_1.produce)(this.ast, draft => {
|
|
812
|
-
draft.prefix = tree.prefix;
|
|
813
|
-
});
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
/**
|
|
817
|
-
* Applies the template to a block.
|
|
818
|
-
*
|
|
819
|
-
* @returns A Promise resolving to the modified AST
|
|
820
|
-
*/
|
|
821
|
-
applyToBlock() {
|
|
822
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
823
|
-
const { tree } = this.coordinates;
|
|
824
|
-
// Create a copy of the AST with the prefix from the target
|
|
825
|
-
return (0, immer_1.produce)(this.ast, draft => {
|
|
826
|
-
draft.prefix = tree.prefix;
|
|
827
|
-
});
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* Processor for template strings.
|
|
833
|
-
* Converts a template string with captures into an AST pattern.
|
|
834
|
-
*/
|
|
835
|
-
class TemplateProcessor {
|
|
836
|
-
/**
|
|
837
|
-
* Creates a new template processor.
|
|
838
|
-
*
|
|
839
|
-
* @param templateParts The string parts of the template
|
|
840
|
-
* @param captures The captures between the string parts
|
|
841
|
-
* @param imports Import statements to prepend for type attribution
|
|
842
|
-
* @param dependencies NPM dependencies for type attribution
|
|
843
|
-
*/
|
|
844
|
-
constructor(templateParts, captures, imports = [], dependencies = {}) {
|
|
845
|
-
this.templateParts = templateParts;
|
|
846
|
-
this.captures = captures;
|
|
847
|
-
this.imports = imports;
|
|
848
|
-
this.dependencies = dependencies;
|
|
849
|
-
}
|
|
850
|
-
/**
|
|
851
|
-
* Converts the template to an AST pattern.
|
|
852
|
-
*
|
|
853
|
-
* @returns A Promise resolving to the AST pattern
|
|
854
|
-
*/
|
|
855
|
-
toAstPattern() {
|
|
856
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
857
|
-
// Combine template parts and placeholders
|
|
858
|
-
const templateString = this.buildTemplateString();
|
|
859
|
-
// Use cache to get or parse the compilation unit
|
|
860
|
-
const cu = yield templateCache.getOrParse(templateString, this.captures, this.imports, this.dependencies);
|
|
861
|
-
// Extract the relevant part of the AST
|
|
862
|
-
return this.extractPatternFromAst(cu);
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* Builds a template string with placeholders for captures.
|
|
867
|
-
*
|
|
868
|
-
* @returns The template string
|
|
869
|
-
*/
|
|
870
|
-
buildTemplateString() {
|
|
871
|
-
let result = '';
|
|
872
|
-
for (let i = 0; i < this.templateParts.length; i++) {
|
|
873
|
-
result += this.templateParts[i];
|
|
874
|
-
if (i < this.captures.length) {
|
|
875
|
-
const capture = this.captures[i];
|
|
876
|
-
result += PlaceholderUtils.createCapture(capture.name);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
return result;
|
|
880
|
-
}
|
|
881
|
-
/**
|
|
882
|
-
* Extracts the pattern from the parsed AST.
|
|
883
|
-
*
|
|
884
|
-
* @param cu The compilation unit
|
|
885
|
-
* @returns The extracted pattern
|
|
886
|
-
*/
|
|
887
|
-
extractPatternFromAst(cu) {
|
|
888
|
-
// Skip import statements to get to the actual pattern code
|
|
889
|
-
const patternStatementIndex = this.imports.length;
|
|
890
|
-
// Extract the relevant part of the AST based on the template content
|
|
891
|
-
const firstStatement = cu.statements[patternStatementIndex].element;
|
|
892
|
-
let extracted;
|
|
893
|
-
// If the first statement is an expression statement, extract the expression
|
|
894
|
-
if (firstStatement.kind === _1.JS.Kind.ExpressionStatement) {
|
|
895
|
-
extracted = firstStatement.expression;
|
|
896
|
-
}
|
|
897
|
-
else {
|
|
898
|
-
// Otherwise, return the statement itself
|
|
899
|
-
extracted = firstStatement;
|
|
900
|
-
}
|
|
901
|
-
// Attach CaptureMarkers to capture identifiers
|
|
902
|
-
return this.attachCaptureMarkers(extracted);
|
|
903
|
-
}
|
|
904
|
-
/**
|
|
905
|
-
* Attaches CaptureMarkers to capture identifiers in the AST.
|
|
906
|
-
* This allows efficient capture detection without string parsing.
|
|
907
|
-
*
|
|
908
|
-
* @param ast The AST to process
|
|
909
|
-
* @returns The AST with CaptureMarkers attached
|
|
910
|
-
*/
|
|
911
|
-
attachCaptureMarkers(ast) {
|
|
912
|
-
const visited = new Set();
|
|
913
|
-
return (0, immer_1.produce)(ast, draft => {
|
|
914
|
-
this.visitAndAttachMarkers(draft, visited);
|
|
915
|
-
});
|
|
916
|
-
}
|
|
917
|
-
/**
|
|
918
|
-
* Recursively visits AST nodes and attaches CaptureMarkers to capture identifiers.
|
|
919
|
-
*
|
|
920
|
-
* @param node The node to visit
|
|
921
|
-
* @param visited Set of already visited nodes to avoid cycles
|
|
922
|
-
*/
|
|
923
|
-
visitAndAttachMarkers(node, visited) {
|
|
924
|
-
var _a;
|
|
925
|
-
if (!node || typeof node !== 'object' || visited.has(node)) {
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
// Mark as visited to avoid cycles
|
|
929
|
-
visited.add(node);
|
|
930
|
-
// If this is an identifier that looks like a capture, attach a marker
|
|
931
|
-
if (node.kind === java_1.J.Kind.Identifier && ((_a = node.simpleName) === null || _a === void 0 ? void 0 : _a.startsWith(PlaceholderUtils.CAPTURE_PREFIX))) {
|
|
932
|
-
const captureInfo = PlaceholderUtils.parseCapture(node.simpleName);
|
|
933
|
-
if (captureInfo) {
|
|
934
|
-
// Initialize markers if needed
|
|
935
|
-
if (!node.markers) {
|
|
936
|
-
node.markers = { kind: 'org.openrewrite.marker.Markers', id: (0, uuid_1.randomId)(), markers: [] };
|
|
937
|
-
}
|
|
938
|
-
if (!node.markers.markers) {
|
|
939
|
-
node.markers.markers = [];
|
|
940
|
-
}
|
|
941
|
-
// Add CaptureMarker
|
|
942
|
-
node.markers.markers.push(new CaptureMarker(captureInfo.name));
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
// Recursively visit all properties
|
|
946
|
-
for (const key in node) {
|
|
947
|
-
if (node.hasOwnProperty(key)) {
|
|
948
|
-
const value = node[key];
|
|
949
|
-
if (Array.isArray(value)) {
|
|
950
|
-
value.forEach(item => this.visitAndAttachMarkers(item, visited));
|
|
951
|
-
}
|
|
952
|
-
else if (typeof value === 'object' && value !== null) {
|
|
953
|
-
this.visitAndAttachMarkers(value, visited);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
|
-
* Implementation of a replacement rule.
|
|
961
|
-
*/
|
|
962
|
-
class RewriteRuleImpl {
|
|
963
|
-
constructor(before, after) {
|
|
964
|
-
this.before = before;
|
|
965
|
-
this.after = after;
|
|
966
|
-
}
|
|
967
|
-
tryOn(cursor, node) {
|
|
968
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
969
|
-
for (const pattern of this.before) {
|
|
970
|
-
const match = yield pattern.match(node);
|
|
971
|
-
if (match) {
|
|
972
|
-
const result = yield this.after.apply(cursor, node, match);
|
|
973
|
-
if (result) {
|
|
974
|
-
return result;
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
// Return undefined if no patterns match
|
|
979
|
-
return undefined;
|
|
980
|
-
});
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
/**
|
|
984
|
-
* Creates a replacement rule using a capture context and configuration.
|
|
985
|
-
*
|
|
986
|
-
* @param builderFn Function that takes a capture context and returns before/after configuration
|
|
987
|
-
* @returns A replacement rule that can be applied to AST nodes
|
|
988
|
-
*
|
|
989
|
-
* @example
|
|
990
|
-
* // Single pattern
|
|
991
|
-
* const swapOperands = rewrite(() => ({
|
|
992
|
-
* before: pattern`${"left"} + ${"right"}`,
|
|
993
|
-
* after: template`${"right"} + ${"left"}`
|
|
994
|
-
* }));
|
|
995
|
-
*
|
|
996
|
-
* @example
|
|
997
|
-
* // Multiple patterns
|
|
998
|
-
* const normalizeComparisons = rewrite(() => ({
|
|
999
|
-
* before: [
|
|
1000
|
-
* pattern`${"left"} == ${"right"}`,
|
|
1001
|
-
* pattern`${"left"} === ${"right"}`
|
|
1002
|
-
* ],
|
|
1003
|
-
* after: template`${"left"} === ${"right"}`
|
|
1004
|
-
* }));
|
|
1005
|
-
*
|
|
1006
|
-
* @example
|
|
1007
|
-
* // Using in a visitor - IMPORTANT: use `|| node` to handle undefined when no match
|
|
1008
|
-
* class MyVisitor extends JavaScriptVisitor<any> {
|
|
1009
|
-
* override async visitBinary(binary: J.Binary, p: any): Promise<J | undefined> {
|
|
1010
|
-
* const rule = rewrite(() => ({
|
|
1011
|
-
* before: pattern`${capture('a')} + ${capture('b')}`,
|
|
1012
|
-
* after: template`${capture('b')} + ${capture('a')}`
|
|
1013
|
-
* }));
|
|
1014
|
-
* // tryOn() returns undefined if no pattern matches, so always use || node
|
|
1015
|
-
* return await rule.tryOn(this.cursor, binary) || binary;
|
|
1016
|
-
* }
|
|
1017
|
-
* }
|
|
1018
|
-
*/
|
|
1019
|
-
function rewrite(builderFn) {
|
|
1020
|
-
const config = builderFn();
|
|
1021
|
-
// Ensure we have valid before and after properties
|
|
1022
|
-
if (!config.before || !config.after) {
|
|
1023
|
-
throw new Error('Builder function must return an object with before and after properties');
|
|
1024
|
-
}
|
|
1025
|
-
return new RewriteRuleImpl(Array.isArray(config.before) ? config.before : [config.before], config.after);
|
|
1026
|
-
}
|
|
1027
|
-
//# sourceMappingURL=templating.js.map
|