diff --git a/rewrite-javascript/rewrite/CLAUDE.md b/rewrite-javascript/rewrite/CLAUDE.md index 3e814024eeb..98d1f449e70 100644 --- a/rewrite-javascript/rewrite/CLAUDE.md +++ b/rewrite-javascript/rewrite/CLAUDE.md @@ -203,4 +203,7 @@ Each language module has `rpc.ts` with a Sender (visit tree → serialize to que 4. Check `src/rpc/queue.ts` for deadlock in read/write operations ### Type Checking -Run `npm run typecheck` frequently to catch type mismatches early. +`npm run typecheck` only checks `src/` and `test/` via `tsc --noEmit`, but vitest type +definitions are not resolved by `tsc` alone. This means type errors inside test files +(e.g., incorrect generic inference on overloaded functions) can be missed locally while +CI catches them. Prefer `npm test` (typecheck + vitest) as the final check before pushing. diff --git a/rewrite-javascript/rewrite/src/javascript/templating/capture.ts b/rewrite-javascript/rewrite/src/javascript/templating/capture.ts index e8f55961303..bc443f8deac 100644 --- a/rewrite-javascript/rewrite/src/javascript/templating/capture.ts +++ b/rewrite-javascript/rewrite/src/javascript/templating/capture.ts @@ -15,7 +15,7 @@ */ import {Cursor} from '../..'; import {J, Type} from '../../java'; -import {Any, Capture, CaptureConstraintContext, CaptureOptions, ConstraintFunction, TemplateParam, VariadicOptions} from './types'; +import {Any, Capture, CaptureConstraintContext, CaptureKind, CaptureOptions, ConstraintFunction, TemplateParam, VariadicOptions} from './types'; /** * Combines multiple constraints with AND logic. @@ -73,6 +73,8 @@ export const CAPTURE_CONSTRAINT_SYMBOL = Symbol('captureConstraint'); export const CAPTURE_CAPTURING_SYMBOL = Symbol('captureCapturing'); // Symbol to access type information without triggering Proxy export const CAPTURE_TYPE_SYMBOL = Symbol('captureType'); +// Symbol to access capture kind without triggering Proxy +export const CAPTURE_KIND_SYMBOL = Symbol('captureKind'); // Symbol to identify RawCode instances export const RAW_CODE_SYMBOL = Symbol('rawCode'); @@ -83,11 +85,13 @@ export class CaptureImpl implements Capture { [CAPTURE_CONSTRAINT_SYMBOL]: ConstraintFunction | undefined; [CAPTURE_CAPTURING_SYMBOL]: boolean; [CAPTURE_TYPE_SYMBOL]: string | Type | undefined; + [CAPTURE_KIND_SYMBOL]: CaptureKind; - constructor(name: string, options?: CaptureOptions, capturing: boolean = true) { + constructor(name: string, options?: CaptureOptions & { type?: string | Type }, capturing: boolean = true, kind: CaptureKind = CaptureKind.Expression) { this.name = name; this[CAPTURE_NAME_SYMBOL] = name; this[CAPTURE_CAPTURING_SYMBOL] = capturing; + this[CAPTURE_KIND_SYMBOL] = kind; // Normalize variadic options if (options?.variadic) { @@ -106,7 +110,7 @@ export class CaptureImpl implements Capture { this[CAPTURE_CONSTRAINT_SYMBOL] = options.constraint; } - // Store type if provided + // Store type if provided (only meaningful for Expression kind) if (options?.type) { this[CAPTURE_TYPE_SYMBOL] = options.type; } @@ -135,6 +139,10 @@ export class CaptureImpl implements Capture { getType(): string | Type | undefined { return this[CAPTURE_TYPE_SYMBOL]; } + + getKind(): CaptureKind { + return this[CAPTURE_KIND_SYMBOL]; + } } export class TemplateParamImpl implements TemplateParam { @@ -309,6 +317,9 @@ function createCaptureProxy(impl: CaptureImpl): any { if (prop === CAPTURE_TYPE_SYMBOL) { return target[CAPTURE_TYPE_SYMBOL]; } + if (prop === CAPTURE_KIND_SYMBOL) { + return target[CAPTURE_KIND_SYMBOL]; + } // Support using Capture as object key via computed properties {[x]: value} if (prop === Symbol.toPrimitive || prop === 'toString' || prop === 'valueOf') { @@ -316,7 +327,7 @@ function createCaptureProxy(impl: CaptureImpl): any { } // Allow methods to be called directly on the target - if (prop === 'getName' || prop === 'isVariadic' || prop === 'getVariadicOptions' || prop === 'getConstraint' || prop === 'isCapturing' || prop === 'getType') { + if (prop === 'getName' || prop === 'isVariadic' || prop === 'getVariadicOptions' || prop === 'getConstraint' || prop === 'isCapturing' || prop === 'getType' || prop === 'getKind') { return target[prop].bind(target); } @@ -388,6 +399,17 @@ export function capture(nameOrOptions?: string | CaptureOptions): Ca // Static counter for generating unique IDs for unnamed captures capture.nextUnnamedId = 1; +// Type-only declarations so TypeScript recognizes the runtime properties +// attached to the capture function (nextUnnamedId, expr, ident, typeRef, stmt). +// The actual values are assigned imperatively below the factory definitions. +export namespace capture { + export let nextUnnamedId: number; + export let expr: typeof import('./capture').expr; + export let ident: typeof import('./capture').ident; + export let typeRef: typeof import('./capture').typeRef; + export let stmt: typeof import('./capture').stmt; +} + /** * Creates a non-capturing pattern match for use in patterns. * @@ -581,6 +603,117 @@ export function raw(code: string): RawCode { return new RawCode(code); } +/** + * Options specific to expression captures, extending CaptureOptions with type attribution. + */ +export interface ExprCaptureOptions extends CaptureOptions { + /** + * Type annotation for this expression capture. When provided, the template engine + * generates a preamble declaring the capture identifier with this type annotation, + * allowing the TypeScript parser/compiler to produce a properly type-attributed AST. + * + * Can be specified as: + * - A string type annotation (e.g., "boolean", "number", "Promise") + * - A Type instance from the AST + * + * @example + * ```typescript + * const chain = expr({ name: 'chain', type: 'Promise' }); + * pattern`${chain}.catch(err => console.log(err))` + * + * const items = expr({ name: 'items', type: 'number[]' }); + * pattern`${items}.map(x => x * 2)` + * ``` + */ + type?: string | Type; +} + +/** + * Creates an expression capture. This is the most common capture kind. + * Only expression captures support the `type` option for type attribution. + * + * @example + * const e = expr('x'); + * pattern`foo(${e})` + * + * @example + * const e = expr({ name: 'x', type: 'boolean' }); + * pattern`${e} || false` + */ +export function expr(name?: string): Capture & T; +export function expr(options: ExprCaptureOptions & { variadic?: never }): Capture & T; +export function expr(options: ExprCaptureOptions & { variadic: true | VariadicOptions }): Capture & T[]; +export function expr(nameOrOptions?: string | ExprCaptureOptions): Capture & T { + return createKindCapture(CaptureKind.Expression, nameOrOptions); +} + +/** + * Creates an identifier/name capture. + * + * @example + * const n = ident('method'); + * pattern`${n}()` + */ +export function ident(name?: string): Capture & T; +export function ident(options: CaptureOptions & { variadic?: never }): Capture & T; +export function ident(options: CaptureOptions & { variadic: true | VariadicOptions }): Capture & T[]; +export function ident(nameOrOptions?: string | CaptureOptions): Capture & T { + return createKindCapture(CaptureKind.Identifier, nameOrOptions); +} + +/** + * Creates a type reference capture. + * + * @example + * const t = typeRef('ret'); + * pattern`function foo(): ${t}` + */ +export function typeRef(name?: string): Capture & T; +export function typeRef(options: CaptureOptions & { variadic?: never }): Capture & T; +export function typeRef(options: CaptureOptions & { variadic: true | VariadicOptions }): Capture & T[]; +export function typeRef(nameOrOptions?: string | CaptureOptions): Capture & T { + return createKindCapture(CaptureKind.TypeReference, nameOrOptions); +} + +/** + * Creates a statement capture. + * + * @example + * const s = stmt('body'); + * pattern`if (cond) ${s}` + */ +export function stmt(name?: string): Capture & T; +export function stmt(options: CaptureOptions & { variadic?: never }): Capture & T; +export function stmt(options: CaptureOptions & { variadic: true | VariadicOptions }): Capture & T[]; +export function stmt(nameOrOptions?: string | CaptureOptions): Capture & T { + return createKindCapture(CaptureKind.Statement, nameOrOptions); +} + +/** + * Internal helper for kind-specific capture factory functions. + */ +function createKindCapture(kind: CaptureKind, nameOrOptions?: string | ExprCaptureOptions): Capture & T { + let name: string | undefined; + let options: ExprCaptureOptions | undefined; + + if (typeof nameOrOptions === 'string') { + name = nameOrOptions; + } else { + options = nameOrOptions; + name = options?.name; + } + + const captureName = name || `unnamed_${capture.nextUnnamedId++}`; + const impl = new CaptureImpl(captureName, options, true, kind); + return createCaptureProxy(impl); +} + +// Attach kind-specific factories to capture for namespace-qualified access +capture.expr = expr; +capture.ident = ident; +capture.typeRef = typeRef; +capture.stmt = stmt; + /** * Concise alias for `capture`. Works well for inline captures in patterns and templates. * diff --git a/rewrite-javascript/rewrite/src/javascript/templating/engine.ts b/rewrite-javascript/rewrite/src/javascript/templating/engine.ts index bc2d030429e..30402d00c2d 100644 --- a/rewrite-javascript/rewrite/src/javascript/templating/engine.ts +++ b/rewrite-javascript/rewrite/src/javascript/templating/engine.ts @@ -18,7 +18,8 @@ import {emptySpace, J, Statement, Type} from '../../java'; import {Any, Capture, JavaScriptParser, JavaScriptVisitor, JS} from '..'; import {create as produce} from 'mutative'; import {CaptureMarker, PlaceholderUtils, WRAPPER_FUNCTION_NAME} from './utils'; -import {CAPTURE_NAME_SYMBOL, CAPTURE_TYPE_SYMBOL, CaptureImpl, CaptureValue, RAW_CODE_SYMBOL, RawCode} from './capture'; +import {CAPTURE_KIND_SYMBOL, CAPTURE_NAME_SYMBOL, CAPTURE_TYPE_SYMBOL, CaptureImpl, CaptureValue, RAW_CODE_SYMBOL, RawCode} from './capture'; +import {CaptureKind} from './types'; import {PlaceholderReplacementVisitor} from './placeholder-replacement'; import {JavaCoordinates} from './template'; import {maybeAutoFormat} from '../format'; @@ -269,7 +270,8 @@ export class TemplateEngine { } /** - * Generates type preamble declarations for captures/parameters with type annotations. + * Generates type preamble declarations for expression captures/parameters with type annotations. + * Only expression captures get preamble declarations — identifiers, types, and statements don't. * * @param parameters The parameters * @returns Array of preamble statements @@ -288,6 +290,11 @@ export class TemplateEngine { const isTreeArray = Array.isArray(param) && param.length > 0 && isTree(param[0]); if (isCapture) { + // Only expression captures get preamble declarations + const captureKind = param[CAPTURE_KIND_SYMBOL]; + if (captureKind !== undefined && captureKind !== CaptureKind.Expression) { + continue; + } const captureType = param[CAPTURE_TYPE_SYMBOL]; if (captureType) { const typeString = typeof captureType === 'string' @@ -429,7 +436,7 @@ export class TemplateEngine { contextStatements: string[] = [], dependencies: Record = {} ): Promise { - // Generate type preamble for captures with types (skip RawCode) + // Generate type preamble for expression captures with types (skip RawCode and non-expression captures) const preamble: string[] = []; for (const capture of captures) { // Skip raw code - it's not a capture @@ -437,6 +444,12 @@ export class TemplateEngine { continue; } + // Only expression captures get preamble declarations + const captureKind = (capture as any)[CAPTURE_KIND_SYMBOL]; + if (captureKind !== undefined && captureKind !== CaptureKind.Expression) { + continue; + } + const captureName = (capture as any)[CAPTURE_NAME_SYMBOL] || capture.getName(); const captureType = (capture as any)[CAPTURE_TYPE_SYMBOL]; if (captureType) { @@ -450,7 +463,6 @@ export class TemplateEngine { preamble.push(`let ${placeholder}: ${typeString};`); } } - // Don't add preamble declarations without types - they don't provide type attribution } // Build the template string with placeholders for captures and raw code diff --git a/rewrite-javascript/rewrite/src/javascript/templating/index.ts b/rewrite-javascript/rewrite/src/javascript/templating/index.ts index d0db0c15d6e..a3c4fccd1ca 100644 --- a/rewrite-javascript/rewrite/src/javascript/templating/index.ts +++ b/rewrite-javascript/rewrite/src/javascript/templating/index.ts @@ -33,6 +33,8 @@ export type { MatchAttemptResult } from './types'; +export type { ExprCaptureOptions } from './capture'; + // Export capture functionality export { and, @@ -42,7 +44,11 @@ export { any, param, raw, - _ + _, + expr, + ident, + typeRef, + stmt } from './capture'; // Export pattern functionality diff --git a/rewrite-javascript/rewrite/src/javascript/templating/types.ts b/rewrite-javascript/rewrite/src/javascript/templating/types.ts index a008f95d154..bbcb538fdd3 100644 --- a/rewrite-javascript/rewrite/src/javascript/templating/types.ts +++ b/rewrite-javascript/rewrite/src/javascript/templating/types.ts @@ -19,6 +19,21 @@ import type {Pattern} from "./pattern"; import type {Template} from "./template"; import type {CaptureValue, RawCode} from "./capture"; +/** + * The kind of syntactic element a capture represents. + * Used by the template engine to generate appropriate scaffold placeholders. + */ +export enum CaptureKind { + /** An expression (the default). */ + Expression = 'expression', + /** An identifier / name. */ + Identifier = 'identifier', + /** A type reference. */ + TypeReference = 'type-reference', + /** A statement. */ + Statement = 'statement', +} + /** * Options for variadic captures that match zero or more nodes in a sequence. */ @@ -136,44 +151,6 @@ export interface CaptureOptions { * ``` */ constraint?: ConstraintFunction; - /** - * Type annotation for this capture. When provided, the template engine will generate - * a preamble declaring the capture identifier with this type annotation, allowing - * the TypeScript parser/compiler to produce a properly type-attributed AST. - * - * **Why Use Type Attribution:** - * When matching against TypeScript code with type information, providing a type ensures - * the pattern's AST has matching type attribution, which can be important for: - * - Semantic matching based on types - * - Matching code that depends on type inference - * - Ensuring pattern parses with correct type context - * - * Can be specified as: - * - A string type annotation (e.g., "boolean", "string", "number", "Promise", "User[]") - * - A Type instance from the AST (the type will be inferred from the Type) - * - * @example - * ```typescript - * // Match promise chains with proper type attribution - * const chain = capture({ - * name: 'chain', - * type: 'Promise', // TypeScript will attribute this as Promise type - * constraint: (call: J.MethodInvocation) => { - * // Validate promise chain structure - * return call.name.simpleName === 'then'; - * } - * }); - * pattern`${chain}.catch(err => console.log(err))` - * - * // Match arrays with type annotation - * const items = capture({ - * name: 'items', - * type: 'number[]', // Array of numbers - * }); - * pattern`${items}.map(x => x * 2)` - * ``` - */ - type?: string | Type; } /** diff --git a/rewrite-javascript/rewrite/test/javascript/templating/builder-api.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/builder-api.test.ts index 218e46341a1..898e8f18f0e 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/builder-api.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/builder-api.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {capture, Capture, JavaScriptVisitor, Pattern, template, Template, typescript} from "../../../src/javascript"; +import {expr, Capture, JavaScriptVisitor, Pattern, template, Template, typescript} from "../../../src/javascript"; import {Expression, J} from "../../../src/java"; describe('Builder API', () => { @@ -31,7 +31,7 @@ describe('Builder API', () => { }); test('creates template with code and parameters', () => { - const value = capture('value'); + const value = expr('value'); const tmpl = Template.builder() .code('const x = ') .param(value) @@ -62,7 +62,7 @@ describe('Builder API', () => { test('handles conditional construction', async () => { const needsValidation = true; - const value = capture('value'); + const value = expr('value'); const builder = Template.builder().code('function validate(x) {'); if (needsValidation) { @@ -100,7 +100,7 @@ describe('Builder API', () => { const argCount = 3; for (let i = 0; i < argCount; i++) { if (i > 0) builder.code(', '); - builder.param(capture(`arg${i}`)); + builder.param(expr(`arg${i}`)); } builder.code(')'); @@ -120,8 +120,8 @@ describe('Builder API', () => { }); test('handles multiple consecutive param calls', () => { - const a = capture('a'); - const b = capture('b'); + const a = expr('a'); + const b = expr('b'); const tmpl = Template.builder() .param(a) .param(b) @@ -179,7 +179,7 @@ describe('Builder API', () => { }); test('creates pattern with code and captures', () => { - const value = capture('value'); + const value = expr('value'); const pat = Pattern.builder() .code('const x = ') .capture(value) @@ -189,8 +189,8 @@ describe('Builder API', () => { }); test('creates pattern equivalent to pattern literal', async () => { - const left = capture('left'); - const right = capture('right'); + const left = expr('left'); + const right = expr('right'); // Using builder const builderPat = Pattern.builder() @@ -222,7 +222,7 @@ describe('Builder API', () => { const captures: Capture[] = []; for (let i = 0; i < argCount; i++) { if (i > 0) builder.code(', '); - const cap = capture(); + const cap = expr(); captures.push(cap); builder.capture(cap); } @@ -283,8 +283,8 @@ describe('Builder API', () => { }); test('handles multiple consecutive capture calls', () => { - const a = capture('a'); - const b = capture('b'); + const a = expr('a'); + const b = expr('b'); const pat = Pattern.builder() .capture(a) .capture(b) @@ -294,7 +294,7 @@ describe('Builder API', () => { }); test('pattern from builder can be configured', () => { - const value = capture('value'); + const value = expr('value'); const pat = Pattern.builder() .code('const x = ') .capture(value) @@ -312,8 +312,8 @@ describe('Builder API', () => { describe('Builder API Integration', () => { test('pattern and template builders work together', async () => { - const left = capture('left'); - const right = capture('right'); + const left = expr('left'); + const right = expr('right'); const pat = Pattern.builder() .capture(left) @@ -349,7 +349,7 @@ describe('Builder API', () => { const builder = Pattern.builder().code('processArgs('); for (let i = 0; i < expectedArgs.length; i++) { if (i > 0) builder.code(', '); - builder.capture(capture(expectedArgs[i])); + builder.capture(expr(expectedArgs[i])); } builder.code(')'); @@ -359,7 +359,7 @@ describe('Builder API', () => { const tmplBuilder = Template.builder().code('processArgs('); for (let i = expectedArgs.length - 1; i >= 0; i--) { if (i < expectedArgs.length - 1) tmplBuilder.code(', '); - tmplBuilder.param(capture(expectedArgs[i])); + tmplBuilder.param(expr(expectedArgs[i])); } tmplBuilder.code(')'); diff --git a/rewrite-javascript/rewrite/test/javascript/templating/capture-constraints.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/capture-constraints.test.ts index 332cc8c2bd1..d638ce76517 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/capture-constraints.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/capture-constraints.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {Cursor} from "../../../src"; -import {and, capture, JavaScriptParser, not, or, pattern} from "../../../src/javascript"; +import {and, expr, JavaScriptParser, not, or, pattern} from "../../../src/javascript"; import {isBinary, J} from "../../../src/java"; describe('Capture Constraints', () => { @@ -43,7 +43,7 @@ describe('Capture Constraints', () => { describe('Simple constraints', () => { test('number constraint with both success and failure cases', async () => { - const value = capture({ + const value = expr({ constraint: (node) => typeof node.value === 'number' && node.value > 100 }); const pat = pattern`${value}`; @@ -59,7 +59,7 @@ describe('Capture Constraints', () => { }); test('string constraint with both success and failure cases', async () => { - const text = capture({ + const text = expr({ constraint: (node) => typeof node.value === 'string' && node.value.startsWith('hello') }); const pat = pattern`${text}`; @@ -77,7 +77,7 @@ describe('Capture Constraints', () => { describe('and() composition', () => { test('validates all constraints must pass', async () => { - const value = capture({ + const value = expr({ constraint: and( (node) => typeof node.value === 'number', (node) => (node.value as number) > 50, @@ -100,7 +100,7 @@ describe('Capture Constraints', () => { describe('or() composition', () => { test('validates at least one constraint must pass', async () => { - const value = capture({ + const value = expr({ constraint: or( (node) => typeof node.value === 'string', (node) => typeof node.value === 'number' && node.value > 1000 @@ -124,7 +124,7 @@ describe('Capture Constraints', () => { describe('not() composition', () => { test('inverts constraint result', async () => { - const value = capture({ + const value = expr({ constraint: not((node) => typeof node.value === 'string') }); const pat = pattern`${value}`; @@ -143,7 +143,7 @@ describe('Capture Constraints', () => { test('combines and, or, not', async () => { // Match numbers > 50 that are either even OR > 200 // But NOT divisible by 10 - const value = capture({ + const value = expr({ constraint: and( (node) => typeof node.value === 'number', (node) => (node.value as number) > 50, @@ -178,7 +178,7 @@ describe('Capture Constraints', () => { describe('Constraints on identifiers', () => { test('validates identifier names with both success and failure', async () => { - const name = capture({ + const name = expr({ constraint: (node) => node.simpleName.startsWith('get') && !node.simpleName.includes('_') }); const pat = pattern`${name}`; @@ -196,7 +196,7 @@ describe('Capture Constraints', () => { describe('Constraints in complex patterns', () => { test('applies constraint in method invocation', async () => { - const arg = capture({ + const arg = expr({ constraint: (node) => typeof node.value === 'number' && node.value > 10 }); const pat = pattern`foo(${arg})`; @@ -213,10 +213,10 @@ describe('Capture Constraints', () => { }); test('multiple captures with different constraints', async () => { - const left = capture({ + const left = expr({ constraint: (node) => typeof node.value === 'number' && node.value > 5 }); - const right = capture({ + const right = expr({ constraint: (node) => typeof node.value === 'number' && node.value < 5 }); const pat = pattern`${left} + ${right}`; @@ -237,7 +237,7 @@ describe('Capture Constraints', () => { let constraintCalled = false; let parentIsBinary = false; - const left = capture({ + const left = expr({ constraint: (node, context) => { constraintCalled = true; // Check that cursor can navigate to parent @@ -251,8 +251,8 @@ describe('Capture Constraints', () => { const pat = pattern`${left} + 20`; // Match against a binary expression - const expr = await parseExpression('10 + 20') as J.Binary; - const match = await pat.match(expr, new Cursor(expr, undefined)); + const parsed = await parseExpression('10 + 20') as J.Binary; + const match = await pat.match(parsed, new Cursor(parsed, undefined)); expect(match).toBeDefined(); expect(constraintCalled).toBe(true); @@ -264,7 +264,7 @@ describe('Capture Constraints', () => { // Test 1: Cursor at ast root when not explicitly provided let cursorReceived: any = 'not-called'; let astNode: J | undefined; - const value1 = capture({ + const value1 = expr({ constraint: (node, context) => { cursorReceived = context.cursor; astNode = node; @@ -282,7 +282,7 @@ describe('Capture Constraints', () => { // Test 2: Composition functions forward cursor let cursorReceivedInAnd: any = 'not-called'; - const value2 = capture({ + const value2 = expr({ constraint: and( (node) => typeof node.value === 'number', (node, context) => { @@ -299,7 +299,7 @@ describe('Capture Constraints', () => { expect(cursorReceivedInAnd.parent?.value).toBe(await parseExpression('50')); // Test 3: or composition with cursor-aware constraints - const value3 = capture({ + const value3 = expr({ constraint: or( (node) => typeof node.value === 'string', (node, context) => { @@ -319,7 +319,7 @@ describe('Capture Constraints', () => { expect(match3b).toBeUndefined(); // Test 4: not composition with cursor-aware constraint - const value4 = capture({ + const value4 = expr({ constraint: not((node, context) => { // Reject if cursor has a grandparent (i.e., not at root) return context.cursor.parent?.parent !== undefined; @@ -339,7 +339,7 @@ describe('Capture Constraints', () => { let capturedNode: J | undefined; let cursorValue: J | undefined; - const left = capture({ + const left = expr({ constraint: (node, context) => { capturedNode = node; cursorValue = context.cursor.value as J; @@ -349,8 +349,8 @@ describe('Capture Constraints', () => { const pat = pattern`${left} + 20`; // Match against the expression - const expr = await parseExpression('10 + 20') as J.Binary; - const match = await pat.match(expr, new Cursor(expr, undefined)); + const parsed = await parseExpression('10 + 20') as J.Binary; + const match = await pat.match(parsed, new Cursor(parsed, undefined)); expect(match).toBeDefined(); expect(capturedNode).toBeDefined(); @@ -367,7 +367,7 @@ describe('Capture Constraints', () => { let capturedArg: J | undefined; let parentKind: typeof J.Kind | undefined; - const arg = capture({ + const arg = expr({ constraint: (node, context) => { capturedArg = node; // The cursor's parent should be at a higher level in the tree @@ -380,8 +380,8 @@ describe('Capture Constraints', () => { }); const pat = pattern`foo(${arg})`; - const expr = await parseExpression('foo(42)') as J.MethodInvocation; - const match = await pat.match(expr, new Cursor(expr, undefined)); + const parsed = await parseExpression('foo(42)') as J.MethodInvocation; + const match = await pat.match(parsed, new Cursor(parsed, undefined)); expect(match).toBeDefined(); expect(capturedArg).toBeDefined(); @@ -397,8 +397,8 @@ describe('Capture Constraints', () => { describe('Constraints with capture access', () => { test('constraint can access previously matched captures', async () => { // Pattern: ${left} + ${right} where right must equal left - const left = capture('left'); - const right = capture({ + const left = expr('left'); + const right = expr({ name: 'right', constraint: (node, context) => { const leftValue = context.captures.get(left); @@ -421,8 +421,8 @@ describe('Capture Constraints', () => { test('constraint can access captures by string name', async () => { // Test that captures can be accessed by string name as well as Capture object - const left = capture('left'); - const right = capture({ + const left = expr('left'); + const right = expr({ name: 'right', constraint: (node, context) => { // Access by string name instead of Capture object @@ -445,8 +445,8 @@ describe('Capture Constraints', () => { let hasLeftByCapture = false; let hasLeftByString = false; - const left = capture('hasTestLeft'); - const right = capture({ + const left = expr('hasTestLeft'); + const right = expr({ name: 'hasTestRight', constraint: (node, context) => { // Verify has() works with both Capture object and string @@ -457,8 +457,8 @@ describe('Capture Constraints', () => { }); const pat = pattern`${left} + ${right}`; - const expr = await parseExpression('15 + 25'); - const match = await pat.match(expr, new Cursor(expr, undefined)); + const parsed = await parseExpression('15 + 25'); + const match = await pat.match(parsed, new Cursor(parsed, undefined)); expect(match).toBeDefined(); expect(hasLeftByCapture).toBe(true); expect(hasLeftByString).toBe(true); @@ -466,8 +466,8 @@ describe('Capture Constraints', () => { test('multiple captures with dependent constraints', async () => { // Pattern: ${a} + ${b} + ${c} where b > a and c > b - const a = capture('a'); - const b = capture({ + const a = expr('a'); + const b = expr({ name: 'b', constraint: (node, context) => { const aValue = context.captures.get(a) as J.Literal | undefined; @@ -477,7 +477,7 @@ describe('Capture Constraints', () => { node.value > aValue.value; } }); - const c = capture({ + const c = expr({ name: 'c', constraint: (node, context) => { const bValue = context.captures.get(b) as J.Literal | undefined; @@ -514,8 +514,8 @@ describe('Capture Constraints', () => { let leftWasAvailable = false; let rightSawItself = false; - const left = capture('stateTestLeft'); - const right = capture({ + const left = expr('stateTestLeft'); + const right = expr({ name: 'stateTestRight', constraint: (node, context) => { rightConstraintCalled = true; @@ -532,8 +532,8 @@ describe('Capture Constraints', () => { }); const pat = pattern`${left} + ${right}`; - const expr = await parseExpression('30 + 40'); - const match = await pat.match(expr, new Cursor(expr, undefined)); + const parsed = await parseExpression('30 + 40'); + const match = await pat.match(parsed, new Cursor(parsed, undefined)); expect(match).toBeDefined(); expect(rightConstraintCalled).toBe(true); expect(leftWasAvailable).toBe(true); // Should see left capture diff --git a/rewrite-javascript/rewrite/test/javascript/templating/capture-kinds.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/capture-kinds.test.ts new file mode 100644 index 00000000000..85021f41efc --- /dev/null +++ b/rewrite-javascript/rewrite/test/javascript/templating/capture-kinds.test.ts @@ -0,0 +1,146 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + capture, expr, ident, typeRef, stmt, any, + Pattern, pattern +} from "../../../src/javascript"; +import { + CAPTURE_KIND_SYMBOL +} from "../../../src/javascript/templating/capture"; +import { CaptureKind } from "../../../src/javascript/templating/types"; + +describe('capture kinds', () => { + describe('CaptureKind enum', () => { + test('has expected values', () => { + expect(CaptureKind.Expression).toBe('expression'); + expect(CaptureKind.Identifier).toBe('identifier'); + expect(CaptureKind.TypeReference).toBe('type-reference'); + expect(CaptureKind.Statement).toBe('statement'); + }); + }); + + describe('factory functions', () => { + test('expr() creates capture with Expression kind', () => { + const c = expr('x'); + expect(c.getName()).toBe('x'); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Expression); + }); + + test('ident() creates capture with Identifier kind', () => { + const c = ident('name'); + expect(c.getName()).toBe('name'); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Identifier); + }); + + test('typeRef() creates capture with TypeReference kind', () => { + const c = typeRef('t'); + expect(c.getName()).toBe('t'); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.TypeReference); + }); + + test('stmt() creates capture with Statement kind', () => { + const c = stmt('s'); + expect(c.getName()).toBe('s'); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Statement); + }); + + test('factory functions work without a name', () => { + const e = expr(); + const i = ident(); + const t = typeRef(); + const s = stmt(); + expect(e.getName()).toMatch(/^unnamed_/); + expect(i.getName()).toMatch(/^unnamed_/); + expect(t.getName()).toMatch(/^unnamed_/); + expect(s.getName()).toMatch(/^unnamed_/); + }); + + test('factory functions accept options', () => { + const c = expr({ + name: 'x', + constraint: (node) => true + }); + expect(c.getName()).toBe('x'); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Expression); + expect(c.getConstraint()).toBeDefined(); + }); + + test('factory functions accept variadic options', () => { + const c = expr({ + name: 'args', + variadic: true + }); + expect(c.getName()).toBe('args'); + expect(c.isVariadic()).toBe(true); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Expression); + }); + }); + + describe('backwards compatibility', () => { + test('capture() defaults to Expression kind', () => { + const c = capture('x'); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Expression); + }); + + test('capture() with options defaults to Expression kind', () => { + const c = capture({ name: 'x' }); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Expression); + }); + }); + + describe('namespace access on capture', () => { + test('capture.expr() works', () => { + const c = capture.expr('x'); + expect(c.getName()).toBe('x'); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Expression); + }); + + test('capture.ident() works', () => { + const c = capture.ident('n'); + expect(c.getName()).toBe('n'); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Identifier); + }); + + test('capture.typeRef() works', () => { + const c = capture.typeRef('t'); + expect(c.getName()).toBe('t'); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.TypeReference); + }); + + test('capture.stmt() works', () => { + const c = capture.stmt('s'); + expect(c.getName()).toBe('s'); + expect((c as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Statement); + }); + }); + + describe('any() defaults', () => { + test('any() defaults to Expression kind', () => { + const a = any(); + expect((a as any)[CAPTURE_KIND_SYMBOL]).toBe(CaptureKind.Expression); + }); + }); + + describe('usage in patterns', () => { + test('factory captures work in pattern template literals', () => { + const e = expr('x'); + const n = ident('method'); + const p = pattern`${n}(${e})`; + expect(p).toBeInstanceOf(Pattern); + expect(p.captures.length).toBe(2); + }); + }); +}); diff --git a/rewrite-javascript/rewrite/test/javascript/templating/capture-property-access.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/capture-property-access.test.ts index 8dc75de1abf..17c7b039e88 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/capture-property-access.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/capture-property-access.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {capture, JavaScriptVisitor, pattern, template, typescript} from "../../../src/javascript"; +import {expr, JavaScriptVisitor, pattern, template, typescript} from "../../../src/javascript"; import {J} from "../../../src/java"; describe('forwardRef pattern with replacement', () => { @@ -23,7 +23,7 @@ describe('forwardRef pattern with replacement', () => { test('capture with property access', () => { // Test replacing forwardRef(Type) with Type // This captures the argument to forwardRef and replaces it with just the argument - const arg = capture(); + const arg = expr(); const pat = pattern`forwardRef(${arg})`; const tmpl = template`${arg}`; @@ -62,7 +62,7 @@ describe('forwardRef pattern with replacement', () => { test('capture with nested property access in template', () => { // Test: forwardRef((props) => ...) becomes (props) => ... // This tests a more complex case where we're matching the full forwardRef pattern - const componentDef = capture(); + const componentDef = expr(); const pat = pattern`forwardRef(${componentDef})`; const tmpl = template`${componentDef}`; @@ -104,7 +104,7 @@ describe('forwardRef pattern with replacement', () => { test('capture with property access in template', () => { // Test accessing properties of captured nodes in templates - const method = capture('method'); + const method = expr('method'); const pat = pattern`foo(${method})`; // Access the name property of the captured method invocation @@ -148,7 +148,7 @@ describe('forwardRef pattern with replacement', () => { test('capture with array access in template', () => { // Test accessing array elements via bracket notation - const invocation = capture('invocation'); + const invocation = expr('invocation'); const pat = pattern`${invocation}`; // Access the first argument via array index diff --git a/rewrite-javascript/rewrite/test/javascript/templating/capture-types.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/capture-types.test.ts index 0553b26d918..e6fcff572af 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/capture-types.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/capture-types.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {capture, JavaScriptVisitor, JS, pattern, template, typescript} from "../../../src/javascript"; +import {expr, JavaScriptVisitor, JS, pattern, template, typescript} from "../../../src/javascript"; import {J, Type} from "../../../src/java"; describe('capture types', () => { @@ -24,7 +24,7 @@ describe('capture types', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { // Create a capture with a type annotation - const x = capture({type: 'boolean'}); + const x = expr({type: 'boolean'}); const pat = pattern`${x} || false`; const match = await pat.match(binary, this.cursor); @@ -45,7 +45,7 @@ describe('capture types', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { // Create a capture with a type annotation - const condition = capture({type: 'boolean'}); + const condition = expr({type: 'boolean'}); const pat = pattern`${condition} && true`; const match = await pat.match(binary, this.cursor); @@ -66,8 +66,8 @@ describe('capture types', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { // Create captures with different type annotations - const x = capture({type: 'number'}); - const y = capture({type: 'number'}); + const x = expr({type: 'number'}); + const y = expr({type: 'number'}); const pat = pattern`${x} + ${y}`; const match = await pat.match(binary, this.cursor); @@ -88,7 +88,7 @@ describe('capture types', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { // Create a capture without a type annotation - const x = capture(); + const x = expr(); const pat = pattern`${x} + 1`; const match = await pat.match(binary, this.cursor); @@ -148,7 +148,7 @@ describe('capture types', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { // Create a capture with Type.Primitive.Boolean - const condition = capture({type: Type.Primitive.Boolean}); + const condition = expr({type: Type.Primitive.Boolean}); const pat = pattern`${condition} && true`; const match = await pat.match(binary, this.cursor); @@ -168,7 +168,7 @@ describe('capture types', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { // Create a capture with Type.Primitive.String - const str = capture({type: Type.Primitive.String}); + const str = expr({type: Type.Primitive.String}); const pat = pattern`${str} + ""`; const match = await pat.match(binary, this.cursor); @@ -188,7 +188,7 @@ describe('capture types', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { // Create a capture with Type.Primitive.Double - const num = capture({type: Type.Primitive.Double}); + const num = expr({type: Type.Primitive.Double}); const pat = pattern`${num} + 0`; const match = await pat.match(binary, this.cursor); @@ -217,7 +217,7 @@ describe('capture types', () => { } as Type.Array; // Use the array type in a capture within a pattern - const arr = capture({type: arrayType}); + const arr = expr({type: arrayType}); const pat = pattern`oldMethod(${arr})`; const match = await pat.match(method, this.cursor); diff --git a/rewrite-javascript/rewrite/test/javascript/templating/dependencies.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/dependencies.test.ts index 50926781e8a..e3ae8b0a97d 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/dependencies.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/dependencies.test.ts @@ -17,7 +17,7 @@ */ import { _, - capture, + expr, JavaScriptParser, JavaScriptVisitor, MethodMatcher, @@ -302,7 +302,7 @@ describe('template dependencies integration', () => { // This builds on the existing functionality to ensure our semantic equality checking works const spec = new RecipeSpec(); const swapOperands = rewrite(() => { - const {left, right} = {left: capture(), right: capture()}; + const {left, right} = {left: expr(), right: expr()}; return { before: pattern`${left} + ${right}`, after: template`${right} + ${left}` @@ -348,7 +348,7 @@ describe('template dependencies integration', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitMethodInvocation(method: J.MethodInvocation, _p: any): Promise { // Create rewrite rules fresh for each invocation - const arg = capture(); + const arg = expr(); const replaceUtilIsArray = rewrite(() => ({ before: pattern`util.isArray(${arg})`.configure({ context: ["import * as util from 'util'"], @@ -450,7 +450,7 @@ describe('template dependencies integration', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitMethodInvocation(method: J.MethodInvocation, _p: any): Promise { - const dateArg = capture('dateArg'); + const dateArg = expr('dateArg'); // Single pattern that matches both isDate(x) and util.isDate(x) via type attribution const replaceIsDate = rewrite(() => ({ before: pattern`isDate(${dateArg})`.configure({ diff --git a/rewrite-javascript/rewrite/test/javascript/templating/flatten-block.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/flatten-block.test.ts index 8e6efda0e30..a12520711de 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/flatten-block.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/flatten-block.test.ts @@ -19,15 +19,15 @@ import {typescript} from "../../../src/javascript"; import {ExecutionContext} from "../../../src"; import {JavaScriptVisitor} from "../../../src/javascript/visitor"; import {J} from "../../../src/java"; -import {rewrite, pattern, template, capture, flattenBlock} from "../../../src/javascript/templating"; +import {rewrite, pattern, template, expr, flattenBlock} from "../../../src/javascript/templating"; describe('flattenBlock', () => { test('flattens block statements into parent block', async () => { const spec = new RecipeSpec(); - const cond = capture('cond'); - const arr = capture('arr'); - const cb = capture('cb'); + const cond = expr('cond'); + const arr = expr('arr'); + const cb = expr('cb'); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitReturn(ret: J.Return, ctx: ExecutionContext): Promise { @@ -74,9 +74,9 @@ describe('flattenBlock', () => { test('does not affect non-matching returns', async () => { const spec = new RecipeSpec(); - const cond = capture('cond'); - const arr = capture('arr'); - const cb = capture('cb'); + const cond = expr('cond'); + const arr = expr('arr'); + const cb = expr('cb'); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitReturn(ret: J.Return, ctx: ExecutionContext): Promise { diff --git a/rewrite-javascript/rewrite/test/javascript/templating/from-recipe.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/from-recipe.test.ts index e1df6e95dba..ce279a65b6c 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/from-recipe.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/from-recipe.test.ts @@ -15,7 +15,7 @@ */ import {fromVisitor, RecipeSpec} from "../../../src/test"; import { - capture, + expr, fromRecipe, JavaScriptVisitor, pattern, @@ -88,8 +88,8 @@ describe('fromRecipe', () => { // Rule: Swap operands const swapRule = rewrite(() => ({ - before: pattern`${capture('a')} + ${capture('b')}`, - after: template`${capture('b')} + ${capture('a')}` + before: pattern`${expr('a')} + ${expr('b')}`, + after: template`${expr('b')} + ${expr('a')}` })); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { diff --git a/rewrite-javascript/rewrite/test/javascript/templating/lenient-type-matching.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/lenient-type-matching.test.ts index c066957811d..26586c874ac 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/lenient-type-matching.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/lenient-type-matching.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {capture, JavaScriptVisitor, npm, packageJson, pattern, template, tsx, typescript} from "../../../src/javascript"; +import {expr, ident, JavaScriptVisitor, npm, packageJson, pattern, template, tsx, typescript} from "../../../src/javascript"; import {J, Type} from "../../../src/java"; import {withDir} from "tmp-promise"; @@ -26,7 +26,7 @@ describe('lenient type matching in patterns', () => { const tempDir = repo.path; // Pattern with unconstrained captures for props, ref, and body - const pat = pattern`React.forwardRef((${capture('props')}, ${capture('ref')}) => ${capture('body')})` + const pat = pattern`React.forwardRef((${expr('props')}, ${expr('ref')}) => ${expr('body')})` .configure({ context: [`import * as React from 'react'`], dependencies: {'@types/react': '^18.0.0'} @@ -91,7 +91,7 @@ describe('lenient type matching in patterns', () => { test('untyped function pattern matches typed function with return type', async () => { // Pattern with untyped function (no return type) should match typed function - const pat = pattern`function ${capture('name')}() { return "hello"; }`; + const pat = pattern`function ${ident('name')}() { return "hello"; }`; const testCode = ` function greet(): string { return "hello"; } @@ -123,13 +123,13 @@ function greet(): string { return "hello"; } test('untyped pattern matches and transforms with template replacement', async () => { // Pattern without type annotations matches typed code and replaces it with template // This demonstrates: matching untyped pattern against typed code + capturing + template replacement - const pat = pattern`forwardRef(function ${capture('name')}(props, ref) { return null; })` + const pat = pattern`forwardRef(function ${ident('name')}(props, ref) { return null; })` .configure({ context: [`import { forwardRef } from 'react'`] }); // Template that uses the captured name as an identifier - const tmpl = template`console.log(${capture('name')})`; + const tmpl = template`console.log(${ident('name')})`; spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitMethodInvocation(methodInvocation: J.MethodInvocation, _p: any): Promise { @@ -158,7 +158,7 @@ function greet(): string { return "hello"; } test('strict type matching mode rejects untyped pattern against typed code', async () => { // Pattern with strict type matching (lenientTypeMatching: false) should NOT match typed function - const pat = pattern`function ${capture('name')}() { return "hello"; }` + const pat = pattern`function ${ident('name')}() { return "hello"; }` .configure({ lenientTypeMatching: false }); @@ -189,7 +189,7 @@ function greet(): string { return "hello"; } test('lenient type matching can be explicitly enabled', async () => { // Pattern with explicit lenient type matching should match typed function - const pat = pattern`function ${capture('name')}() { return "hello"; }` + const pat = pattern`function ${ident('name')}() { return "hello"; }` .configure({ lenientTypeMatching: true }); @@ -223,7 +223,7 @@ function greet(): string { return "hello"; } test('strict mode with matching types does match', async () => { // Pattern with strict type matching and matching return type SHOULD match - const pat = pattern`function ${capture('name')}(): string { return "hello"; }` + const pat = pattern`function ${ident('name')}(): string { return "hello"; }` .configure({ lenientTypeMatching: false }); @@ -261,7 +261,7 @@ function greet(): string { return "hello"; } const tempDir = repo.path; // Pattern uses the original import name - const pat = pattern`isDate(${capture('arg')})` + const pat = pattern`isDate(${expr('arg')})` .configure({ context: [`import { isDate } from 'node:util/types'`], lenientTypeMatching: false, // Strict type matching @@ -319,7 +319,7 @@ function greet(): string { return "hello"; } const tempDir = repo.path; // Pattern uses the original import name, with lenient mode (default) - const pat = pattern`isDate(${capture('arg')})` + const pat = pattern`isDate(${expr('arg')})` .configure({ context: [`import { isDate } from 'node:util/types'`], // lenientTypeMatching defaults to true @@ -375,7 +375,7 @@ function greet(): string { return "hello"; } test('aliased import matching without type attribution (import-based resolution)', async () => { // Pattern uses the original import name // Testing if parser tracks import origins (module + original name) without type attribution - const pat = pattern`isDate(${capture('arg')})` + const pat = pattern`isDate(${expr('arg')})` .configure({ context: [`import { isDate } from 'node:util/types'`] // Note: NO dependencies - pattern won't have type attribution @@ -419,7 +419,7 @@ function greet(): string { return "hello"; } const tempDir = repo.path; // Pattern uses named import: forwardRef() - const pat = pattern`forwardRef(${capture('fn')})` + const pat = pattern`forwardRef(${expr('fn')})` .configure({ context: [`import { forwardRef } from 'react'`], dependencies: {'@types/react': '^18.0.0'} diff --git a/rewrite-javascript/rewrite/test/javascript/templating/match.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/match.test.ts index bf913530d4c..ea4479d6423 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/match.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/match.test.ts @@ -16,7 +16,7 @@ import { fromVisitor, RecipeSpec } from "../../../src/test"; import { Cursor } from "../../../src"; import { - capture, + expr, JavaScriptParser, JavaScriptVisitor, pattern, @@ -63,7 +63,7 @@ describe('match extraction', () => { override async visitBinary(binary: J.Binary, _p: any): Promise { // Create capture objects - const left = capture(), right = capture(); + const left = expr(), right = expr(); // Create a pattern that matches "a + b" using the capture objects const m = await pattern`${left} + ${right}`.match(binary, this.cursor); @@ -85,8 +85,8 @@ describe('match extraction', () => { override async visitBinary(binary: J.Binary, p: any): Promise { const swapOperands = rewrite(() => ({ - before: pattern`${capture('left')} + ${capture('right')}`, - after: template`${capture('right')} + ${capture('left')}` + before: pattern`${expr('left')} + ${expr('right')}`, + after: template`${expr('right')} + ${expr('left')}` }) ); return await swapOperands.tryOn(this.cursor, binary); @@ -104,7 +104,7 @@ describe('match extraction', () => { override async visitBinary(binary: J.Binary, p: any): Promise { if (binary.operator.element === J.Binary.Type.Addition) { // Create capture objects without explicit names - const { left, right } = { left: capture(), right: capture() }; + const { left, right } = { left: expr(), right: expr() }; // Create a pattern that matches "a + b" using the capture objects const m = await pattern`${left} + ${right}`.match(binary, this.cursor); @@ -137,10 +137,10 @@ describe('match extraction', () => { override async visitBinary(binary: J.Binary, p: any): Promise { if (binary.operator.element === J.Binary.Type.Addition) { // Use inline named captures - const m = await pattern`${capture('left')} + ${capture('right')}`.match(binary, this.cursor); + const m = await pattern`${expr('left')} + ${expr('right')}`.match(binary, this.cursor); if (m) { // Can retrieve by string name - return await template`${capture('right')} + ${capture('left')}`.apply(binary, this.cursor, { values: m }); + return await template`${expr('right')} + ${expr('left')}`.apply(binary, this.cursor, { values: m }); } } return binary; @@ -157,7 +157,7 @@ describe('match extraction', () => { // Verify that specifying a non-existent package causes npm install to fail const nonExistentPackage = 'this-package-definitely-does-not-exist-12345'; - const pat = pattern`${capture('left')} + ${capture('right')}` + const pat = pattern`${expr('left')} + ${expr('right')}` .configure({ context: [`import { SomeType } from "${nonExistentPackage}"`], dependencies: { [nonExistentPackage]: '^1.0.0' } diff --git a/rewrite-javascript/rewrite/test/javascript/templating/non-capturing-any.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/non-capturing-any.test.ts index 321ce3e9e5c..780dd59c55e 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/non-capturing-any.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/non-capturing-any.test.ts @@ -16,7 +16,7 @@ import { Cursor } from "../../../src"; import { any, - capture, + expr, JavaScriptParser, JavaScriptVisitor, pattern, @@ -174,11 +174,11 @@ describe('Non-Capturing any() Function', () => { describe('Mixed captures and any()', () => { test('captures important value, ignores others', async () => { - const important = capture('important'); + const important = expr('important'); const pat = pattern`compute(${any()}, ${important}, ${any()})`; - const expr = await parseExpression('compute(10, 20, 30)'); - const match = await pat.match(expr, new Cursor(expr, undefined)); + const parsed = await parseExpression('compute(10, 20, 30)'); + const match = await pat.match(parsed, new Cursor(parsed, undefined)); expect(match).toBeDefined(); // Only the middle value should be captured @@ -191,12 +191,12 @@ describe('Non-Capturing any() Function', () => { }); test('first capture, rest any()', async () => { - const first = capture('first'); + const first = expr('first'); const rest = any({ variadic: true }); const pat = pattern`foo(${first}, ${rest})`; - const expr = await parseExpression('foo(1, 2, 3, 4)'); - const match = await pat.match(expr, new Cursor(expr, undefined)); + const parsed = await parseExpression('foo(1, 2, 3, 4)'); + const match = await pat.match(parsed, new Cursor(parsed, undefined)); expect(match).toBeDefined(); // First should be captured @@ -234,7 +234,7 @@ describe('Non-Capturing any() Function', () => { test('mix captures and any() in rewrite', async () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitMethodInvocation(method: J.MethodInvocation, p: any): Promise { - const value = capture('value'); + const value = expr('value'); return await rewrite(() => ({ before: pattern`process(${any()}, ${value})`, after: template`process(${value})` diff --git a/rewrite-javascript/rewrite/test/javascript/templating/pattern-debug-logging.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/pattern-debug-logging.test.ts index 2f7b7d19008..501082090e2 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/pattern-debug-logging.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/pattern-debug-logging.test.ts @@ -1,5 +1,5 @@ import {type MockInstance} from 'vitest'; -import {capture, isExpressionStatement, JavaScriptParser, JS, pattern} from '../../../src/javascript'; +import {expr, isExpressionStatement, JavaScriptParser, JS, pattern} from '../../../src/javascript'; import {J} from '../../../src/java'; describe('Pattern Debug Logging', () => { @@ -27,7 +27,7 @@ describe('Pattern Debug Logging', () => { }); test('no debug logging by default', async () => { - const value = capture('value'); + const value = expr('value'); const pat = pattern`console.log(${value})`; const node = await parseExpression('console.log(42)'); @@ -38,7 +38,7 @@ describe('Pattern Debug Logging', () => { }); test('call-level debug: { debug: true }', async () => { - const value = capture('value'); + const value = expr('value'); const pat = pattern`console.log(${value})`; const node = await parseExpression('console.log(42)'); @@ -53,7 +53,7 @@ describe('Pattern Debug Logging', () => { }); test('pattern-level debug: pattern({ debug: true })', async () => { - const value = capture('value'); + const value = expr('value'); const pat = pattern({ debug: true })`console.log(${value})`; const node = await parseExpression('console.log(42)'); @@ -69,7 +69,7 @@ describe('Pattern Debug Logging', () => { test('global debug: PATTERN_DEBUG=true', async () => { process.env.PATTERN_DEBUG = 'true'; - const value = capture('value'); + const value = expr('value'); const pat = pattern`console.log(${value})`; const node = await parseExpression('console.log(42)'); @@ -85,7 +85,7 @@ describe('Pattern Debug Logging', () => { test('precedence: call > pattern > global', async () => { process.env.PATTERN_DEBUG = 'true'; - const value = capture('value'); + const value = expr('value'); // Pattern has debug: false, but global is true const pat = pattern({ debug: false })`console.log(${value})`; const node = await parseExpression('console.log(42)'); @@ -100,7 +100,7 @@ describe('Pattern Debug Logging', () => { test('explicit debug: false disables when global is true', async () => { process.env.PATTERN_DEBUG = 'true'; - const value = capture('value'); + const value = expr('value'); const pat = pattern`console.log(${value})`; const node = await parseExpression('console.log(42)'); @@ -112,7 +112,7 @@ describe('Pattern Debug Logging', () => { }); test('logs failure with path and explanation', async () => { - const value = capture('value'); + const value = expr('value'); const pat = pattern`console.log(${value})`; // Use console.error instead - should not match const node = await parseExpression('console.error(42)'); @@ -132,8 +132,8 @@ describe('Pattern Debug Logging', () => { }); test('pattern source includes capture names', async () => { - const x = capture('x'); - const y = capture('y'); + const x = expr('x'); + const y = expr('y'); const pat = pattern({ debug: true })`foo(${x}, ${y})`; const node = await parseExpression('foo(1, 2)'); @@ -145,7 +145,7 @@ describe('Pattern Debug Logging', () => { }); test('variadic captures show array format', async () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); const pat = pattern({ debug: true })`console.log(${args})`; const node = await parseExpression('console.log(1, 2, 3)'); @@ -160,8 +160,8 @@ describe('Pattern Debug Logging', () => { }); test('shows path for nested mismatch', async () => { - const x = capture('x'); - const y = capture('y'); + const x = expr('x'); + const y = expr('y'); const pat = pattern({ debug: true })`${x} + ${y}`; // Pattern expects addition, but we provide subtraction const node = await parseExpression('a - b'); diff --git a/rewrite-javascript/rewrite/test/javascript/templating/pattern-debug.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/pattern-debug.test.ts index 1dbf7f87d3d..13f8fe7011f 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/pattern-debug.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/pattern-debug.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {capture, isExpressionStatement, JavaScriptParser, JS, pattern} from '../../../src/javascript'; +import {expr, isExpressionStatement, JavaScriptParser, JS, pattern} from '../../../src/javascript'; import {J} from '../../../src/java'; /** @@ -35,7 +35,7 @@ describe('Pattern Debugging', () => { } test('successful match returns matched=true with result', async () => { - const x = capture('x'); + const x = expr('x'); const pat = pattern`console.log(${x})`; const node = await parseExpression('console.log(42)'); @@ -63,7 +63,7 @@ describe('Pattern Debugging', () => { }); test('constraint failure provides detailed explanation', async () => { - const value = capture({ + const value = expr({ constraint: (node: J) => { // This constraint will fail return false; @@ -81,7 +81,7 @@ describe('Pattern Debugging', () => { }); test('debug log contains constraint evaluation entries', async () => { - const value = capture({ + const value = expr({ constraint: (node: J) => true }); const pat = pattern`${value}`; @@ -144,7 +144,7 @@ describe('Pattern Debugging', () => { }); test('variadic constraint failure is logged in debug', async () => { - const args = capture({ + const args = expr({ variadic: true, constraint: (nodes: J[]) => { // Require exactly 2 arguments @@ -244,7 +244,7 @@ describe('Pattern Debugging', () => { }); test('path tracking includes intermediate steps for object destructuring', async () => { - const name = capture(); + const name = expr(); const pat = pattern`const {${name}} = obj;`; // Target has two variables, pattern expects one diff --git a/rewrite-javascript/rewrite/test/javascript/templating/pattern-matching.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/pattern-matching.test.ts index e2a43d6c757..0b7afa065d2 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/pattern-matching.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/pattern-matching.test.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {capture, pattern} from "../../../src/javascript"; +import {expr, pattern} from "../../../src/javascript"; describe('Pattern Matching', () => { describe('Pattern', () => { test('creates a pattern with correct template parts and captures', () => { - const p = pattern`${capture()} + ${capture()}`; + const p = pattern`${expr()} + ${expr()}`; expect(p.templateParts).toBeDefined(); expect(p.captures).toBeDefined(); diff --git a/rewrite-javascript/rewrite/test/javascript/templating/raw-code.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/raw-code.test.ts index 271bde47224..580ebdb83f8 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/raw-code.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/raw-code.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {capture, JavaScriptVisitor, pattern, raw, rewrite, template, typescript, _} from "../../../src/javascript"; +import {expr, JavaScriptVisitor, pattern, raw, rewrite, template, typescript, _} from "../../../src/javascript"; import {J} from "../../../src/java"; describe('raw() function', () => { @@ -23,7 +23,7 @@ describe('raw() function', () => { describe('construction-time string interpolation', () => { test('splices raw code directly into template', () => { const methodName = "info"; - const msg = capture('msg'); + const msg = expr('msg'); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitMethodInvocation(method: J.MethodInvocation, p: any): Promise { @@ -51,7 +51,7 @@ describe('raw() function', () => { test('works with multiple raw() interpolations', () => { const obj = "console"; const method = "log"; - const msg = capture('msg'); + const msg = expr('msg'); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitMethodInvocation(invocation: J.MethodInvocation, p: any): Promise { @@ -78,7 +78,7 @@ describe('raw() function', () => { test('works with operators', () => { const operator = ">="; - const value = capture('value'); + const value = expr('value'); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { @@ -132,7 +132,7 @@ describe('raw() function', () => { describe('mixed with other parameters', () => { test('can mix raw() with capture()', () => { const prefix = "user"; - const value = capture('value'); + const value = expr('value'); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { @@ -190,7 +190,7 @@ describe('raw() function', () => { describe('raw() in patterns', () => { test('matches pattern with raw() method name', async () => { const methodName = "log"; - const msg = capture('msg'); + const msg = expr('msg'); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitMethodInvocation(method: J.MethodInvocation, p: any): Promise { @@ -215,7 +215,7 @@ describe('raw() function', () => { test('combines raw() in both pattern and template', async () => { const oldMethod = "warn"; const newMethod = "error"; - const msg = capture('msg'); + const msg = expr('msg'); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitMethodInvocation(method: J.MethodInvocation, p: any): Promise { @@ -266,7 +266,7 @@ describe('raw() function', () => { test('multiple raw() in single pattern', async () => { const obj = "console"; const method = "log"; - const msg = capture('msg'); + const msg = expr('msg'); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitMethodInvocation(invocation: J.MethodInvocation, p: any): Promise { @@ -290,8 +290,8 @@ describe('raw() function', () => { test('raw() with captures in complex pattern', async () => { const prefix = "user"; - const field = capture('field'); - const value = capture('value'); + const field = expr('field'); + const value = expr('value'); spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { diff --git a/rewrite-javascript/rewrite/test/javascript/templating/replace-with-context.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/replace-with-context.test.ts index 63d17463431..eaacf0e7d36 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/replace-with-context.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/replace-with-context.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {capture, JavaScriptVisitor, pattern, rewrite, template, typescript} from '../../../src/javascript'; +import {expr, JavaScriptVisitor, pattern, rewrite, template, typescript} from '../../../src/javascript'; import {RecipeSpec, fromVisitor} from '../../../src/test'; import {J} from '../../../src/java'; @@ -25,8 +25,8 @@ describe('replace with context', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { const rule = rewrite(() => { - const left = capture("left"); - const right = capture("right"); + const left = expr("left"); + const right = expr("right"); return { before: [ pattern`${left} == ${right}`, @@ -53,13 +53,13 @@ describe('replace with context', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitBinary(binary: J.Binary, p: any): Promise { const rule = rewrite(() => { - const expr = capture(); + const e = expr(); return { before: [ - pattern`${expr} || false`, - pattern`false || ${expr}` + pattern`${e} || false`, + pattern`false || ${e}` ], - after: template`${expr}` + after: template`${e}` }; }); return await rule.tryOn(this.cursor, binary) || binary; diff --git a/rewrite-javascript/rewrite/test/javascript/templating/replace.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/replace.test.ts index ab768757b9c..6bf35ba8e96 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/replace.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/replace.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {capture, JavaScriptVisitor, pattern, template, typescript} from "../../../src/javascript"; +import {expr, JavaScriptVisitor, pattern, template, typescript} from "../../../src/javascript"; import {Expression, J} from "../../../src/java"; import {create as produce} from "mutative"; import {produceAsync} from "../../../src"; @@ -93,7 +93,7 @@ describe('template2 replace', () => { }); // Use capture for late binding - myValue capture is looked up in the values map - const myValue = capture(); + const myValue = expr(); return template`${myValue}`.apply(literal, this.cursor, {values: new Map([[myValue, replacement]])}); } return literal; @@ -106,7 +106,7 @@ describe('template2 replace', () => { }); test('scalar capture preserves trailing semicolon', () => { - const arg = capture(); + const arg = expr(); const pat = pattern`foo(${arg})`; const tmpl = template`bar(${arg})`; @@ -129,7 +129,7 @@ describe('template2 replace', () => { }); test('scalar capture preserves comments', () => { - const arg = capture(); + const arg = expr(); const pat = pattern`oldFunc(${arg})`; const tmpl = template`newFunc(${arg})`; @@ -155,7 +155,7 @@ describe('template2 replace', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitMethodInvocation(method: J.MethodInvocation, p: any): Promise { if ((method.name as J.Identifier).simpleName === 'oldMethod' && method.select) { - const select = capture(); + const select = expr(); return await template`${select}.newMethod()`.apply( method, this.cursor, diff --git a/rewrite-javascript/rewrite/test/javascript/templating/rewrite.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/rewrite.test.ts index 3f3cad34485..0fa1033ee21 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/rewrite.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/rewrite.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {capture, JavaScriptVisitor, pattern, rewrite, template, typescript} from "../../../src/javascript"; +import {expr, JavaScriptVisitor, pattern, rewrite, template, typescript} from "../../../src/javascript"; import {J} from "../../../src/java"; describe('RewriteRule composition', () => { @@ -24,14 +24,14 @@ describe('RewriteRule composition', () => { test('chains two rules that both match', () => { // Rule 1: Swap operands of addition const rule1 = rewrite(() => ({ - before: pattern`${capture('a')} + ${capture('b')}`, - after: template`${capture('b')} + ${capture('a')}` + before: pattern`${expr('a')} + ${expr('b')}`, + after: template`${expr('b')} + ${expr('a')}` })); // Rule 2: Change '1 + x' to '2 + x' const rule2 = rewrite(() => ({ - before: pattern`1 + ${capture('x')}`, - after: template`2 + ${capture('x')}` + before: pattern`1 + ${expr('x')}`, + after: template`2 + ${expr('x')}` })); const combined = rule1.andThen(rule2); @@ -51,14 +51,14 @@ describe('RewriteRule composition', () => { test('first rule matches, second does not', () => { // Rule 1: Swap operands of addition const rule1 = rewrite(() => ({ - before: pattern`${capture('a')} + ${capture('b')}`, - after: template`${capture('b')} + ${capture('a')}` + before: pattern`${expr('a')} + ${expr('b')}`, + after: template`${expr('b')} + ${expr('a')}` })); // Rule 2: Change 'foo + x' to 'bar + x' (will not match after swap) const rule2 = rewrite(() => ({ - before: pattern`foo + ${capture('x')}`, - after: template`bar + ${capture('x')}` + before: pattern`foo + ${expr('x')}`, + after: template`bar + ${expr('x')}` })); const combined = rule1.andThen(rule2); @@ -79,13 +79,13 @@ describe('RewriteRule composition', () => { test('first rule does not match, returns undefined', () => { // Rule 1: Match subtraction const rule1 = rewrite(() => ({ - before: pattern`${capture('a')} - ${capture('b')}`, - after: template`${capture('b')} - ${capture('a')}` + before: pattern`${expr('a')} - ${expr('b')}`, + after: template`${expr('b')} - ${expr('a')}` })); // Rule 2: This should never be called const rule2 = rewrite(() => ({ - before: pattern`${capture('x')} + ${capture('y')}`, + before: pattern`${expr('x')} + ${expr('y')}`, after: template`0` })); @@ -107,20 +107,20 @@ describe('RewriteRule composition', () => { test('chains three rules', () => { // Rule 1: Swap operands const rule1 = rewrite(() => ({ - before: pattern`${capture('a')} + ${capture('b')}`, - after: template`${capture('b')} + ${capture('a')}` + before: pattern`${expr('a')} + ${expr('b')}`, + after: template`${expr('b')} + ${expr('a')}` })); // Rule 2: Change '1 + x' to '2 + x' const rule2 = rewrite(() => ({ - before: pattern`1 + ${capture('x')}`, - after: template`2 + ${capture('x')}` + before: pattern`1 + ${expr('x')}`, + after: template`2 + ${expr('x')}` })); // Rule 3: Change '2 + x' to '3 + x' const rule3 = rewrite(() => ({ - before: pattern`2 + ${capture('x')}`, - after: template`3 + ${capture('x')}` + before: pattern`2 + ${expr('x')}`, + after: template`3 + ${expr('x')}` })); const combined = rule1.andThen(rule2).andThen(rule3); @@ -141,14 +141,14 @@ describe('RewriteRule composition', () => { test('neither rule matches', () => { // Rule 1: Match subtraction const rule1 = rewrite(() => ({ - before: pattern`${capture('a')} - ${capture('b')}`, - after: template`${capture('b')} - ${capture('a')}` + before: pattern`${expr('a')} - ${expr('b')}`, + after: template`${expr('b')} - ${expr('a')}` })); // Rule 2: Match multiplication const rule2 = rewrite(() => ({ - before: pattern`${capture('a')} * ${capture('b')}`, - after: template`${capture('b')} * ${capture('a')}` + before: pattern`${expr('a')} * ${expr('b')}`, + after: template`${expr('b')} * ${expr('a')}` })); const combined = rule1.andThen(rule2); @@ -171,13 +171,13 @@ describe('RewriteRule composition', () => { test('first rule matches, alternative is not tried', () => { // Rule 1: Match addition const rule1 = rewrite(() => ({ - before: pattern`${capture('a')} + ${capture('b')}`, - after: template`${capture('b')} + ${capture('a')}` + before: pattern`${expr('a')} + ${expr('b')}`, + after: template`${expr('b')} + ${expr('a')}` })); // Rule 2: This should never be called when rule1 matches const rule2 = rewrite(() => ({ - before: pattern`${capture('x')} + ${capture('y')}`, + before: pattern`${expr('x')} + ${expr('y')}`, after: template`0` })); @@ -199,14 +199,14 @@ describe('RewriteRule composition', () => { test('first rule does not match, alternative matches', () => { // Rule 1: Match subtraction const rule1 = rewrite(() => ({ - before: pattern`${capture('a')} - ${capture('b')}`, - after: template`${capture('b')} - ${capture('a')}` + before: pattern`${expr('a')} - ${expr('b')}`, + after: template`${expr('b')} - ${expr('a')}` })); // Rule 2: Match addition const rule2 = rewrite(() => ({ - before: pattern`${capture('x')} + ${capture('y')}`, - after: template`${capture('y')} + ${capture('x')}` + before: pattern`${expr('x')} + ${expr('y')}`, + after: template`${expr('y')} + ${expr('x')}` })); const combined = rule1.orElse(rule2); @@ -227,14 +227,14 @@ describe('RewriteRule composition', () => { test('neither rule matches', () => { // Rule 1: Match subtraction const rule1 = rewrite(() => ({ - before: pattern`${capture('a')} - ${capture('b')}`, - after: template`${capture('b')} - ${capture('a')}` + before: pattern`${expr('a')} - ${expr('b')}`, + after: template`${expr('b')} - ${expr('a')}` })); // Rule 2: Match multiplication const rule2 = rewrite(() => ({ - before: pattern`${capture('x')} * ${capture('y')}`, - after: template`${capture('y')} * ${capture('x')}` + before: pattern`${expr('x')} * ${expr('y')}`, + after: template`${expr('y')} * ${expr('x')}` })); const combined = rule1.orElse(rule2); @@ -255,14 +255,14 @@ describe('RewriteRule composition', () => { test('specific pattern with general fallback', () => { // Specific: Match foo with second argument being 0 const specific = rewrite(() => ({ - before: pattern`foo(${capture('x')}, 0)`, - after: template`bar(${capture('x')})` + before: pattern`foo(${expr('x')}, 0)`, + after: template`bar(${expr('x')})` })); // General: Match foo with any two arguments const general = rewrite(() => ({ - before: pattern`foo(${capture('x')}, ${capture('y')})`, - after: template`baz(${capture('x')}, ${capture('y')})` + before: pattern`foo(${expr('x')}, ${expr('y')})`, + after: template`baz(${expr('x')}, ${expr('y')})` })); const combined = specific.orElse(general); @@ -287,20 +287,20 @@ const b = baz(x, 1);` test('chains three rules with orElse', () => { // Rule 1: Match subtraction const rule1 = rewrite(() => ({ - before: pattern`${capture('a')} - ${capture('b')}`, - after: template`subtract(${capture('a')}, ${capture('b')})` + before: pattern`${expr('a')} - ${expr('b')}`, + after: template`subtract(${expr('a')}, ${expr('b')})` })); // Rule 2: Match multiplication const rule2 = rewrite(() => ({ - before: pattern`${capture('a')} * ${capture('b')}`, - after: template`multiply(${capture('a')}, ${capture('b')})` + before: pattern`${expr('a')} * ${expr('b')}`, + after: template`multiply(${expr('a')}, ${expr('b')})` })); // Rule 3: Match addition const rule3 = rewrite(() => ({ - before: pattern`${capture('a')} + ${capture('b')}`, - after: template`add(${capture('a')}, ${capture('b')})` + before: pattern`${expr('a')} + ${expr('b')}`, + after: template`add(${expr('a')}, ${expr('b')})` })); const combined = rule1.orElse(rule2).orElse(rule3); @@ -329,20 +329,20 @@ const c = multiply(x, y);` test('orElse followed by andThen', () => { // Transform foo(x, 0) to bar(x), then wrap in parens const specific = rewrite(() => ({ - before: pattern`foo(${capture('x')}, 0)`, - after: template`bar(${capture('x')})` + before: pattern`foo(${expr('x')}, 0)`, + after: template`bar(${expr('x')})` })); // Fallback: transform foo(x, y) to baz(x, y), then wrap in parens const general = rewrite(() => ({ - before: pattern`foo(${capture('x')}, ${capture('y')})`, - after: template`baz(${capture('x')}, ${capture('y')})` + before: pattern`foo(${expr('x')}, ${expr('y')})`, + after: template`baz(${expr('x')}, ${expr('y')})` })); // Add parentheses const addParens = rewrite(() => ({ - before: pattern`${capture('expr')}`, - after: template`(${capture('expr')})` + before: pattern`${expr('expr')}`, + after: template`(${expr('expr')})` })); const combined = specific.orElse(general).andThen(addParens); @@ -367,26 +367,26 @@ const b = (baz(x, 1));` test('andThen followed by orElse', () => { // Rule 1: Transform subtraction to function call, then try to optimize const subToFunc = rewrite(() => ({ - before: pattern`${capture('a')} - ${capture('b')}`, - after: template`subtract(${capture('a')}, ${capture('b')})` + before: pattern`${expr('a')} - ${expr('b')}`, + after: template`subtract(${expr('a')}, ${expr('b')})` })); // Optimize: subtract(x, 0) -> x const optimizeSub = rewrite(() => ({ - before: pattern`subtract(${capture('x')}, 0)`, - after: template`${capture('x')}` + before: pattern`subtract(${expr('x')}, 0)`, + after: template`${expr('x')}` })); // Rule 2: Transform addition to function call, then try to optimize const addToFunc = rewrite(() => ({ - before: pattern`${capture('a')} + ${capture('b')}`, - after: template`add(${capture('a')}, ${capture('b')})` + before: pattern`${expr('a')} + ${expr('b')}`, + after: template`add(${expr('a')}, ${expr('b')})` })); // Optimize: add(x, 0) -> x const optimizeAdd = rewrite(() => ({ - before: pattern`add(${capture('x')}, 0)`, - after: template`${capture('x')}` + before: pattern`add(${expr('x')}, 0)`, + after: template`${expr('x')}` })); const combined = subToFunc.andThen(optimizeSub).orElse(addToFunc.andThen(optimizeAdd)); diff --git a/rewrite-javascript/rewrite/test/javascript/templating/statement-expression-wrapping.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/statement-expression-wrapping.test.ts index 38d6343bcca..a80019b6db3 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/statement-expression-wrapping.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/statement-expression-wrapping.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {capture, JavaScriptParser, JavaScriptVisitor, JS, pattern, template, typescript} from '../../../src/javascript'; +import {expr, stmt, JavaScriptParser, JavaScriptVisitor, JS, pattern, template, typescript} from '../../../src/javascript'; import {J} from '../../../src/java'; import {Cursor} from '../../../src'; import {fromVisitor, RecipeSpec} from '../../../src/test'; @@ -39,7 +39,7 @@ describe('Statement Expression Wrapping', () => { const initializerCursor = new Cursor(initializer, varDeclCursor); // Pattern to match the 'x' identifier - const pat = pattern`${capture('expr')}`; + const pat = pattern`${expr('expr')}`; const match = await pat.match(initializer, initializerCursor); expect(match).toBeTruthy(); @@ -69,7 +69,7 @@ describe('Statement Expression Wrapping', () => { const stmtCursor = new Cursor(exprStmt, cuCursor); // Pattern to match the statement - const pat = pattern`${capture('stmt')};`; + const pat = pattern`${stmt('stmt')};`; const match = await pat.match(exprStmt, stmtCursor); expect(match).toBeTruthy(); @@ -96,7 +96,7 @@ describe('Statement Expression Wrapping', () => { const initializerCursor = new Cursor(initializer); // Pattern to match the 'x' identifier - const pat = pattern`${capture('expr')}`; + const pat = pattern`${expr('expr')}`; const match = await pat.match(initializer, initializerCursor); expect(match).toBeTruthy(); @@ -126,7 +126,7 @@ describe('Statement Expression Wrapping', () => { const initializerCursor = new Cursor(initializer, varDeclCursor); // Pattern to match the method invocation - const pat = pattern`${capture('call')}`; + const pat = pattern`${expr('call')}`; const match = await pat.match(initializer, initializerCursor); expect(match).toBeTruthy(); diff --git a/rewrite-javascript/rewrite/test/javascript/templating/unnamed-capture.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/unnamed-capture.test.ts index c858196b78c..6ee6e55ff17 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/unnamed-capture.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/unnamed-capture.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {capture, JavaScriptVisitor, pattern, rewrite, template, typescript} from "../../../src/javascript"; +import {expr, ident, JavaScriptVisitor, pattern, rewrite, template, typescript} from "../../../src/javascript"; import {Expression, J} from "../../../src/java"; describe('unnamed capture', () => { @@ -24,15 +24,15 @@ describe('unnamed capture', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { - protected override async visitExpression(expr: Expression, p: any): Promise { - const left = capture(); - const right = capture(); + protected override async visitExpression(expression: Expression, p: any): Promise { + const left = expr(); + const right = expr(); //language=typescript - let m = await pattern`${left} + ${right}`.match(expr, this.cursor) || - await pattern`${left} * ${right}`.match(expr, this.cursor); + let m = await pattern`${left} + ${right}`.match(expression, this.cursor) || + await pattern`${left} * ${right}`.match(expression, this.cursor); - return m && await template`${right} + ${left}`.apply(expr, this.cursor, {values: m}) || expr; + return m && await template`${right} + ${left}`.apply(expression, this.cursor, {values: m}) || expression; } }); @@ -50,9 +50,9 @@ describe('unnamed capture', () => { spec.recipe = fromVisitor(new class extends JavaScriptVisitor { override async visitTernary(ternary: J.Ternary, p: any): Promise { - const obj = capture(); - const property = capture(); - const defaultValue = capture(); + const obj = expr(); + const property = ident(); + const defaultValue = expr(); //language=typescript return await rewrite(() => ({ diff --git a/rewrite-javascript/rewrite/test/javascript/templating/variadic-array-proxy.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/variadic-array-proxy.test.ts index 618d3c6fc3a..d224cc2bfc4 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/variadic-array-proxy.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/variadic-array-proxy.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {capture, JavaScriptVisitor, pattern, Pattern, template, Template, typescript} from "../../../src/javascript"; +import {expr, JavaScriptVisitor, pattern, Pattern, template, Template, typescript} from "../../../src/javascript"; import {Expression, J} from "../../../src/java"; describe('variadic array proxy behavior', () => { @@ -36,7 +36,7 @@ describe('variadic array proxy behavior', () => { } test('access first element with index [0]', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); spec.recipe = fromVisitor(matchAndReplace(pattern`foo(${args})`, template`bar(${args[0]})`)); return spec.rewriteRun( @@ -45,7 +45,7 @@ describe('variadic array proxy behavior', () => { }); test('access second element with index [1]', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); spec.recipe = fromVisitor(matchAndReplace(pattern`foo(${args})`, template`bar(${args[1]})`)); return spec.rewriteRun( @@ -54,7 +54,7 @@ describe('variadic array proxy behavior', () => { }); test('access last element with index [2]', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); spec.recipe = fromVisitor(matchAndReplace(pattern`foo(${args})`, template`bar(${args[2]})`)); return spec.rewriteRun( @@ -63,7 +63,7 @@ describe('variadic array proxy behavior', () => { }); test('slice from index 1', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); spec.recipe = fromVisitor(matchAndReplace(pattern`foo(${args})`, template`bar(${(args.slice(1))})`)); return spec.rewriteRun( @@ -72,7 +72,7 @@ describe('variadic array proxy behavior', () => { }); test('slice with start and end indices', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); spec.recipe = fromVisitor(matchAndReplace(pattern`foo(${args})`, template`bar(${(args.slice(1, 3))})`)); return spec.rewriteRun( @@ -81,7 +81,7 @@ describe('variadic array proxy behavior', () => { }); test('combine first element and slice for rest', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); spec.recipe = fromVisitor(matchAndReplace(pattern`foo(${args})`, template`bar(${args[0]}, ${(args.slice(1))})`)); return spec.rewriteRun( @@ -90,7 +90,7 @@ describe('variadic array proxy behavior', () => { }); test('slice can return empty array', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); spec.recipe = fromVisitor(matchAndReplace(pattern`foo(${args})`, template`bar(${(args.slice(10))})`)); return spec.rewriteRun( @@ -99,7 +99,7 @@ describe('variadic array proxy behavior', () => { }); test('reorder arguments using indices', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); spec.recipe = fromVisitor(matchAndReplace( pattern`foo(${args})`, template`bar(${args[1]}, ${args[0]}, ${args.slice(2)})`) diff --git a/rewrite-javascript/rewrite/test/javascript/templating/variadic-basic.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/variadic-basic.test.ts index a8c8b522e11..1daf6956b87 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/variadic-basic.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/variadic-basic.test.ts @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {capture} from "../../../src/javascript"; +import {expr} from "../../../src/javascript"; describe('variadic capture basic functionality', () => { test('regular capture is not variadic', () => { - const arg = capture('arg'); + const arg = expr('arg'); expect(arg.isVariadic()).toBe(false); expect(arg.getVariadicOptions()).toBeUndefined(); }); test('variadic: true creates variadic capture with defaults', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); expect(args.isVariadic()).toBe(true); const options = args.getVariadicOptions(); @@ -33,7 +33,7 @@ describe('variadic capture basic functionality', () => { }); test('variadic with min/max bounds', () => { - const args = capture({ variadic: { min: 1, max: 3 } }); + const args = expr({ variadic: { min: 1, max: 3 } }); expect(args.isVariadic()).toBe(true); const options = args.getVariadicOptions(); @@ -42,7 +42,7 @@ describe('variadic capture basic functionality', () => { }); test('variadic with all options', () => { - const args = capture({ name: 'args', + const args = expr({ name: 'args', variadic: { min: 2, max: 5 } }); @@ -52,7 +52,7 @@ describe('variadic capture basic functionality', () => { }); test('unnamed variadic capture', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); expect(args.isVariadic()).toBe(true); expect(args.getName()).toMatch(/^unnamed_\d+$/); }); diff --git a/rewrite-javascript/rewrite/test/javascript/templating/variadic-constraints.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/variadic-constraints.test.ts index 0c11af5f346..3ab1ce83aba 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/variadic-constraints.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/variadic-constraints.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {capture, JavaScriptParser, pattern} from "../../../src/javascript"; +import {expr, JavaScriptParser, pattern} from "../../../src/javascript"; import {J} from "../../../src/java"; describe('Variadic Capture Constraints', () => { @@ -38,7 +38,7 @@ describe('Variadic Capture Constraints', () => { describe('Array-level validation', () => { test('constraint validates entire array', async () => { - const args = capture({ + const args = expr({ variadic: true, constraint: (nodes) => nodes.every(n => typeof n.value === 'number') }); @@ -57,21 +57,21 @@ describe('Variadic Capture Constraints', () => { }); test('constraint works with empty array', async () => { - const args = capture({ + const args = expr({ variadic: true, constraint: (nodes) => nodes.length === 0 || nodes.every(n => typeof n.value === 'number') }); const pat = pattern`foo(${args})`; // Should match - empty array - const expr = await parseExpression('foo()'); - const match = await pat.match(expr, undefined!); + const parsed = await parseExpression('foo()'); + const match = await pat.match(parsed, undefined!); expect(match).toBeDefined(); expect((match?.get(args) as unknown as J[]).length).toBe(0); }); test('constraint can check array length', async () => { - const args = capture({ + const args = expr({ variadic: true, constraint: (nodes) => nodes.length >= 2 }); @@ -96,7 +96,7 @@ describe('Variadic Capture Constraints', () => { describe('Relationship validation', () => { test('constraint can validate relationships between elements', async () => { - const args = capture({ + const args = expr({ variadic: true, constraint: (nodes) => { // All must be numbers and in ascending order @@ -136,7 +136,7 @@ describe('Variadic Capture Constraints', () => { }); test('constraint can check first/last elements', async () => { - const args = capture({ + const args = expr({ variadic: true, constraint: (nodes) => { // First and last must be numbers @@ -167,7 +167,7 @@ describe('Variadic Capture Constraints', () => { describe('Combined with min/max', () => { test('constraint works together with min/max bounds', async () => { - const args = capture({ + const args = expr({ variadic: { min: 2, max: 4 }, constraint: (nodes) => nodes.every(n => typeof n.value === 'number') }); diff --git a/rewrite-javascript/rewrite/test/javascript/templating/variadic-container.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/variadic-container.test.ts index 3ecb29c0750..a9fc86ae5a7 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/variadic-container.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/variadic-container.test.ts @@ -28,7 +28,7 @@ * visitor to use matchSequence logic instead of strict length comparison when variadic * captures are present, similar to how method invocations are handled. */ -import {capture, JavaScriptParser, JavaScriptVisitor, JS, pattern, template, typescript} from "../../../src/javascript"; +import {expr, JavaScriptParser, JavaScriptVisitor, JS, pattern, template, typescript} from "../../../src/javascript"; import {J} from "../../../src/java"; import {fromVisitor, RecipeSpec} from "../../../src/test"; import {Cursor} from "../../../src"; @@ -52,7 +52,7 @@ describe('variadic pattern matching in containers', () => { } test('variadic capture in object destructuring pattern', async () => { - const props = capture({ variadic: true }); + const props = expr({ variadic: true }); const pat = pattern`function foo({${props}}) {}`; // The pattern has 1 element (variadic capture), but the target has 2+ elements @@ -81,8 +81,8 @@ describe('variadic pattern matching in containers', () => { }); test('variadic capture with required property in object destructuring', async () => { - const first = capture('first'); - const rest = capture({ variadic: true }); + const first = expr('first'); + const rest = expr({ variadic: true }); const pat = pattern`function foo({${first}, ${rest}}) {}`; // The pattern has 2 elements (first + variadic), but target has 3+ elements @@ -108,7 +108,7 @@ describe('variadic pattern matching in containers', () => { }); test('variadic capture in array destructuring pattern', async () => { - const elements = capture({ variadic: true }); + const elements = expr({ variadic: true }); const pat = pattern`function foo([${elements}]) {}`; // The pattern has 1 element (variadic capture), but the target has 2+ elements @@ -138,7 +138,7 @@ describe('variadic pattern matching in containers', () => { test('variadic capture with min/max constraints in containers', async () => { // Test min constraint - const props1 = capture({ variadic: { min: 2 } }); + const props1 = expr({ variadic: { min: 2 } }); const pat1 = pattern`function foo({${props1}}) {}`; expect(await pat1.match(await parse('function foo({}) {}'), undefined!)).toBeUndefined(); // min not satisfied @@ -147,7 +147,7 @@ describe('variadic pattern matching in containers', () => { expect(await pat1.match(await parse('function foo({a, b, c}) {}'), undefined!)).toBeDefined(); // more than min // Test max constraint - const props2 = capture({ variadic: { max: 2 } }); + const props2 = expr({ variadic: { max: 2 } }); const pat2 = pattern`function foo({${props2}}) {}`; expect(await pat2.match(await parse('function foo({}) {}'), undefined!)).toBeDefined(); // within max @@ -162,7 +162,7 @@ describe('variadic pattern matching in containers', () => { let receivedCursor: any = null; // Capture with constraint that checks the array length and verifies cursor is present - const props = capture({ + const props = expr({ variadic: true, constraint: (nodes: J[], context) => { receivedCursor = context.cursor; @@ -201,10 +201,10 @@ describe('variadic pattern matching in containers', () => { // This test reproduces the issue where variadic captures in containers // are not properly expanded during template replacement - const propsWithBindings = capture({ variadic: true }); - const ref = capture(); - const body = capture({ variadic: true }); - const name = capture(); + const propsWithBindings = expr({ variadic: true }); + const ref = expr(); + const body = expr({ variadic: true }); + const name = expr(); // Pattern: forwardRef(function Name({...props}, ref) {...}) const beforePattern = pattern`forwardRef(function ${name}({${propsWithBindings}}, ${ref}) {${body}})`; @@ -239,10 +239,10 @@ describe('variadic pattern matching in containers', () => { // This test verifies that { ref: ${ref} } preserves the "ref:" property name // when replacing ${ref} with the captured value - const ref = capture(); - const props = capture({ variadic: true }); - const body = capture({ variadic: true }); - const name = capture(); + const ref = expr(); + const props = expr({ variadic: true }); + const body = expr({ variadic: true }); + const name = expr(); // Reuse the forwardRef pattern but with a template that has propertyName const beforePattern = pattern`forwardRef(function ${name}({${props}}, ${ref}) {${body}})`; diff --git a/rewrite-javascript/rewrite/test/javascript/templating/variadic-expansion.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/variadic-expansion.test.ts index 6b06dae8147..55fb827adb9 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/variadic-expansion.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/variadic-expansion.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {any, capture, JavaScriptVisitor, pattern, Pattern, template, Template, typescript} from "../../../src/javascript"; +import {any, expr, JavaScriptVisitor, pattern, Pattern, template, Template, typescript} from "../../../src/javascript"; import {J} from "../../../src/java"; describe('variadic template expansion', () => { @@ -36,7 +36,7 @@ describe('variadic template expansion', () => { } test('expand variadic capture - zero arguments', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); const pat = pattern`foo(${args})`; const tmpl = template`bar(${args})`; @@ -48,7 +48,7 @@ describe('variadic template expansion', () => { }); test('expand variadic capture - single argument', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); const pat = pattern`foo(${args})`; const tmpl = template`bar(${args})`; @@ -60,7 +60,7 @@ describe('variadic template expansion', () => { }); test('expand variadic capture - multiple arguments', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); const pat = pattern`foo(${args})`; const tmpl = template`bar(${args})`; @@ -72,8 +72,8 @@ describe('variadic template expansion', () => { }); test('expand variadic with fixed arguments before', () => { - const first = capture(); - const rest = capture({ variadic: true }); + const first = expr(); + const rest = expr({ variadic: true }); const pat = pattern`foo(${first}, ${rest})`; const tmpl = template`bar(${first}, ${rest})`; @@ -85,8 +85,8 @@ describe('variadic template expansion', () => { }); test('expand variadic with fixed arguments after', () => { - const first = capture({ variadic: true }); - const last = capture(); + const first = expr({ variadic: true }); + const last = expr(); const pat = pattern`foo(${first}, ${last})`; const tmpl = template`bar(${first}, ${last})`; @@ -99,7 +99,7 @@ describe('variadic template expansion', () => { test('match with any() before-middle-after - zero before, zero after', () => { const before = any({ variadic: true }); - const middle = capture({constraint: node => node.simpleName === 'x'}); + const middle = expr({constraint: node => node.simpleName === 'x'}); const after = any({ variadic: true }); const pat = pattern`foo(${before}, ${middle}, ${after})`; const tmpl = template`bar(${middle})`; @@ -113,7 +113,7 @@ describe('variadic template expansion', () => { test('match with any() before-middle-after - one before, zero after', () => { const before = any({ variadic: true }); - const middle = capture({constraint: node => node.simpleName === 'x'}); + const middle = expr({constraint: node => node.simpleName === 'x'}); const after = any({ variadic: true }); const pat = pattern`foo(${before}, ${middle}, ${after})`; const tmpl = template`bar(${middle})`; @@ -127,7 +127,7 @@ describe('variadic template expansion', () => { test('match with any() before-middle-after - zero before, one after', () => { const before = any({ variadic: true }); - const middle = capture({constraint: node => node.simpleName === 'x'}); + const middle = expr({constraint: node => node.simpleName === 'x'}); const after = any({ variadic: true }); const pat = pattern`foo(${before}, ${middle}, ${after})`; const tmpl = template`bar(${middle})`; @@ -141,7 +141,7 @@ describe('variadic template expansion', () => { test('match with any() before-middle-after - one before, one after', () => { const before = any({ variadic: true }); - const middle = capture({constraint: node => node.simpleName === 'x'}); + const middle = expr({constraint: node => node.simpleName === 'x'}); const after = any({ variadic: true }); const pat = pattern`foo(${before}, ${middle}, ${after})`; const tmpl = template`bar(${middle})`; @@ -154,7 +154,7 @@ describe('variadic template expansion', () => { }); test('variadic followed by literal - should not consume literal', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); const pat = pattern`foo(${args}, 'bar')`; const tmpl = template`baz(${args})`; @@ -166,7 +166,7 @@ describe('variadic template expansion', () => { }); test('variadic followed by literal - no match if literal missing', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); const pat = pattern`foo(${args}, 'bar')`; const tmpl = template`baz(${args})`; diff --git a/rewrite-javascript/rewrite/test/javascript/templating/variadic-marker.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/variadic-marker.test.ts index 6d00c9e710d..a89230ccc6b 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/variadic-marker.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/variadic-marker.test.ts @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {capture, pattern} from "../../../src/javascript"; +import {expr, pattern} from "../../../src/javascript"; describe('variadic marker attachment', () => { test('regular capture does not have variadic marker', () => { - const arg = capture('arg'); + const arg = expr('arg'); const pat = pattern`foo(${arg})`; // Verify the capture object itself @@ -26,7 +26,7 @@ describe('variadic marker attachment', () => { }); test('variadic capture stores options in capture object', () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); const pat = pattern`foo(${args})`; // Verify the capture object itself @@ -35,7 +35,7 @@ describe('variadic marker attachment', () => { }); test('variadic capture with custom options stores them correctly', () => { - const args = capture({ + const args = expr({ variadic: { min: 1, max: 3 @@ -49,7 +49,7 @@ describe('variadic marker attachment', () => { }); test('pattern captures array includes variadic capture', () => { - const args = capture({ name: 'args', variadic: true }); + const args = expr({ name: 'args', variadic: true }); const pat = pattern`foo(${args})`; // Pattern should have the captures array diff --git a/rewrite-javascript/rewrite/test/javascript/templating/variadic-matching.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/variadic-matching.test.ts index b8368c9c590..c83ee28d57b 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/variadic-matching.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/variadic-matching.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {capture, JavaScriptParser, JS, pattern} from "../../../src/javascript"; +import {expr, JavaScriptParser, JS, pattern} from "../../../src/javascript"; describe('variadic pattern matching against real code', () => { const parser = new JavaScriptParser(); @@ -34,7 +34,7 @@ describe('variadic pattern matching against real code', () => { } test('variadic capture matches 0, 1, or many arguments', async () => { - const args = capture({ variadic: true }); + const args = expr({ variadic: true }); const pat = pattern`foo(${args})`; // Zero arguments @@ -63,8 +63,8 @@ describe('variadic pattern matching against real code', () => { }); test('required first argument + variadic rest', async () => { - const first = capture('first'); - const rest = capture({ variadic: true }); + const first = expr('first'); + const rest = expr({ variadic: true }); const pat = pattern`foo(${first}, ${rest})`; // Should NOT match foo() - missing required first @@ -88,7 +88,7 @@ describe('variadic pattern matching against real code', () => { test('variadic with min, max, and min+max constraints', async () => { // Test 1: min constraint - const args1 = capture({ variadic: { min: 2 } }); + const args1 = expr({ variadic: { min: 2 } }); const pat1 = pattern`foo(${args1})`; expect(await pat1.match(await parseExpr('foo()'), undefined!)).toBeUndefined(); // min not satisfied @@ -97,7 +97,7 @@ describe('variadic pattern matching against real code', () => { expect(await pat1.match(await parseExpr('foo(1, 2, 3)'), undefined!)).toBeDefined(); // more than min // Test 2: max constraint - const args2 = capture({ variadic: { max: 2 } }); + const args2 = expr({ variadic: { max: 2 } }); const pat2 = pattern`foo(${args2})`; expect(await pat2.match(await parseExpr('foo()'), undefined!)).toBeDefined(); // within max @@ -105,7 +105,7 @@ describe('variadic pattern matching against real code', () => { expect(await pat2.match(await parseExpr('foo(1, 2, 3)'), undefined!)).toBeUndefined(); // exceeds max // Test 3: min and max constraints - const args3 = capture({ variadic: { min: 1, max: 2 } }); + const args3 = expr({ variadic: { min: 1, max: 2 } }); const pat3 = pattern`foo(${args3})`; expect(await pat3.match(await parseExpr('foo()'), undefined!)).toBeUndefined(); // below min @@ -115,9 +115,9 @@ describe('variadic pattern matching against real code', () => { }); test('pattern with regular captures and variadic', async () => { - const first = capture('first'); - const middle = capture({ variadic: true }); - const last = capture('last'); + const first = expr('first'); + const middle = expr({ variadic: true }); + const last = expr('last'); const pat = pattern`foo(${first}, ${middle}, ${last})`; // Should match foo(1, 2) - first=1, middle=[], last=2 diff --git a/rewrite-javascript/rewrite/test/javascript/templating/variadic-statement.test.ts b/rewrite-javascript/rewrite/test/javascript/templating/variadic-statement.test.ts index 10add1f3163..4bf1d31010c 100644 --- a/rewrite-javascript/rewrite/test/javascript/templating/variadic-statement.test.ts +++ b/rewrite-javascript/rewrite/test/javascript/templating/variadic-statement.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import {fromVisitor, RecipeSpec} from "../../../src/test"; -import {capture, JavaScriptVisitor, Pattern, pattern, Template, template, typescript} from "../../../src/javascript"; +import {expr, stmt, JavaScriptVisitor, Pattern, pattern, Template, template, typescript} from "../../../src/javascript"; import {J} from "../../../src/java"; import {create as produce} from "mutative"; @@ -44,7 +44,7 @@ describe('variadic statement matching and expansion', () => { } test('match block with zero leading statements using any()', () => { - const leadingStmts = capture({ variadic: true }); + const leadingStmts = stmt({ variadic: true }); const pat = pattern`{ ${leadingStmts} return x; @@ -72,7 +72,7 @@ describe('variadic statement matching and expansion', () => { }); test('match block with one leading statement using any()', () => { - const leadingStmts = capture({ variadic: true }); + const leadingStmts = stmt({ variadic: true }); const pat = pattern`{ ${leadingStmts} return x; @@ -102,7 +102,7 @@ describe('variadic statement matching and expansion', () => { }); test('match block with multiple leading statements using any()', () => { - const leadingStmts = capture({ variadic: true }); + const leadingStmts = stmt({ variadic: true }); const pat = pattern`{ ${leadingStmts} return x; @@ -134,7 +134,7 @@ describe('variadic statement matching and expansion', () => { }); test('match block with trailing statements using any()', () => { - const trailingStmts = capture({ variadic: true }); + const trailingStmts = stmt({ variadic: true }); const pat = pattern`{ console.log('start'); ${trailingStmts} @@ -164,8 +164,8 @@ describe('variadic statement matching and expansion', () => { }); test('capture and reorder statements', () => { - const first = capture(); - const second = capture(); + const first = stmt(); + const second = stmt(); const pat = pattern`{ ${first} ${second} @@ -193,7 +193,7 @@ describe('variadic statement matching and expansion', () => { }); test('match with variadic min constraint', () => { - const leadingStmts = capture({ variadic: { min: 1 } }); + const leadingStmts = stmt({ variadic: { min: 1 } }); const pat = pattern`{ ${leadingStmts} return x; @@ -230,7 +230,7 @@ describe('variadic statement matching and expansion', () => { }); test('match with variadic max constraint', () => { - const leadingStmts = capture({ variadic: { max: 1 } }); + const leadingStmts = stmt({ variadic: { max: 1 } }); const pat = pattern`{ ${leadingStmts} return x; @@ -280,7 +280,7 @@ describe('variadic statement matching and expansion', () => { }); test('match empty block with variadic capture', () => { - const stmts = capture({ variadic: true }); + const stmts = stmt({ variadic: true }); const pat = pattern`{ ${stmts} }`; @@ -304,7 +304,7 @@ describe('variadic statement matching and expansion', () => { }); test('capture variadic statements for reuse', () => { - const stmts = capture({ variadic: true }); + const stmts = stmt({ variadic: true }); const pat = pattern`{ try { ${stmts} @@ -344,8 +344,8 @@ describe('variadic statement matching and expansion', () => { }); test('non-variadic capture should preserve trailing semicolons', () => { - // Bug report: using capture() (non-variadic) for function bodies loses trailing semicolons - const body = capture(); + // Bug report: using stmt() (non-variadic) for function bodies loses trailing semicolons + const body = stmt(); const pat = pattern`{${body}}`; const tmpl = template`{ console.log('before'); @@ -370,7 +370,7 @@ describe('variadic statement matching and expansion', () => { test('variadic capture should preserve trailing semicolons', () => { // Variadic captures should also preserve semicolons and formatting - const body = capture({ variadic: true }); + const body = stmt({ variadic: true }); const pat = pattern`{${body}}`; const tmpl = template`{ console.log('before'); @@ -395,7 +395,7 @@ describe('variadic statement matching and expansion', () => { test('function body capture with wrapper pattern should preserve semicolons', () => { // More complex example: extracting function body from wrapper pattern - const {args, body} = {args: capture(), body: capture({ variadic: true })}; + const {args, body} = {args: expr(), body: stmt({ variadic: true })}; const pat = pattern`{ return wrapper(function(${args}) {${body}}); }`;