import { Cursor, Tree } from '../..'; import { J, Type } from '../../java'; import type { Pattern } from "./pattern"; import type { Template } from "./template"; import type { CaptureValue, RawCode } from "./capture"; /** * Options for variadic captures that match zero or more nodes in a sequence. */ export interface VariadicOptions { /** * Minimum number of nodes that must be matched (default: 0). */ min?: number; /** * Maximum number of nodes that can be matched (default: unlimited). */ max?: number; } /** * Read-only access to captures matched so far during pattern matching. * Provides a consistent interface with MatchResult for looking up captured values. */ export interface CaptureMap { /** * Gets the value of a capture by Capture object or name. * Returns undefined if the capture hasn't been matched yet. */ get(capture: Capture): T | undefined; get(capture: string): any; /** * Checks if a capture has been matched. */ has(capture: Capture | string): boolean; } /** * Context passed to capture constraint functions. * Provides access to the cursor for AST navigation and previously matched captures. */ export interface CaptureConstraintContext { /** * The cursor pointing to the node being matched. * Allows navigating the AST (parent, root, etc.). */ cursor: Cursor; /** * Read-only view of values captured so far in the matching process. * Allows constraints to depend on previous captures. * Returns undefined for captures that haven't been processed yet. */ captures: CaptureMap; } /** * Constraint function for captures. * * Receives the node being validated and a context providing access to: * - cursor: For navigating the AST * - captures: For accessing previously matched captures * * For non-variadic captures: use ConstraintFunction where T is the node type * For variadic captures: use ConstraintFunction where T[] is the array type * * When used with variadic captures, the cursor points to the nearest common parent * of the captured elements. */ export type ConstraintFunction = (node: T, context: CaptureConstraintContext) => boolean; /** * Options for the capture function. * * The constraint function receives different parameter types depending on whether * the capture is variadic: * - For regular captures: constraint receives a single node of type T * - For variadic captures: constraint receives an array of nodes of type T[] * * The context parameter provides access to the cursor and previously matched captures. */ export interface CaptureOptions { name?: string; variadic?: boolean | VariadicOptions; /** * Optional constraint function that validates whether a captured node should be accepted. * The function receives: * - node: The captured node (or array of nodes for variadic captures) * - context: Provides access to cursor and previously matched captures * * @param node The captured node to validate * @param context Provides cursor for AST navigation and previously matched captures * @returns true if the capture should be accepted, false otherwise * * @example * ```typescript * // Simple node validation * capture('size', { * constraint: (node) => typeof node.value === 'number' && node.value > 100 * }) * * // Context-aware validation using cursor * capture('method', { * constraint: (node, context) => { * if (!node.name.simpleName.startsWith('get')) return false; * const cls = context.cursor.firstEnclosing(isClassDeclaration); * return cls?.name.simpleName === 'ApiController'; * } * }) * * // Validation depending on previous captures * const min = capture('min'); * const max = capture('max', { * constraint: (node, context) => { * const minVal = context.captures.get(min); * return minVal && node.value > minVal.value; * } * }) * ``` */ 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; } /** * Capture specification for pattern matching. * Represents a placeholder in a template pattern that can capture a part of the AST. * * @template T The expected type of the captured AST node (for TypeScript autocomplete) * * @remarks * **Important: Type Parameter is for IDE Support Only** * * The generic type parameter `` provides IDE autocomplete and type checking in your code, * but does NOT enforce any runtime constraints on what the capture will match. * * **Pattern Matching Behavior:** * - A bare `pattern`${capture()}`` will structurally match ANY expression * - Pattern structure determines matching: `pattern`foo(${capture()})`` only matches `foo()` calls with one arg * - Use structural patterns to narrow matching scope before applying semantic validation * * **Variadic Captures:** * Use `{ variadic: true }` to match zero or more nodes in a sequence: * ```typescript * const args = capture({ variadic: true }); * pattern`foo(${args})` // Matches: foo(), foo(a), foo(a, b, c) * ``` */ export interface Capture { /** * Gets the string name of this capture. */ getName(): string; /** * Returns true if this is a variadic capture (matching zero or more nodes). */ isVariadic(): boolean; /** * Returns the variadic options if this is a variadic capture, undefined otherwise. */ getVariadicOptions(): VariadicOptions | undefined; /** * Gets the constraint function if this capture has one. * For regular captures (T = Expression), constraint receives a single node. * For variadic captures (T = Expression[]), constraint receives an array of nodes. * The constraint function can optionally receive a cursor for context-aware validation. */ getConstraint?(): ConstraintFunction | undefined; } /** * Non-capturing pattern match specification. * Represents a placeholder in a pattern that matches AST nodes without binding them to a name. * * Use `any()` when you need to match structure without caring about the specific values. * The key difference from `Capture` is that `Any` cannot be used in templates - the TypeScript * type system prevents this at compile time. * * @template T The expected type of the matched AST node (for TypeScript autocomplete and constraints) * * @remarks * **Why Any is Separate from Capture:** * * Using a separate type provides compile-time safety: * - `pattern`foo(${any()})`` - ✅ OK in patterns * - `template`bar(${any()})`` - ❌ TypeScript error (Any not assignable to template parameters) * * This prevents logical errors where you try to use a non-capturing match in a template. * * **Semantic Parallel with TypeScript's `any`:** * * Just as TypeScript's `any` type means "be permissive about types here", * pattern matching's `any()` means "be permissive about values here": * - TypeScript `any`: Accept any type, don't check it * - Pattern `any()`: Match any value, don't bind it * * @example * // Match without capturing * const pat = pattern`foo(${any()})` * * @example * // Variadic any - match zero or more without capturing * const first = any(); * const rest = any({ variadic: true }); * const pat = pattern`bar(${first}, ${rest})` * * @example * // With constraints - validate but don't capture * const numericArg = any({ * constraint: (node) => typeof node.value === 'number' * }); * const pat = pattern`process(${numericArg})` */ export interface Any { /** * Gets the internal identifier for this any pattern. */ getName(): string; /** * Returns true if this is a variadic any (matching zero or more nodes). */ isVariadic(): boolean; /** * Returns the variadic options if this is a variadic any, undefined otherwise. */ getVariadicOptions(): VariadicOptions | undefined; /** * Gets the constraint function if this any pattern has one. * For regular any (T = Expression), constraint receives a single node. * For variadic any (T = Expression[]), constraint receives an array of nodes. */ getConstraint?(): ConstraintFunction | undefined; } /** * Template parameter specification for template-only parameter substitution. * Unlike Capture, TemplateParam does not support property access and is simpler. * * @template T The expected type of the parameter value (for TypeScript autocomplete only) */ export interface TemplateParam { /** * The name of the parameter, used to look up the value in the values map. */ readonly name: string; /** * Gets the string name of this parameter. */ getName(): string; } /** * Configuration options for patterns. */ export interface PatternOptions { /** * Declarations to provide type attribution context for the pattern. * These can include import statements, type declarations, function declarations, or any * other declarations needed for proper type information. They are prepended to the pattern * when parsing to ensure proper type attribution. * * @example * ```typescript * pattern`forwardRef(${capture('comp')})` * .configure({ * context: [ * `import { forwardRef } from 'react'`, * `type MyType = { value: number }` * ] * }) * ``` */ context?: string[]; /** * @deprecated Use `context` instead. This alias will be removed in a future version. * * Import statements to provide type attribution context. * These are prepended to the pattern when parsing to ensure proper type information. */ imports?: string[]; /** * NPM dependencies required for import resolution and type attribution. * Maps package names to version specifiers (e.g., { 'util': '^1.0.0' }). * The template engine will create a package.json with these dependencies. */ dependencies?: Record; /** * When true, allows patterns without type annotations to match code with type annotations. * This enables more flexible pattern matching during development or when full type attribution * is not needed. When false, enforces strict type matching where both pattern and target must * have matching type annotations. * * @default true (lenient matching enabled for backward compatibility) */ lenientTypeMatching?: boolean; /** * Enable debug logging for this pattern. * When enabled, all match attempts will log detailed information to stderr, * including the AST path traversed, mismatches encountered, and captured values. * * Can be overridden at the match() call level. * Global debug can be enabled via PATTERN_DEBUG=true environment variable. * * Precedence: match() call > pattern configure() > PATTERN_DEBUG env var * * @default undefined (inherits from environment or match() call) * * @example * ```typescript * // Pattern-level debug * const pat = pattern({ debug: true })`console.log(${value})`; * * // Disable debug for a noisy pattern when global debug is on * const noisyPat = pattern({ debug: false })`import ${x} from ${y}`; * ``` */ debug?: boolean; } /** * Options for individual match() calls. */ export interface MatchOptions { /** * Enable debug logging for this specific match() call. * Overrides pattern-level debug setting and global PATTERN_DEBUG env var. * * @example * ```typescript * // Debug just this call * const match = await pattern.match(node, cursor, { debug: true }); * * // Disable debug for this call even if pattern or global debug is on * const match = await pattern.match(node, cursor, { debug: false }); * ``` */ debug?: boolean; } /** * Valid parameter types for template literals. * - Capture: For pattern matching and reuse * - CaptureValue: Result of property access or array operations on captures (e.g., capture.prop, capture[0], capture.slice(1)) * - TemplateParam: For standalone template parameters * - RawCode: For inserting literal code strings at construction time * - Tree: AST nodes to be inserted directly * - Tree[]: Arrays of AST nodes (from variadic capture operations like slice) * - J.RightPadded: Wrapper containing an element with markers (element will be extracted) * - J.RightPadded[]: Array of wrappers (elements will be expanded) * - J.Container: Container with elements (elements will be expanded) * * Note: Primitive values (string, number, boolean) are NOT supported in template literals. * Use raw() for inserting code strings, or Template.builder() API for programmatic construction. */ export type TemplateParameter = Capture | CaptureValue | TemplateParam | RawCode | Tree | Tree[] | J.RightPadded | J.RightPadded[] | J.Container; /** * Parameter specification for template generation (internal). * Represents a placeholder in a template that will be replaced with a parameter value. * This is the internal wrapper used by the template engine. * * Note: The value is typed as `any` rather than `TemplateParameter` to allow flexible * internal handling without excessive type guards. The public API (template function) * constrains inputs to `TemplateParameter`, providing type safety at the API boundary. */ export interface Parameter { /** * The value to substitute into the template. */ value: any; } /** * Configuration options for templates. */ export interface TemplateOptions { /** * Declarations to provide type attribution context for the template. * These can include import statements, type declarations, function declarations, or any * other declarations needed for proper type information. They are prepended to the template * when parsing to ensure proper type attribution. * * @example * ```typescript * template`console.log(${capture('value')})` * .configure({ * context: [ * `type MyType = { value: number }`, * `const console = { log: (x: any) => void 0 }` * ] * }) * ``` */ context?: string[]; /** * @deprecated Use `context` instead. This alias will be removed in a future version. * * Import statements to provide type attribution context. * These are prepended to the template when parsing to ensure proper type information. */ imports?: string[]; /** * NPM dependencies required for import resolution and type attribution. * Maps package names to version specifiers (e.g., { 'util': '^1.0.0' }). * The template engine will create a package.json with these dependencies. */ dependencies?: Record; } /** * Options for template application. */ export interface ApplyOptions { /** * Values for parameters in the template. * Can be a Map, MatchResult, or plain object with capture names as keys. * * @example * ```typescript * // Using MatchResult from pattern matching * const match = await pattern.match(node, cursor); * await template.apply(node, cursor, { values: match }); * * // Using a Map * await template.apply(node, cursor, { * values: new Map([['x', someNode]]) * }); * * // Using a plain object * await template.apply(node, cursor, { * values: { x: someNode } * }); * ``` */ values?: Map | MatchResult | Record; } /** * Represents a replacement rule that can match a pattern and apply a template. */ export interface RewriteRule { /** * Attempts to apply this rewrite rule to the given AST node. * * @param cursor The cursor context at the current position in the AST * @param node The AST node to try matching and transforming * @returns The transformed node if a pattern matched, or `undefined` if no pattern matched. * When using in a visitor, always use the `|| node` pattern to return the original * node when there's no match: `return await rule.tryOn(this.cursor, node) || node;` */ tryOn(cursor: Cursor, node: J): Promise; /** * Chains this rule with another rule, creating a composite rule that applies both transformations sequentially. * * The resulting rule: * 1. First applies this rule to the input node * 2. If this rule matches and transforms the node, applies the next rule to the result * 3. If the next rule returns undefined (no match), keeps the result from the first rule * 4. If this rule returns undefined (no match), returns undefined without trying the next rule * * @param next The rule to apply after this rule * @returns A new RewriteRule that applies both rules in sequence * * @example * ```typescript * const rule1 = rewrite(() => { * const { a, b } = { a: capture(), b: capture() }; * return { * before: pattern`${a} + ${b}`, * after: template`${b} + ${a}` * }; * }); * * const rule2 = rewrite(() => ({ * before: pattern`${capture('x')} + 1`, * after: template`${capture('x')}++` * })); * * const combined = rule1.andThen(rule2); * // Will first swap operands, then if result matches "x + 1", change to "x++" * ``` */ andThen(next: RewriteRule): RewriteRule; /** * Creates a composite rule that tries this rule first, and if it doesn't match, tries an alternative rule. * * The resulting rule: * 1. First applies this rule to the input node * 2. If this rule matches and transforms the node, returns the result * 3. If this rule returns undefined (no match), tries the alternative rule on the original node * * @param alternative The rule to try if this rule doesn't match * @returns A new RewriteRule that tries both rules with fallback behavior * * @example * ```typescript * // Try specific pattern first, fall back to general pattern * const specific = rewrite(() => ({ * before: pattern`foo(${capture('x')}, 0)`, * after: template`bar(${capture('x')})` * })); * * const general = rewrite(() => ({ * before: pattern`foo(${capture('x')}, ${capture('y')})`, * after: template`baz(${capture('x')}, ${capture('y')})` * })); * * const combined = specific.orElse(general); * // Will try specific pattern first, if no match, try general pattern * ``` */ orElse(alternative: RewriteRule): RewriteRule; } /** * Context for preMatch predicate - only has cursor, no captures yet. */ export interface PreMatchContext { /** * The cursor pointing to the node being considered for matching. * Allows navigating the AST (parent, root, etc.). */ cursor: Cursor; } /** * Context for postMatch predicate - has cursor and captured values. */ export interface PostMatchContext { /** * The cursor pointing to the matched node. * Allows navigating the AST (parent, root, etc.). */ cursor: Cursor; /** * Values captured during pattern matching. */ captures: CaptureMap; } /** * Configuration for a replacement rule. */ export interface RewriteConfig { before: Pattern | Pattern[]; after: Template | ((match: MatchResult) => Template); /** * Optional predicate evaluated BEFORE pattern matching. * Use for efficient early filtering based on AST context when captures aren't needed. * If this returns false, pattern matching is skipped entirely. * * @param node The AST node being considered for matching * @param context Context providing cursor for AST navigation * @returns true to proceed with pattern matching, false to skip this node * * @example * ```typescript * rewrite(() => ({ * before: pattern`console.log(${_('msg')})`, * after: template`logger.info(${_('msg')})`, * preMatch: (node, {cursor}) => { * // Only attempt matching inside functions named 'handleError' * const method = cursor.firstEnclosing(isMethodDeclaration); * return method?.name.simpleName === 'handleError'; * } * })); * ``` */ preMatch?: (node: J, context: PreMatchContext) => boolean | Promise; /** * Optional predicate evaluated AFTER pattern matching succeeds. * Use when you need access to captured values to decide whether to apply the transformation. * If this returns false, the transformation is not applied. * * @param node The matched AST node * @param context Context providing cursor for AST navigation and captured values * @returns true to apply the transformation, false to skip * * @example * ```typescript * rewrite(() => ({ * before: pattern`${_('a')} + ${_('b')}`, * after: template`${_('b')} + ${_('a')}`, * postMatch: (node, {cursor, captures}) => { * // Only swap if 'a' is a literal number * const a = captures.get('a'); * return a?.kind === J.Kind.Literal && typeof a.value === 'number'; * } * })); * ``` */ postMatch?: (node: J, context: PostMatchContext) => boolean | Promise; } /** * Options for debugging pattern matching. * Used in Layer 1 (Core Instrumentation) to control debug output. */ export interface DebugOptions { /** * Enable detailed logging during pattern matching. */ enabled?: boolean; /** * Log structural comparison steps. */ logComparison?: boolean; /** * Log constraint evaluation. */ logConstraints?: boolean; } /** * A single debug log entry collected during pattern matching. * Part of Layer 1 (Core Instrumentation). */ export interface DebugLogEntry { /** * Severity level of the log entry. */ level: 'trace' | 'debug' | 'info' | 'warn'; /** * The scope/category of the log entry. */ scope: 'matching' | 'comparison' | 'constraint'; /** * Path in the AST where this log entry was generated. */ path: string[]; /** * Human-readable message. */ message: string; /** * Optional data associated with this log entry. */ data?: any; } /** * Detailed explanation of why a pattern failed to match. * Built by Layer 1 (Core Instrumentation) and exposed by Layer 2 (API). */ export interface MatchExplanation { /** * The reason for the match failure. */ reason: 'structural-mismatch' | 'constraint-failed' | 'type-mismatch' | 'kind-mismatch' | 'value-mismatch' | 'array-length-mismatch'; /** * Path in the AST where the failure occurred (e.g., ['select', 'name']). */ path: string[]; /** * Human-readable description of what was expected. */ expected: string; /** * Human-readable description of what was actually found. */ actual: string; /** * For constraint failures, details about which constraints failed. */ constraintFailures?: Array<{ captureName: string; actualValue: any; error?: string; }>; /** * Additional context about the failure. */ details?: string; } /** * Interface for accessing captured nodes from a successful pattern match. * Part of the public API. */ export interface MatchResult { /** * Get a captured node by name or by Capture object. * * @param capture The capture name (string) or Capture object * @returns The captured node(s), or undefined if not found */ get(capture: string): any; get(capture: Capture): T | undefined; /** * Checks if a capture has been matched. * * @param capture The capture name (string) or Capture object * @returns true if the capture exists in the match result */ has(capture: Capture | string): boolean; } /** * Result of a pattern match attempt with debug information. * Part of Layer 2 (Public API). */ export interface MatchAttemptResult { /** * Whether the pattern matched successfully. */ matched: boolean; /** * If matched, the match result with captured nodes. Undefined if not matched. * Use `result.get('captureName')` or `result.get(captureObject)` to access captures. */ result?: MatchResult; /** * If not matched, explanation of why. Undefined if matched. */ explanation?: MatchExplanation; /** * Debug log entries collected during matching (if debug was enabled). */ debugLog?: DebugLogEntry[]; } //# sourceMappingURL=types.d.ts.map