/*
* 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 {Cursor} from '../..';
import {J, Type} from '../../java';
import {Any, Capture, CaptureConstraintContext, CaptureOptions, ConstraintFunction, TemplateParam, VariadicOptions} from './types';
/**
* Combines multiple constraints with AND logic.
* All constraints must return true for the combined constraint to pass.
*
* @example
* const largeEvenNumber = capture('n', {
* constraint: and(
* (node) => typeof node.value === 'number',
* (node) => node.value > 100,
* (node) => node.value % 2 === 0
* )
* });
*/
export function and(...constraints: ConstraintFunction[]): ConstraintFunction {
return (node: T, context: CaptureConstraintContext) => constraints.every(c => c(node, context));
}
/**
* Combines multiple constraints with OR logic.
* At least one constraint must return true for the combined constraint to pass.
*
* @example
* const stringOrNumber = capture('value', {
* constraint: or(
* (node) => node.kind === J.Kind.Literal && typeof node.value === 'string',
* (node) => node.kind === J.Kind.Literal && typeof node.value === 'number'
* )
* });
*/
export function or(...constraints: ConstraintFunction[]): ConstraintFunction {
return (node: T, context: CaptureConstraintContext) => constraints.some(c => c(node, context));
}
/**
* Negates a constraint.
* Returns true when the constraint returns false, and vice versa.
*
* @example
* const notString = capture('value', {
* constraint: not((node) => typeof node.value === 'string')
* });
*/
export function not(constraint: ConstraintFunction): ConstraintFunction {
return (node: T, context: CaptureConstraintContext) => !constraint(node, context);
}
// Symbol to access the internal capture name without triggering Proxy
export const CAPTURE_NAME_SYMBOL = Symbol('captureName');
// Symbol to access variadic options without triggering Proxy
export const CAPTURE_VARIADIC_SYMBOL = Symbol('captureVariadic');
// Symbol to access constraint function without triggering Proxy
export const CAPTURE_CONSTRAINT_SYMBOL = Symbol('captureConstraint');
// Symbol to access capturing flag without triggering Proxy
export const CAPTURE_CAPTURING_SYMBOL = Symbol('captureCapturing');
// Symbol to access type information without triggering Proxy
export const CAPTURE_TYPE_SYMBOL = Symbol('captureType');
// Symbol to identify RawCode instances
export const RAW_CODE_SYMBOL = Symbol('rawCode');
export class CaptureImpl implements Capture {
public readonly name: string;
[CAPTURE_NAME_SYMBOL]: string;
[CAPTURE_VARIADIC_SYMBOL]: VariadicOptions | undefined;
[CAPTURE_CONSTRAINT_SYMBOL]: ConstraintFunction | undefined;
[CAPTURE_CAPTURING_SYMBOL]: boolean;
[CAPTURE_TYPE_SYMBOL]: string | Type | undefined;
constructor(name: string, options?: CaptureOptions, capturing: boolean = true) {
this.name = name;
this[CAPTURE_NAME_SYMBOL] = name;
this[CAPTURE_CAPTURING_SYMBOL] = capturing;
// Normalize variadic options
if (options?.variadic) {
if (typeof options.variadic === 'boolean') {
this[CAPTURE_VARIADIC_SYMBOL] = {};
} else {
this[CAPTURE_VARIADIC_SYMBOL] = {
min: options.variadic.min,
max: options.variadic.max
};
}
}
// Store constraint if provided
if (options?.constraint) {
this[CAPTURE_CONSTRAINT_SYMBOL] = options.constraint;
}
// Store type if provided
if (options?.type) {
this[CAPTURE_TYPE_SYMBOL] = options.type;
}
}
getName(): string {
return this[CAPTURE_NAME_SYMBOL];
}
isVariadic(): boolean {
return this[CAPTURE_VARIADIC_SYMBOL] !== undefined;
}
getVariadicOptions(): VariadicOptions | undefined {
return this[CAPTURE_VARIADIC_SYMBOL];
}
getConstraint(): ConstraintFunction | undefined {
return this[CAPTURE_CONSTRAINT_SYMBOL];
}
isCapturing(): boolean {
return this[CAPTURE_CAPTURING_SYMBOL];
}
getType(): string | Type | undefined {
return this[CAPTURE_TYPE_SYMBOL];
}
}
export class TemplateParamImpl implements TemplateParam {
public readonly name: string;
constructor(name: string) {
this.name = name;
}
getName(): string {
return this.name;
}
}
/**
* Represents a property access on a captured value.
* When you access a property on a Capture (e.g., method.name), you get a CaptureValue
* that knows how to resolve that property from the matched values.
*/
export class CaptureValue {
constructor(
public readonly rootCapture: Capture,
public readonly propertyPath: string[],
public readonly arrayOperation?: { type: 'index' | 'slice' | 'length'; args?: number[] }
) {}
/**
* Resolves this capture value by looking up the root capture in the values map
* and navigating through the property path.
*/
resolve(values: Pick