@openrewrite/rewrite 8.66.1 → 8.66.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/java/tree.d.ts +10 -1
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +21 -5
- package/dist/java/tree.js.map +1 -1
- package/dist/java/type-visitor.d.ts +1 -1
- package/dist/java/type-visitor.d.ts.map +1 -1
- package/dist/java/visitor.d.ts +2 -2
- package/dist/java/visitor.d.ts.map +1 -1
- package/dist/java/visitor.js +8 -2
- package/dist/java/visitor.js.map +1 -1
- package/dist/javascript/assertions.d.ts +6 -0
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +14 -6
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/comparator.d.ts +154 -7
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +623 -180
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format.d.ts +5 -3
- package/dist/javascript/format.d.ts.map +1 -1
- package/dist/javascript/format.js +85 -43
- package/dist/javascript/format.js.map +1 -1
- package/dist/javascript/index.d.ts +1 -0
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +1 -0
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/parser.d.ts +2 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +39 -30
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/templating/capture.d.ts +81 -14
- package/dist/javascript/templating/capture.d.ts.map +1 -1
- package/dist/javascript/templating/capture.js +98 -8
- package/dist/javascript/templating/capture.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +125 -15
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +946 -118
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +58 -25
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +527 -94
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/index.d.ts +3 -3
- package/dist/javascript/templating/index.d.ts.map +1 -1
- package/dist/javascript/templating/index.js +3 -1
- package/dist/javascript/templating/index.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +121 -16
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +528 -257
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +30 -5
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +183 -81
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/rewrite.d.ts +56 -11
- package/dist/javascript/templating/rewrite.d.ts.map +1 -1
- package/dist/javascript/templating/rewrite.js +143 -16
- package/dist/javascript/templating/rewrite.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +31 -5
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +89 -15
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +359 -12
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +52 -35
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +107 -109
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +21 -11
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/json/rpc.js +2 -2
- package/dist/json/rpc.js.map +1 -1
- package/dist/recipe/order-imports.js.map +1 -1
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +10 -6
- package/dist/test/rewrite-test.js.map +1 -1
- package/dist/version.txt +1 -1
- package/dist/visitor.d.ts +4 -4
- package/dist/visitor.d.ts.map +1 -1
- package/dist/visitor.js +8 -3
- package/dist/visitor.js.map +1 -1
- package/package.json +4 -2
- package/src/java/tree.ts +10 -3
- package/src/java/type-visitor.ts +1 -1
- package/src/java/visitor.ts +11 -5
- package/src/javascript/assertions.ts +9 -3
- package/src/javascript/comparator.ts +676 -185
- package/src/javascript/format.ts +72 -34
- package/src/javascript/index.ts +1 -0
- package/src/javascript/parser.ts +51 -31
- package/src/javascript/templating/capture.ts +107 -15
- package/src/javascript/templating/comparator.ts +1087 -134
- package/src/javascript/templating/engine.ts +601 -103
- package/src/javascript/templating/index.ts +9 -2
- package/src/javascript/templating/pattern.ts +655 -281
- package/src/javascript/templating/placeholder-replacement.ts +183 -80
- package/src/javascript/templating/rewrite.ts +152 -18
- package/src/javascript/templating/template.ts +110 -22
- package/src/javascript/templating/types.ts +386 -12
- package/src/javascript/templating/utils.ts +116 -102
- package/src/javascript/type-mapping.ts +20 -11
- package/src/json/rpc.ts +2 -2
- package/src/recipe/order-imports.ts +1 -1
- package/src/test/rewrite-test.ts +12 -7
- package/src/visitor.ts +14 -6
|
@@ -26,13 +26,13 @@ exports.pattern = pattern;
|
|
|
26
26
|
* See the License for the specific language governing permissions and
|
|
27
27
|
* limitations under the License.
|
|
28
28
|
*/
|
|
29
|
-
const
|
|
30
|
-
const java_1 = require("../../java");
|
|
31
|
-
const index_1 = require("../index");
|
|
32
|
-
const uuid_1 = require("../../uuid");
|
|
29
|
+
const __1 = require("../..");
|
|
33
30
|
const capture_1 = require("./capture");
|
|
34
31
|
const comparator_1 = require("./comparator");
|
|
35
32
|
const utils_1 = require("./utils");
|
|
33
|
+
const engine_1 = require("./engine");
|
|
34
|
+
const print_1 = require("../../print");
|
|
35
|
+
const index_1 = require("../index");
|
|
36
36
|
/**
|
|
37
37
|
* Builder for creating patterns programmatically.
|
|
38
38
|
* Use when pattern structure is not known at compile time.
|
|
@@ -85,7 +85,7 @@ class PatternBuilder {
|
|
|
85
85
|
/**
|
|
86
86
|
* Adds a capture to the pattern.
|
|
87
87
|
*
|
|
88
|
-
* @param value The capture object (Capture or
|
|
88
|
+
* @param value The capture object (Capture, Any, or RawCode) or string name
|
|
89
89
|
* @returns This builder for chaining
|
|
90
90
|
*/
|
|
91
91
|
capture(value) {
|
|
@@ -93,7 +93,7 @@ class PatternBuilder {
|
|
|
93
93
|
if (this.parts.length === 0) {
|
|
94
94
|
this.parts.push('');
|
|
95
95
|
}
|
|
96
|
-
// Convert string to Capture if needed
|
|
96
|
+
// Convert string to Capture if needed, or use value as-is for RawCode
|
|
97
97
|
const captureObj = typeof value === 'string' ? new capture_1.CaptureImpl(value) : value;
|
|
98
98
|
this.captures.push(captureObj);
|
|
99
99
|
// Add an empty string for the next part
|
|
@@ -154,12 +154,25 @@ class Pattern {
|
|
|
154
154
|
* Creates a new pattern from template parts and captures.
|
|
155
155
|
*
|
|
156
156
|
* @param templateParts The string parts of the template
|
|
157
|
-
* @param captures The captures between the string parts (can be Capture or
|
|
157
|
+
* @param captures The captures between the string parts (can be Capture, Any, or RawCode)
|
|
158
158
|
*/
|
|
159
159
|
constructor(templateParts, captures) {
|
|
160
160
|
this.templateParts = templateParts;
|
|
161
161
|
this.captures = captures;
|
|
162
162
|
this._options = {};
|
|
163
|
+
this.unnamedCaptureMapping = new Map();
|
|
164
|
+
this.patternId = Pattern.nextPatternId++;
|
|
165
|
+
// Build mapping for unnamed captures (unnamed_N -> _X)
|
|
166
|
+
let unnamedIndex = 1;
|
|
167
|
+
for (const cap of captures) {
|
|
168
|
+
if (cap && typeof cap === 'object' && 'getName' in cap) {
|
|
169
|
+
const name = cap.getName();
|
|
170
|
+
if (name && name.startsWith('unnamed_')) {
|
|
171
|
+
this.unnamedCaptureMapping.set(name, `_${unnamedIndex}`);
|
|
172
|
+
unnamedIndex++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
163
176
|
}
|
|
164
177
|
/**
|
|
165
178
|
* Configures this pattern with additional options.
|
|
@@ -168,25 +181,98 @@ class Pattern {
|
|
|
168
181
|
* @returns This pattern for method chaining
|
|
169
182
|
*
|
|
170
183
|
* @example
|
|
171
|
-
* pattern`
|
|
184
|
+
* pattern`forwardRef((${props}, ${ref}) => ${body})`
|
|
172
185
|
* .configure({
|
|
173
|
-
*
|
|
174
|
-
* dependencies: {
|
|
186
|
+
* context: ['import { forwardRef } from "react"'],
|
|
187
|
+
* dependencies: {'@types/react': '^18.0.0'}
|
|
175
188
|
* })
|
|
176
189
|
*/
|
|
177
190
|
configure(options) {
|
|
178
191
|
this._options = Object.assign(Object.assign({}, this._options), options);
|
|
192
|
+
// Invalidate cache when configuration changes
|
|
193
|
+
this._cachedAstPattern = undefined;
|
|
179
194
|
return this;
|
|
180
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Gets the AST pattern for this pattern, using two-level caching:
|
|
198
|
+
* 1. Instance-level cache (fastest - this pattern instance)
|
|
199
|
+
* 2. Global LRU cache (fast - shared across pattern instances with same code)
|
|
200
|
+
* 3. Compute via TemplateProcessor (slow - parse and process)
|
|
201
|
+
*
|
|
202
|
+
* @returns The cached or newly computed pattern AST
|
|
203
|
+
* @internal
|
|
204
|
+
*/
|
|
205
|
+
getAstPattern() {
|
|
206
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
207
|
+
// Level 1: Instance cache (fastest path)
|
|
208
|
+
if (this._cachedAstPattern) {
|
|
209
|
+
return this._cachedAstPattern;
|
|
210
|
+
}
|
|
211
|
+
// Generate cache key for global lookup
|
|
212
|
+
// Include raw code values in the key since they affect the generated AST
|
|
213
|
+
const contextStatements = this._options.context || this._options.imports || [];
|
|
214
|
+
const capturesKey = this.captures.map(c => {
|
|
215
|
+
if (c instanceof capture_1.RawCode || (c && typeof c === 'object' && c[capture_1.RAW_CODE_SYMBOL])) {
|
|
216
|
+
return `raw:${c.code}`;
|
|
217
|
+
}
|
|
218
|
+
return c.getName();
|
|
219
|
+
}).join(',');
|
|
220
|
+
const cacheKey = (0, utils_1.generateCacheKey)(this.templateParts, capturesKey, contextStatements, this._options.dependencies || {});
|
|
221
|
+
// Level 2: Global cache (fast path - shared with Template)
|
|
222
|
+
const cached = utils_1.globalAstCache.get(cacheKey);
|
|
223
|
+
if (cached) {
|
|
224
|
+
this._cachedAstPattern = cached;
|
|
225
|
+
return cached;
|
|
226
|
+
}
|
|
227
|
+
// Level 3: Compute via TemplateEngine (slow path)
|
|
228
|
+
const result = yield engine_1.TemplateEngine.getPatternTree(this.templateParts, this.captures, contextStatements, this._options.dependencies || {});
|
|
229
|
+
// Cache in both levels
|
|
230
|
+
utils_1.globalAstCache.set(cacheKey, result);
|
|
231
|
+
this._cachedAstPattern = result;
|
|
232
|
+
return result;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
181
235
|
/**
|
|
182
236
|
* Creates a matcher for this pattern against a specific AST node.
|
|
183
237
|
*
|
|
184
238
|
* @param ast The AST node to match against
|
|
185
|
-
* @
|
|
239
|
+
* @param cursor Optional cursor at the node's position in a larger tree. Used for context-aware
|
|
240
|
+
* capture constraints to navigate to parent nodes. If omitted, a cursor will be
|
|
241
|
+
* created at the ast root, allowing constraints to navigate within the matched subtree.
|
|
242
|
+
* @param options Optional match options (e.g., debug flag)
|
|
243
|
+
* @returns A MatchResult if the pattern matches, undefined otherwise
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* // Normal match
|
|
248
|
+
* const match = await pattern.match(node);
|
|
249
|
+
*
|
|
250
|
+
* // Debug this specific call
|
|
251
|
+
* const match = await pattern.match(node, cursor, { debug: true });
|
|
252
|
+
* ```
|
|
186
253
|
*/
|
|
187
|
-
match(ast) {
|
|
254
|
+
match(ast, cursor, options) {
|
|
188
255
|
return __awaiter(this, void 0, void 0, function* () {
|
|
189
|
-
|
|
256
|
+
// Three-level precedence: call > pattern > global
|
|
257
|
+
const debugEnabled = (options === null || options === void 0 ? void 0 : options.debug) !== undefined
|
|
258
|
+
? options.debug // 1. Explicit call-level (true OR false)
|
|
259
|
+
: (this._options.debug !== undefined
|
|
260
|
+
? this._options.debug // 2. Explicit pattern-level
|
|
261
|
+
: process.env.PATTERN_DEBUG === 'true'); // 3. Global
|
|
262
|
+
if (debugEnabled) {
|
|
263
|
+
// Use matchWithExplanation and log the result
|
|
264
|
+
const result = yield this.matchWithExplanation(ast, cursor);
|
|
265
|
+
yield this.logMatchResult(ast, cursor, result);
|
|
266
|
+
if (result.matched) {
|
|
267
|
+
// result.result is the MatchResult class instance
|
|
268
|
+
return result.result;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Fast path - no debug
|
|
275
|
+
const matcher = new Matcher(this, ast, cursor);
|
|
190
276
|
const success = yield matcher.matches();
|
|
191
277
|
if (!success) {
|
|
192
278
|
return undefined;
|
|
@@ -196,8 +282,245 @@ class Pattern {
|
|
|
196
282
|
return new MatchResult(new Map(storage));
|
|
197
283
|
});
|
|
198
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Formats and logs the match result to stderr.
|
|
287
|
+
* @private
|
|
288
|
+
*/
|
|
289
|
+
logMatchResult(ast, cursor, result) {
|
|
290
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
291
|
+
const patternSource = this.getPatternSource();
|
|
292
|
+
const patternId = `Pattern #${this.patternId}`;
|
|
293
|
+
const nodeKind = ast.kind || 'unknown';
|
|
294
|
+
// Format kind: extract short name (e.g., "org.openrewrite.java.tree.J$Binary" -> "J$Binary")
|
|
295
|
+
const shortKind = typeof nodeKind === 'string'
|
|
296
|
+
? nodeKind.split('.').pop() || nodeKind
|
|
297
|
+
: nodeKind;
|
|
298
|
+
// First, log the pattern source
|
|
299
|
+
console.error(`[${patternId}] ${patternSource}`);
|
|
300
|
+
// Build the complete match result message
|
|
301
|
+
const lines = [];
|
|
302
|
+
// Print the target tree being matched
|
|
303
|
+
let treeStr;
|
|
304
|
+
try {
|
|
305
|
+
const printer = print_1.TreePrinters.printer(index_1.JS.Kind.CompilationUnit);
|
|
306
|
+
treeStr = yield printer.print(ast);
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
treeStr = '(tree printing unavailable)';
|
|
310
|
+
}
|
|
311
|
+
if (result.matched) {
|
|
312
|
+
// Success case - result first, then tree, then captures
|
|
313
|
+
lines.push(`[${patternId}] ✅ SUCCESS matching against ${shortKind}:`);
|
|
314
|
+
treeStr.split('\n').forEach(line => lines.push(`[${patternId}] ${line}`));
|
|
315
|
+
// Log captured values
|
|
316
|
+
if (result.result) {
|
|
317
|
+
const storage = result.result.storage;
|
|
318
|
+
if (storage && storage.size > 0) {
|
|
319
|
+
for (const [name, value] of storage) {
|
|
320
|
+
const extractedValue = result.result.extractElements(value);
|
|
321
|
+
const valueStr = this.formatCapturedValue(extractedValue);
|
|
322
|
+
const displayName = this.unnamedCaptureMapping.get(name) || name;
|
|
323
|
+
lines.push(`[${patternId}] Captured '${displayName}': ${valueStr}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
// Failure case - result first, then tree, then explanation
|
|
330
|
+
lines.push(`[${patternId}] ❌ FAILED matching against ${shortKind}:`);
|
|
331
|
+
treeStr.split('\n').forEach(line => lines.push(`[${patternId}] ${line}`));
|
|
332
|
+
const explanation = result.explanation;
|
|
333
|
+
if (explanation) {
|
|
334
|
+
// Always show path, even if empty, to make it clear where the mismatch occurred
|
|
335
|
+
const compactedPath = this.compactPath(explanation.path);
|
|
336
|
+
const pathStr = compactedPath.length > 0 ? compactedPath.join(' → ') : '';
|
|
337
|
+
lines.push(`[${patternId}] At path: [${pathStr}]`);
|
|
338
|
+
lines.push(`[${patternId}] Reason: ${explanation.reason}`);
|
|
339
|
+
lines.push(`[${patternId}] Expected: ${explanation.expected}`);
|
|
340
|
+
lines.push(`[${patternId}] Actual: ${explanation.actual}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Single console.error call with all lines joined
|
|
344
|
+
console.error(lines.join('\n'));
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Compacts array index navigations into the previous path element.
|
|
349
|
+
* For example: ['J$VariableDeclarations#variables', '0'] → ['J$VariableDeclarations#variables[0]']
|
|
350
|
+
* @private
|
|
351
|
+
*/
|
|
352
|
+
compactPath(path) {
|
|
353
|
+
const compacted = [];
|
|
354
|
+
let i = 0;
|
|
355
|
+
while (i < path.length) {
|
|
356
|
+
const current = path[i];
|
|
357
|
+
// Check if current element is itself a numeric index
|
|
358
|
+
if (/^\d+$/.test(current)) {
|
|
359
|
+
// This is a bare numeric index - shouldn't normally happen
|
|
360
|
+
// If we have a previous element, append to it
|
|
361
|
+
if (compacted.length > 0) {
|
|
362
|
+
compacted[compacted.length - 1] += `[${current}]`;
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
// No previous element to attach to - this is an error in path construction
|
|
366
|
+
// Skip it to avoid bare [0] in output
|
|
367
|
+
console.warn(`Warning: Path starts with numeric index '${current}' - skipping`);
|
|
368
|
+
}
|
|
369
|
+
i++;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
// Look ahead to collect consecutive numeric indices
|
|
373
|
+
let j = i + 1;
|
|
374
|
+
const indices = [];
|
|
375
|
+
while (j < path.length && /^\d+$/.test(path[j])) {
|
|
376
|
+
indices.push(path[j]);
|
|
377
|
+
j++;
|
|
378
|
+
}
|
|
379
|
+
// If we found numeric indices, append them to current element
|
|
380
|
+
if (indices.length > 0) {
|
|
381
|
+
compacted.push(current + indices.map(idx => `[${idx}]`).join(''));
|
|
382
|
+
i = j; // Skip the indices we just processed
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
compacted.push(current);
|
|
386
|
+
i++;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return compacted;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Gets the source code representation of this pattern for logging.
|
|
393
|
+
* @private
|
|
394
|
+
*/
|
|
395
|
+
getPatternSource() {
|
|
396
|
+
// Reconstruct pattern source from template parts
|
|
397
|
+
let source = '';
|
|
398
|
+
for (let i = 0; i < this.templateParts.length; i++) {
|
|
399
|
+
source += this.templateParts[i];
|
|
400
|
+
if (i < this.captures.length) {
|
|
401
|
+
const cap = this.captures[i];
|
|
402
|
+
// Skip raw code
|
|
403
|
+
if (cap instanceof capture_1.RawCode || (cap && typeof cap === 'object' && cap[capture_1.RAW_CODE_SYMBOL])) {
|
|
404
|
+
source += '${raw(...)}';
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
// Show capture name or placeholder
|
|
408
|
+
const name = cap[capture_1.CAPTURE_NAME_SYMBOL];
|
|
409
|
+
if (cap && typeof cap === 'object' && name) {
|
|
410
|
+
// Use mapped name for unnamed captures, or original name
|
|
411
|
+
const displayName = this.unnamedCaptureMapping.get(name) || name;
|
|
412
|
+
source += `\${${displayName}}`;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
source += '${...}';
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return source;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Formats a captured value for logging.
|
|
423
|
+
* @private
|
|
424
|
+
*/
|
|
425
|
+
formatCapturedValue(value) {
|
|
426
|
+
if (value === null)
|
|
427
|
+
return 'null';
|
|
428
|
+
if (value === undefined)
|
|
429
|
+
return 'undefined';
|
|
430
|
+
// Check if it's an array (variadic capture)
|
|
431
|
+
if (Array.isArray(value)) {
|
|
432
|
+
if (value.length === 0)
|
|
433
|
+
return '[]';
|
|
434
|
+
const items = value.slice(0, 3).map(v => this.formatSingleValue(v));
|
|
435
|
+
const suffix = value.length > 3 ? `, ... (${value.length} total)` : '';
|
|
436
|
+
return `[${items.join(', ')}${suffix}]`;
|
|
437
|
+
}
|
|
438
|
+
return this.formatSingleValue(value);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Formats a single AST node for logging.
|
|
442
|
+
* @private
|
|
443
|
+
*/
|
|
444
|
+
formatSingleValue(value) {
|
|
445
|
+
if (!value || typeof value !== 'object') {
|
|
446
|
+
return String(value);
|
|
447
|
+
}
|
|
448
|
+
const kind = value.kind;
|
|
449
|
+
if (!kind)
|
|
450
|
+
return String(value);
|
|
451
|
+
// Extract simple kind name (last segment)
|
|
452
|
+
const kindStr = kind.split('.').pop();
|
|
453
|
+
// For literals, show the value
|
|
454
|
+
if (kindStr === 'Literal' && value.value !== undefined) {
|
|
455
|
+
const litValue = typeof value.value === 'string'
|
|
456
|
+
? `"${value.value}"`
|
|
457
|
+
: String(value.value);
|
|
458
|
+
return `${kindStr}(${litValue})`;
|
|
459
|
+
}
|
|
460
|
+
// For identifiers, show the name
|
|
461
|
+
if (kindStr === 'Identifier' && value.simpleName) {
|
|
462
|
+
return `${kindStr}(${value.simpleName})`;
|
|
463
|
+
}
|
|
464
|
+
// Default: just the kind
|
|
465
|
+
return kindStr;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Matches a pattern against an AST node with detailed debug information.
|
|
469
|
+
* Part of Layer 2 (Public API).
|
|
470
|
+
*
|
|
471
|
+
* This method always enables debug logging and returns detailed information about
|
|
472
|
+
* the match attempt, including:
|
|
473
|
+
* - Whether the pattern matched
|
|
474
|
+
* - Captured nodes (if matched)
|
|
475
|
+
* - Explanation of failure (if not matched)
|
|
476
|
+
* - Debug log entries showing the matching process
|
|
477
|
+
*
|
|
478
|
+
* @param ast The AST node to match against
|
|
479
|
+
* @param cursor Optional cursor at the node's position in a larger tree
|
|
480
|
+
* @param debugOptions Optional debug options (defaults to all logging enabled)
|
|
481
|
+
* @returns Detailed result with debug information
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* const x = capture('x');
|
|
485
|
+
* const pat = pattern`console.log(${x})`;
|
|
486
|
+
* const attempt = await pat.matchWithExplanation(node);
|
|
487
|
+
* if (attempt.matched) {
|
|
488
|
+
* console.log('Matched!');
|
|
489
|
+
* console.log('Captured x:', attempt.result.get('x'));
|
|
490
|
+
* } else {
|
|
491
|
+
* console.log('Failed:', attempt.explanation);
|
|
492
|
+
* console.log('Debug log:', attempt.debugLog);
|
|
493
|
+
* }
|
|
494
|
+
*/
|
|
495
|
+
matchWithExplanation(ast, cursor, debugOptions) {
|
|
496
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
497
|
+
// Default to full debug logging if not specified
|
|
498
|
+
const options = Object.assign({ enabled: true, logComparison: true, logConstraints: true }, debugOptions);
|
|
499
|
+
const matcher = new Matcher(this, ast, cursor, options);
|
|
500
|
+
const success = yield matcher.matches();
|
|
501
|
+
if (success) {
|
|
502
|
+
// Match succeeded - return MatchResult with debug info
|
|
503
|
+
const storage = matcher.storage;
|
|
504
|
+
const matchResult = new MatchResult(new Map(storage));
|
|
505
|
+
return {
|
|
506
|
+
matched: true,
|
|
507
|
+
result: matchResult,
|
|
508
|
+
debugLog: matcher.getDebugLog()
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
// Match failed - return explanation
|
|
513
|
+
return {
|
|
514
|
+
matched: false,
|
|
515
|
+
explanation: matcher.getExplanation(),
|
|
516
|
+
debugLog: matcher.getDebugLog()
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
199
521
|
}
|
|
200
522
|
exports.Pattern = Pattern;
|
|
523
|
+
Pattern.nextPatternId = 1;
|
|
201
524
|
/**
|
|
202
525
|
* Result of a successful pattern match containing captured values.
|
|
203
526
|
*
|
|
@@ -291,12 +614,19 @@ class Matcher {
|
|
|
291
614
|
*
|
|
292
615
|
* @param pattern The pattern to match
|
|
293
616
|
* @param ast The AST node to match against
|
|
617
|
+
* @param cursor Optional cursor at the AST node's position
|
|
618
|
+
* @param debugOptions Optional debug options for instrumentation
|
|
294
619
|
*/
|
|
295
|
-
constructor(pattern, ast) {
|
|
620
|
+
constructor(pattern, ast, cursor, debugOptions) {
|
|
296
621
|
this.pattern = pattern;
|
|
297
622
|
this.ast = ast;
|
|
298
623
|
// Unified storage: holds J for scalar captures, J.RightPadded<J>[] or J[] for variadic captures
|
|
299
624
|
this.storage = new Map();
|
|
625
|
+
this.debugLog = [];
|
|
626
|
+
this.currentPath = [];
|
|
627
|
+
// If no cursor provided, create one at the ast root so constraints can navigate up
|
|
628
|
+
this.cursor = cursor !== null && cursor !== void 0 ? cursor : new __1.Cursor(ast, undefined);
|
|
629
|
+
this.debugOptions = debugOptions !== null && debugOptions !== void 0 ? debugOptions : {};
|
|
300
630
|
}
|
|
301
631
|
/**
|
|
302
632
|
* Checks if the pattern matches the AST node.
|
|
@@ -306,10 +636,7 @@ class Matcher {
|
|
|
306
636
|
matches() {
|
|
307
637
|
return __awaiter(this, void 0, void 0, function* () {
|
|
308
638
|
if (!this.patternAst) {
|
|
309
|
-
|
|
310
|
-
const contextStatements = this.pattern.options.context || this.pattern.options.imports || [];
|
|
311
|
-
const templateProcessor = new TemplateProcessor(this.pattern.templateParts, this.pattern.captures, contextStatements, this.pattern.options.dependencies || {});
|
|
312
|
-
this.patternAst = yield templateProcessor.toAstPattern();
|
|
639
|
+
this.patternAst = yield this.pattern.getAstPattern();
|
|
313
640
|
}
|
|
314
641
|
return this.matchNode(this.patternAst, this.ast);
|
|
315
642
|
});
|
|
@@ -350,6 +677,70 @@ class Matcher {
|
|
|
350
677
|
// Scalar element
|
|
351
678
|
return value;
|
|
352
679
|
}
|
|
680
|
+
/**
|
|
681
|
+
* Logs a debug message if debugging is enabled.
|
|
682
|
+
* Part of Layer 1 (Core Instrumentation).
|
|
683
|
+
*
|
|
684
|
+
* @param level The severity level
|
|
685
|
+
* @param scope The scope/category
|
|
686
|
+
* @param message The message to log
|
|
687
|
+
* @param data Optional data to include
|
|
688
|
+
*/
|
|
689
|
+
log(level, scope, message, data) {
|
|
690
|
+
if (!this.debugOptions.enabled)
|
|
691
|
+
return;
|
|
692
|
+
// Filter by scope if specific logging is requested
|
|
693
|
+
if (scope === 'comparison' && !this.debugOptions.logComparison)
|
|
694
|
+
return;
|
|
695
|
+
if (scope === 'constraint' && !this.debugOptions.logConstraints)
|
|
696
|
+
return;
|
|
697
|
+
this.debugLog.push({
|
|
698
|
+
level,
|
|
699
|
+
scope,
|
|
700
|
+
path: [...this.currentPath],
|
|
701
|
+
message,
|
|
702
|
+
data
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Sets the explanation for why the pattern match failed.
|
|
707
|
+
* Only sets the first failure (most relevant).
|
|
708
|
+
* Part of Layer 1 (Core Instrumentation).
|
|
709
|
+
*
|
|
710
|
+
* @param reason The reason for failure
|
|
711
|
+
* @param expected Human-readable description of what was expected
|
|
712
|
+
* @param actual Human-readable description of what was found
|
|
713
|
+
* @param details Optional additional context
|
|
714
|
+
*/
|
|
715
|
+
setExplanation(reason, expected, actual, details) {
|
|
716
|
+
// Only set the first failure (most relevant)
|
|
717
|
+
if (this.explanation)
|
|
718
|
+
return;
|
|
719
|
+
this.explanation = {
|
|
720
|
+
reason,
|
|
721
|
+
path: [...this.currentPath],
|
|
722
|
+
expected,
|
|
723
|
+
actual,
|
|
724
|
+
details
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Pushes a path component onto the current path.
|
|
729
|
+
* Used to track where in the AST tree we are during matching.
|
|
730
|
+
* Part of Layer 1 (Core Instrumentation).
|
|
731
|
+
*
|
|
732
|
+
* @param name The path component to push
|
|
733
|
+
*/
|
|
734
|
+
pushPath(name) {
|
|
735
|
+
this.currentPath.push(name);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Pops the last path component from the current path.
|
|
739
|
+
* Part of Layer 1 (Core Instrumentation).
|
|
740
|
+
*/
|
|
741
|
+
popPath() {
|
|
742
|
+
this.currentPath.pop();
|
|
743
|
+
}
|
|
353
744
|
/**
|
|
354
745
|
* Matches a pattern node against a target node.
|
|
355
746
|
*
|
|
@@ -359,67 +750,105 @@ class Matcher {
|
|
|
359
750
|
*/
|
|
360
751
|
matchNode(pattern, target) {
|
|
361
752
|
return __awaiter(this, void 0, void 0, function* () {
|
|
362
|
-
var _a;
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
//
|
|
368
|
-
if (pattern.kind !== target.kind) {
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
// Use the pattern matching comparator with configured lenient type matching
|
|
372
|
-
// Default to true for backward compatibility with existing patterns
|
|
753
|
+
var _a, _b, _c;
|
|
754
|
+
// Always delegate to the comparator visitor, which handles:
|
|
755
|
+
// - Capture detection and constraint evaluation
|
|
756
|
+
// - Kind checking
|
|
757
|
+
// - Deep structural comparison
|
|
758
|
+
// This centralizes all matching logic in one place
|
|
373
759
|
const lenientTypeMatching = (_a = this.pattern.options.lenientTypeMatching) !== null && _a !== void 0 ? _a : true;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
760
|
+
// Factory pattern: instantiate debug or production comparator
|
|
761
|
+
// Zero cost in production - DebugPatternMatchingComparator is never instantiated
|
|
762
|
+
const matcherCallbacks = {
|
|
763
|
+
handleCapture: (capture, t, w) => this.handleCapture(capture, t, w),
|
|
764
|
+
handleVariadicCapture: (capture, ts, ws) => this.handleVariadicCapture(capture, ts, ws),
|
|
377
765
|
saveState: () => this.saveState(),
|
|
378
|
-
restoreState: (state) => this.restoreState(state)
|
|
379
|
-
|
|
380
|
-
|
|
766
|
+
restoreState: (state) => this.restoreState(state),
|
|
767
|
+
// Debug callbacks (Layer 1) - grouped together, always present or absent
|
|
768
|
+
debug: this.debugOptions.enabled ? {
|
|
769
|
+
log: (level, scope, message, data) => this.log(level, scope, message, data),
|
|
770
|
+
setExplanation: (reason, expected, actual, details) => this.setExplanation(reason, expected, actual, details),
|
|
771
|
+
getExplanation: () => this.explanation,
|
|
772
|
+
restoreExplanation: (explanation) => { this.explanation = explanation; },
|
|
773
|
+
clearExplanation: () => { this.explanation = undefined; },
|
|
774
|
+
pushPath: (name) => this.pushPath(name),
|
|
775
|
+
popPath: () => this.popPath()
|
|
776
|
+
} : undefined
|
|
777
|
+
};
|
|
778
|
+
const comparator = this.debugOptions.enabled
|
|
779
|
+
? new comparator_1.DebugPatternMatchingComparator(matcherCallbacks, lenientTypeMatching)
|
|
780
|
+
: new comparator_1.PatternMatchingComparator(matcherCallbacks, lenientTypeMatching);
|
|
781
|
+
// Pass cursors to allow constraints to navigate to root
|
|
782
|
+
// Pattern cursor is undefined (pattern is the root), target cursor is provided by user
|
|
783
|
+
const result = yield comparator.compare(pattern, target, undefined, this.cursor);
|
|
784
|
+
// If match failed and no explanation was set, provide a generic one
|
|
785
|
+
if (!result && this.debugOptions.enabled && !this.explanation) {
|
|
786
|
+
const patternKind = ((_b = pattern.kind) === null || _b === void 0 ? void 0 : _b.split('.').pop()) || 'unknown';
|
|
787
|
+
const targetKind = ((_c = target.kind) === null || _c === void 0 ? void 0 : _c.split('.').pop()) || 'unknown';
|
|
788
|
+
this.setExplanation('structural-mismatch', `Pattern node of type ${patternKind}`, `Target node of type ${targetKind}`, 'Nodes did not match structurally');
|
|
789
|
+
}
|
|
790
|
+
return result;
|
|
381
791
|
});
|
|
382
792
|
}
|
|
383
793
|
/**
|
|
384
|
-
* Saves the current state
|
|
794
|
+
* Saves the current state for backtracking.
|
|
795
|
+
* Includes both capture storage AND debug state (explanation, log, path).
|
|
385
796
|
*
|
|
386
797
|
* @returns A snapshot of the current state
|
|
387
798
|
*/
|
|
388
799
|
saveState() {
|
|
389
|
-
return
|
|
800
|
+
return {
|
|
801
|
+
storage: new Map(this.storage),
|
|
802
|
+
debugState: this.debugOptions.enabled ? {
|
|
803
|
+
explanation: this.explanation,
|
|
804
|
+
logLength: this.debugLog.length,
|
|
805
|
+
path: [...this.currentPath]
|
|
806
|
+
} : undefined
|
|
807
|
+
};
|
|
390
808
|
}
|
|
391
809
|
/**
|
|
392
810
|
* Restores a previously saved state for backtracking.
|
|
811
|
+
* Restores both capture storage AND debug state.
|
|
393
812
|
*
|
|
394
813
|
* @param state The state to restore
|
|
395
814
|
*/
|
|
396
815
|
restoreState(state) {
|
|
816
|
+
// Restore capture storage
|
|
397
817
|
this.storage.clear();
|
|
398
|
-
state.forEach((value, key) => this.storage.set(key, value));
|
|
818
|
+
state.storage.forEach((value, key) => this.storage.set(key, value));
|
|
819
|
+
// Restore debug state if it was saved
|
|
820
|
+
if (state.debugState) {
|
|
821
|
+
// Restore explanation to the saved state
|
|
822
|
+
// This clears any explanations set during failed exploratory attempts (like pivot detection)
|
|
823
|
+
this.explanation = state.debugState.explanation;
|
|
824
|
+
// Truncate debug log to saved length (remove entries added during failed attempt)
|
|
825
|
+
this.debugLog.length = state.debugState.logLength;
|
|
826
|
+
// Restore path
|
|
827
|
+
this.currentPath.length = 0;
|
|
828
|
+
this.currentPath.push(...state.debugState.path);
|
|
829
|
+
}
|
|
399
830
|
}
|
|
400
831
|
/**
|
|
401
832
|
* Handles a capture placeholder.
|
|
402
833
|
*
|
|
403
|
-
* @param
|
|
834
|
+
* @param capture The pattern node capture
|
|
404
835
|
* @param target The target node
|
|
405
836
|
* @param wrapper Optional wrapper containing the target (for preserving markers)
|
|
406
837
|
* @returns true if the capture is successful, false otherwise
|
|
407
838
|
*/
|
|
408
|
-
handleCapture(
|
|
409
|
-
var _a
|
|
410
|
-
const captureName =
|
|
839
|
+
handleCapture(capture, target, wrapper) {
|
|
840
|
+
var _a;
|
|
841
|
+
const captureName = capture.captureName;
|
|
411
842
|
if (!captureName) {
|
|
412
843
|
return false;
|
|
413
844
|
}
|
|
414
|
-
// Find the original capture object to get
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
845
|
+
// Find the original capture object to get capturing flag
|
|
846
|
+
// Note: Constraints are now evaluated in PatternMatchingComparator where cursor is correctly positioned
|
|
847
|
+
// Filter out RawCode since it doesn't have getName()
|
|
848
|
+
const captureObj = this.pattern.captures.find(c => !(c instanceof capture_1.RawCode || (c && typeof c === 'object' && c[capture_1.RAW_CODE_SYMBOL])) &&
|
|
849
|
+
c.getName() === captureName);
|
|
421
850
|
// Only store the binding if this is a capturing placeholder
|
|
422
|
-
const capturing = (
|
|
851
|
+
const capturing = (_a = captureObj === null || captureObj === void 0 ? void 0 : captureObj[capture_1.CAPTURE_CAPTURING_SYMBOL]) !== null && _a !== void 0 ? _a : true;
|
|
423
852
|
if (capturing) {
|
|
424
853
|
// Store wrapper if available (preserves markers), otherwise store element
|
|
425
854
|
this.storage.set(captureName, wrapper !== null && wrapper !== void 0 ? wrapper : target);
|
|
@@ -429,26 +858,24 @@ class Matcher {
|
|
|
429
858
|
/**
|
|
430
859
|
* Handles a variadic capture placeholder.
|
|
431
860
|
*
|
|
432
|
-
* @param
|
|
861
|
+
* @param capture The pattern node capture (the variadic capture)
|
|
433
862
|
* @param targets The target nodes that were matched
|
|
434
863
|
* @param wrappers Optional wrappers to preserve markers
|
|
435
864
|
* @returns true if the capture is successful, false otherwise
|
|
436
865
|
*/
|
|
437
|
-
handleVariadicCapture(
|
|
438
|
-
var _a
|
|
439
|
-
const captureName =
|
|
866
|
+
handleVariadicCapture(capture, targets, wrappers) {
|
|
867
|
+
var _a;
|
|
868
|
+
const captureName = capture.captureName;
|
|
440
869
|
if (!captureName) {
|
|
441
870
|
return false;
|
|
442
871
|
}
|
|
443
|
-
// Find the original capture object to get
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
return false;
|
|
449
|
-
}
|
|
872
|
+
// Find the original capture object to get capturing flag
|
|
873
|
+
// Note: Constraints are now evaluated in PatternMatchingComparator where cursor is correctly positioned
|
|
874
|
+
// Filter out RawCode since it doesn't have getName()
|
|
875
|
+
const captureObj = this.pattern.captures.find(c => !(c instanceof capture_1.RawCode || (c && typeof c === 'object' && c[capture_1.RAW_CODE_SYMBOL])) &&
|
|
876
|
+
c.getName() === captureName);
|
|
450
877
|
// Only store the binding if this is a capturing placeholder
|
|
451
|
-
const capturing = (
|
|
878
|
+
const capturing = (_a = captureObj === null || captureObj === void 0 ? void 0 : captureObj[capture_1.CAPTURE_CAPTURING_SYMBOL]) !== null && _a !== void 0 ? _a : true;
|
|
452
879
|
if (capturing) {
|
|
453
880
|
// Store the richest representation: wrappers if available, otherwise elements
|
|
454
881
|
if (wrappers && wrappers.length > 0) {
|
|
@@ -460,222 +887,66 @@ class Matcher {
|
|
|
460
887
|
}
|
|
461
888
|
return true;
|
|
462
889
|
}
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Processor for template strings.
|
|
466
|
-
* Converts a template string with captures into an AST pattern.
|
|
467
|
-
*/
|
|
468
|
-
class TemplateProcessor {
|
|
469
890
|
/**
|
|
470
|
-
*
|
|
891
|
+
* Gets the debug log entries collected during matching.
|
|
892
|
+
* Part of Layer 2 (Public API).
|
|
471
893
|
*
|
|
472
|
-
* @
|
|
473
|
-
* @param captures The captures between the string parts (can be Capture or Any)
|
|
474
|
-
* @param contextStatements Context declarations (imports, types, etc.) to prepend for type attribution
|
|
475
|
-
* @param dependencies NPM dependencies for type attribution
|
|
894
|
+
* @returns The debug log entries, or undefined if debug wasn't enabled
|
|
476
895
|
*/
|
|
477
|
-
|
|
478
|
-
this.
|
|
479
|
-
this.captures = captures;
|
|
480
|
-
this.contextStatements = contextStatements;
|
|
481
|
-
this.dependencies = dependencies;
|
|
896
|
+
getDebugLog() {
|
|
897
|
+
return this.debugOptions.enabled ? [...this.debugLog] : undefined;
|
|
482
898
|
}
|
|
483
899
|
/**
|
|
484
|
-
*
|
|
900
|
+
* Gets the explanation for why the match failed.
|
|
901
|
+
* Part of Layer 2 (Public API).
|
|
485
902
|
*
|
|
486
|
-
* @returns
|
|
903
|
+
* @returns The match explanation, or undefined if match succeeded or no explanation available
|
|
487
904
|
*/
|
|
488
|
-
|
|
489
|
-
return
|
|
490
|
-
// Combine template parts and placeholders
|
|
491
|
-
const templateString = this.buildTemplateString();
|
|
492
|
-
// Use cache to get or parse the compilation unit
|
|
493
|
-
const cu = yield utils_1.templateCache.getOrParse(templateString, this.captures, this.contextStatements, this.dependencies);
|
|
494
|
-
// Extract the relevant part of the AST
|
|
495
|
-
return this.extractPatternFromAst(cu);
|
|
496
|
-
});
|
|
905
|
+
getExplanation() {
|
|
906
|
+
return this.explanation;
|
|
497
907
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
let result = '';
|
|
506
|
-
for (let i = 0; i < this.templateParts.length; i++) {
|
|
507
|
-
result += this.templateParts[i];
|
|
508
|
-
if (i < this.captures.length) {
|
|
509
|
-
const capture = this.captures[i];
|
|
510
|
-
// Use symbol to access capture name without triggering Proxy
|
|
511
|
-
const captureName = capture[capture_1.CAPTURE_NAME_SYMBOL] || capture.getName();
|
|
512
|
-
result += utils_1.PlaceholderUtils.createCapture(captureName, undefined);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
// Check if this looks like a block pattern (starts with { and contains statement keywords)
|
|
516
|
-
const trimmed = result.trim();
|
|
517
|
-
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
518
|
-
// Check for statement keywords that indicate this is a block, not an object literal
|
|
519
|
-
const hasStatementKeywords = /\b(return|if|for|while|do|switch|try|throw|break|continue|const|let|var|function|class)\b/.test(result);
|
|
520
|
-
if (hasStatementKeywords) {
|
|
521
|
-
// Wrap in a function to ensure it parses as a block
|
|
522
|
-
return `function __PATTERN__() ${result}`;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
return result;
|
|
526
|
-
}
|
|
527
|
-
/**
|
|
528
|
-
* Extracts the pattern from the parsed AST.
|
|
529
|
-
*
|
|
530
|
-
* @param cu The compilation unit
|
|
531
|
-
* @returns The extracted pattern
|
|
532
|
-
*/
|
|
533
|
-
extractPatternFromAst(cu) {
|
|
534
|
-
var _a, _b;
|
|
535
|
-
// Skip context statements to get to the actual pattern code
|
|
536
|
-
const patternStatementIndex = this.contextStatements.length;
|
|
537
|
-
// Check if we have any statements at the pattern index
|
|
538
|
-
if (!cu.statements || patternStatementIndex >= cu.statements.length) {
|
|
539
|
-
// If there's no statement at the index, but we have exactly one statement
|
|
540
|
-
// and it's a block, it might be the pattern itself (e.g., pattern`{ ... }`)
|
|
541
|
-
if (cu.statements && cu.statements.length === 1 && cu.statements[0].element.kind === java_1.J.Kind.Block) {
|
|
542
|
-
return this.attachCaptureMarkers(cu.statements[0].element);
|
|
543
|
-
}
|
|
544
|
-
throw new Error(`No statement found at index ${patternStatementIndex} in compilation unit with ${((_a = cu.statements) === null || _a === void 0 ? void 0 : _a.length) || 0} statements`);
|
|
545
|
-
}
|
|
546
|
-
// Extract the relevant part of the AST based on the template content
|
|
547
|
-
const firstStatement = cu.statements[patternStatementIndex].element;
|
|
548
|
-
let extracted;
|
|
549
|
-
// Check if this is our wrapper function for block patterns
|
|
550
|
-
if (firstStatement.kind === java_1.J.Kind.MethodDeclaration) {
|
|
551
|
-
const method = firstStatement;
|
|
552
|
-
if (((_b = method.name) === null || _b === void 0 ? void 0 : _b.simpleName) === '__PATTERN__' && method.body) {
|
|
553
|
-
// Extract the block from the wrapper function
|
|
554
|
-
extracted = method.body;
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
extracted = firstStatement;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
else if (firstStatement.kind === index_1.JS.Kind.ExpressionStatement) {
|
|
561
|
-
// If the first statement is an expression statement, extract the expression
|
|
562
|
-
extracted = firstStatement.expression;
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
// Otherwise, return the statement itself
|
|
566
|
-
extracted = firstStatement;
|
|
567
|
-
}
|
|
568
|
-
// Attach CaptureMarkers to capture identifiers
|
|
569
|
-
return this.attachCaptureMarkers(extracted);
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Attaches CaptureMarkers to capture identifiers in the AST.
|
|
573
|
-
* This allows efficient capture detection without string parsing.
|
|
574
|
-
*
|
|
575
|
-
* @param ast The AST to process
|
|
576
|
-
* @returns The AST with CaptureMarkers attached
|
|
577
|
-
*/
|
|
578
|
-
attachCaptureMarkers(ast) {
|
|
579
|
-
const visited = new Set();
|
|
580
|
-
return (0, immer_1.produce)(ast, draft => {
|
|
581
|
-
this.visitAndAttachMarkers(draft, visited);
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* Recursively visits AST nodes and attaches CaptureMarkers to capture identifiers.
|
|
586
|
-
* For statement-level captures (identifiers in ExpressionStatement), the marker
|
|
587
|
-
* is attached to the ExpressionStatement itself rather than the nested identifier.
|
|
588
|
-
*
|
|
589
|
-
* @param node The node to visit
|
|
590
|
-
* @param visited Set of already visited nodes to avoid cycles
|
|
591
|
-
*/
|
|
592
|
-
visitAndAttachMarkers(node, visited) {
|
|
593
|
-
var _a, _b, _c;
|
|
594
|
-
if (!node || typeof node !== 'object' || visited.has(node)) {
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
// Mark as visited to avoid cycles
|
|
598
|
-
visited.add(node);
|
|
599
|
-
// Check if this is an ExpressionStatement containing a capture identifier
|
|
600
|
-
// For statement-level captures, we attach the marker to the ExpressionStatement itself
|
|
601
|
-
if (node.kind === index_1.JS.Kind.ExpressionStatement &&
|
|
602
|
-
((_a = node.expression) === null || _a === void 0 ? void 0 : _a.kind) === java_1.J.Kind.Identifier &&
|
|
603
|
-
((_b = node.expression.simpleName) === null || _b === void 0 ? void 0 : _b.startsWith(utils_1.PlaceholderUtils.CAPTURE_PREFIX))) {
|
|
604
|
-
const captureInfo = utils_1.PlaceholderUtils.parseCapture(node.expression.simpleName);
|
|
605
|
-
if (captureInfo) {
|
|
606
|
-
// Initialize markers on the ExpressionStatement
|
|
607
|
-
if (!node.markers) {
|
|
608
|
-
node.markers = { kind: 'org.openrewrite.marker.Markers', id: (0, uuid_1.randomId)(), markers: [] };
|
|
609
|
-
}
|
|
610
|
-
if (!node.markers.markers) {
|
|
611
|
-
node.markers.markers = [];
|
|
612
|
-
}
|
|
613
|
-
// Find the original capture object to get variadic options
|
|
614
|
-
const captureObj = this.captures.find(c => c.getName() === captureInfo.name);
|
|
615
|
-
const variadicOptions = captureObj === null || captureObj === void 0 ? void 0 : captureObj.getVariadicOptions();
|
|
616
|
-
// Add CaptureMarker to the ExpressionStatement
|
|
617
|
-
node.markers.markers.push(new utils_1.CaptureMarker(captureInfo.name, variadicOptions));
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
// For non-statement captures (expressions), attach marker to the identifier
|
|
621
|
-
else if (node.kind === java_1.J.Kind.Identifier && ((_c = node.simpleName) === null || _c === void 0 ? void 0 : _c.startsWith(utils_1.PlaceholderUtils.CAPTURE_PREFIX))) {
|
|
622
|
-
const captureInfo = utils_1.PlaceholderUtils.parseCapture(node.simpleName);
|
|
623
|
-
if (captureInfo) {
|
|
624
|
-
// Initialize markers if needed
|
|
625
|
-
if (!node.markers) {
|
|
626
|
-
node.markers = { kind: 'org.openrewrite.marker.Markers', id: (0, uuid_1.randomId)(), markers: [] };
|
|
627
|
-
}
|
|
628
|
-
if (!node.markers.markers) {
|
|
629
|
-
node.markers.markers = [];
|
|
630
|
-
}
|
|
631
|
-
// Find the original capture object to get variadic options
|
|
632
|
-
const captureObj = this.captures.find(c => c.getName() === captureInfo.name);
|
|
633
|
-
const variadicOptions = captureObj === null || captureObj === void 0 ? void 0 : captureObj.getVariadicOptions();
|
|
634
|
-
// Add CaptureMarker with variadic options if available
|
|
635
|
-
node.markers.markers.push(new utils_1.CaptureMarker(captureInfo.name, variadicOptions));
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
// Recursively visit all properties
|
|
639
|
-
for (const key in node) {
|
|
640
|
-
if (node.hasOwnProperty(key)) {
|
|
641
|
-
const value = node[key];
|
|
642
|
-
if (Array.isArray(value)) {
|
|
643
|
-
value.forEach(item => this.visitAndAttachMarkers(item, visited));
|
|
644
|
-
}
|
|
645
|
-
else if (typeof value === 'object' && value !== null) {
|
|
646
|
-
this.visitAndAttachMarkers(value, visited);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
908
|
+
}
|
|
909
|
+
// Implementation
|
|
910
|
+
function pattern(stringsOrOptions, ...captures) {
|
|
911
|
+
// Check if first arg is TemplateStringsArray (direct usage)
|
|
912
|
+
if (Array.isArray(stringsOrOptions) && 'raw' in stringsOrOptions) {
|
|
913
|
+
// Direct usage: pattern`...`
|
|
914
|
+
return createPattern(stringsOrOptions, captures, {});
|
|
650
915
|
}
|
|
916
|
+
// Options usage: pattern({ ... })`...`
|
|
917
|
+
const options = stringsOrOptions;
|
|
918
|
+
return (strings, ...caps) => {
|
|
919
|
+
return createPattern(strings, caps, options);
|
|
920
|
+
};
|
|
651
921
|
}
|
|
652
922
|
/**
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
* @param strings The string parts of the template
|
|
656
|
-
* @param captures The captures between the string parts (Capture, Any, or string names)
|
|
657
|
-
* @returns A Pattern object
|
|
658
|
-
*
|
|
659
|
-
* @example
|
|
660
|
-
* // Using the same capture multiple times for repeated patterns
|
|
661
|
-
* const expr = capture('expr');
|
|
662
|
-
* const redundantOr = pattern`${expr} || ${expr}`;
|
|
663
|
-
*
|
|
664
|
-
* @example
|
|
665
|
-
* // Using any() for non-capturing matches
|
|
666
|
-
* const pat = pattern`foo(${any()})`;
|
|
923
|
+
* Internal helper to create a Pattern instance.
|
|
924
|
+
* @private
|
|
667
925
|
*/
|
|
668
|
-
function
|
|
926
|
+
function createPattern(strings, captures, options) {
|
|
669
927
|
const capturesByName = captures.reduce((map, c) => {
|
|
928
|
+
// Skip raw code - it's not a capture
|
|
929
|
+
if (c instanceof capture_1.RawCode || (typeof c === 'object' && c && c[capture_1.RAW_CODE_SYMBOL])) {
|
|
930
|
+
return map;
|
|
931
|
+
}
|
|
670
932
|
const capture = typeof c === "string" ? new capture_1.CaptureImpl(c) : c;
|
|
671
933
|
// Use symbol to get internal name without triggering Proxy
|
|
672
934
|
const name = capture[capture_1.CAPTURE_NAME_SYMBOL] || capture.getName();
|
|
673
935
|
return map.set(name, capture);
|
|
674
936
|
}, new Map());
|
|
675
|
-
|
|
937
|
+
const pat = new Pattern(strings, captures.map(c => {
|
|
938
|
+
// Return raw code as-is
|
|
939
|
+
if (c instanceof capture_1.RawCode || (typeof c === 'object' && c && c[capture_1.RAW_CODE_SYMBOL])) {
|
|
940
|
+
return c;
|
|
941
|
+
}
|
|
676
942
|
// Use symbol to get internal name without triggering Proxy
|
|
677
943
|
const name = typeof c === "string" ? c : (c[capture_1.CAPTURE_NAME_SYMBOL] || c.getName());
|
|
678
944
|
return capturesByName.get(name);
|
|
679
945
|
}));
|
|
946
|
+
// Apply options if provided
|
|
947
|
+
if (options && Object.keys(options).length > 0) {
|
|
948
|
+
pat.configure(options);
|
|
949
|
+
}
|
|
950
|
+
return pat;
|
|
680
951
|
}
|
|
681
952
|
//# sourceMappingURL=pattern.js.map
|