/*
* 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 {JavaScriptVisitor} from './visitor';
import {J, Type, Expression, Statement, isIdentifier} from '../java';
import {JS, JSX} from './tree';
import {Cursor, Tree} from "../tree";
/**
* A visitor that compares two AST trees in lock step.
* It takes another `J` instance as context and visits both trees simultaneously.
* The visit operation is aborted when the nodes don't match.
*/
export class JavaScriptComparatorVisitor extends JavaScriptVisitor {
/**
* Flag indicating whether the trees match so far
*/
protected match: boolean = true;
/**
* Cursor tracking the current position in the target tree.
* Maintained in parallel with the pattern tree cursor (this.cursor).
*/
protected targetCursor?: Cursor;
/**
* Compares two AST trees.
*
* @param tree1 The first tree to compare (pattern tree)
* @param tree2 The second tree to compare (target tree)
* @param parentCursor1 Optional parent cursor for the pattern tree (for navigating to root)
* @param parentCursor2 Optional parent cursor for the target tree (for navigating to root)
* @returns true if the trees match, false otherwise
*/
async compare(tree1: J, tree2: J, parentCursor1?: Cursor, parentCursor2?: Cursor): Promise {
this.match = true;
// Initialize targetCursor with parent if provided, otherwise undefined (will be set by visit())
this.targetCursor = parentCursor2;
// Initialize this.cursor (pattern cursor) with parent if provided
this.cursor = parentCursor1 || new Cursor(undefined, undefined);
await this.visit(tree1, tree2);
return this.match;
}
/**
* Checks if two nodes have the same kind.
*
* @param j The node being visited
* @param other The other node to compare with
* @returns true if the nodes have the same kind, false otherwise
*/
protected hasSameKind(j: J, other: J): boolean {
return j.kind === other.kind;
}
/**
* Aborts the visit operation by setting the match flag to false.
*
* @param t The node being compared
* @param reason Optional reason for the mismatch (e.g., 'kind-mismatch', 'property-mismatch')
* @param propertyName Optional property name where mismatch occurred
* @param expected Optional expected value
* @param actual Optional actual value
*/
protected abort(t: T, reason?: string, propertyName?: string, expected?: any, actual?: any): T {
this.match = false;
return t;
}
/**
* Specialized abort methods for common mismatch scenarios.
* These provide a cleaner API at call sites.
* Can be overridden in subclasses to extract values from cursors and provide richer error messages.
*/
protected kindMismatch() {
const pattern = this.cursor?.value as any;
return this.abort(pattern, 'kind-mismatch');
}
protected structuralMismatch(propertyName?: string) {
const pattern = this.cursor?.value as any;
return this.abort(pattern, 'structural-mismatch', propertyName);
}
protected arrayLengthMismatch(propertyName: string) {
const pattern = this.cursor?.value as any;
return this.abort(pattern, 'array-length-mismatch', propertyName);
}
protected valueMismatch(propertyName?: string, expected?: any, actual?: any) {
const pattern = this.cursor?.value as any;
// If values not provided, try to extract from cursors (only if propertyName is available)
const expectedVal = expected !== undefined ? expected : (propertyName ? (pattern as any)?.[propertyName] : pattern);
const actualVal = actual !== undefined ? actual : (propertyName ? (this.targetCursor?.value as any)?.[propertyName] : this.targetCursor?.value);
return this.abort(pattern, 'value-mismatch', propertyName, expectedVal, actualVal);
}
protected typeMismatch(propertyName?: string) {
const pattern = this.cursor?.value as any;
const target = this.targetCursor?.value as any;
return this.abort(pattern, 'type-mismatch', propertyName, pattern?.type, target?.type);
}
/**
* Helper method to visit an array property by iterating through both arrays in lock-step.
* Checks length mismatch first, then visits each element pair.
* Can be overridden in subclasses to add path tracking or other instrumentation.
*
* @param parent The parent node containing the array property
* @param propertyName The name of the array property
* @param array1 The array from the first tree
* @param array2 The array from the second tree
* @param visitor Function to visit each element pair (no need to return anything)
* @returns undefined, modifying this.match if a mismatch occurs
*/
protected async visitArrayProperty(
parent: J,
propertyName: string,
array1: T[],
array2: T[],
visitor: (item1: T, item2: T, index: number) => Promise
): Promise {
// Check length mismatch
if (array1.length !== array2.length) {
this.arrayLengthMismatch(propertyName);
return;
}
// Visit each element in lock step
for (let i = 0; i < array1.length; i++) {
await visitor(array1[i], array2[i], i);
if (!this.match) return;
}
}
/**
* Helper method to visit a container property with proper context.
* Can be overridden in subclasses to add path tracking or other instrumentation.
*
* @param parent The parent node containing the container property
* @param propertyName The name of the container property
* @param container The container from the first tree
* @param otherContainer The container from the second tree
* @returns The container from the first tree
*/
protected async visitContainerProperty(
propertyName: string,
container: J.Container,
otherContainer: J.Container
): Promise> {
// Default implementation just calls visitContainer
// Subclasses can override to add property context
await this.visitContainer(container, otherContainer as any);
return container;
}
/**
* Helper to visit a RightPadded property with property context.
* This allows subclasses to track which property is being visited.
*
* @param propertyName The property name for context
* @param rightPadded The RightPadded from the first tree
* @param otherRightPadded The RightPadded from the second tree
* @returns The RightPadded from the first tree
*/
protected async visitRightPaddedProperty(
propertyName: string,
rightPadded: J.RightPadded,
otherRightPadded: J.RightPadded
): Promise> {
// Default implementation just calls visitRightPadded
// Subclasses can override to add property context
return await this.visitRightPadded(rightPadded, otherRightPadded as any);
}
/**
* Helper to visit a LeftPadded property with property context.
* This allows subclasses to track which property is being visited.
*
* @param propertyName The property name for context
* @param leftPadded The LeftPadded from the first tree
* @param otherLeftPadded The LeftPadded from the second tree
* @returns The LeftPadded from the first tree
*/
protected async visitLeftPaddedProperty(
propertyName: string,
leftPadded: J.LeftPadded,
otherLeftPadded: J.LeftPadded
): Promise> {
// Default implementation just calls visitLeftPadded
// Subclasses can override to add property context
return await this.visitLeftPadded(leftPadded, otherLeftPadded as any);
}
/**
* Generic method to visit a property value using the appropriate visitor method.
* This ensures wrappers (RightPadded, LeftPadded, Container) are properly tracked on the cursor.
*
* @param j The property value from the first tree
* @param other The corresponding property value from the second tree
* @param propertyName Optional property name for error reporting
* @returns The visited property value from the first tree
*/
protected async visitProperty(j: any, other: any, propertyName?: string): Promise {
// Handle null/undefined (but not other falsy values like 0, false, '')
if (j == null || other == null) {
if (j !== other) {
return this.structuralMismatch(propertyName);
}
return j;
}
const kind = (j as any).kind;
// Check wrappers by kind
if (kind === J.Kind.RightPadded) {
return propertyName ? await this.visitRightPaddedProperty(propertyName, j, other) :
await this.visitRightPadded(j, other);
}
if (kind === J.Kind.LeftPadded) {
return propertyName ? await this.visitLeftPaddedProperty(propertyName, j, other) :
await this.visitLeftPadded(j, other);
}
if (kind === J.Kind.Container) {
// Use visitContainerProperty when propertyName is provided for proper context tracking
if (propertyName) {
return await this.visitContainerProperty(propertyName, j, other);
}
return await this.visitContainer(j, other);
}
// Check if it's a Space (skip comparison)
if (kind === J.Kind.Space) {
return j;
}
// Check if it's a Type node
if (Type.isType(j)) {
return await this.visitType(j, other);
}
// Check if it's a Tree node (has a kind property with a string value)
if (kind !== undefined && typeof kind === 'string') {
return await this.visit(j, other);
}
// For primitive values, compare directly
if (j !== other) {
return this.valueMismatch(propertyName, j, other);
}
return j;
}
/**
* Generic method to visit all properties of an element, calling visitProperty for each.
* This automatically handles wrappers and ensures proper cursor tracking.
* Also checks that both elements have the same kind.
*
* @param j The element from the first tree
* @param other The corresponding element from the second tree
* @returns The visited element from the first tree
*/
protected async visitElement(j: T, other: T): Promise {
if (!this.match) return j;
// Check if kinds match
if (j.kind !== other.kind) {
return this.kindMismatch();
}
// Iterate over all properties
for (const key of Object.keys(j)) {
// Skip internal/private properties, id property, and markers property
if (key.startsWith('_') || key === 'kind' || key === 'id' || key === 'markers') {
continue;
}
const jValue = (j as any)[key];
const otherValue = (other as any)[key];
// Handle arrays - compare element by element
if (Array.isArray(jValue)) {
if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
return this.arrayLengthMismatch(key);
}
for (let i = 0; i < jValue.length; i++) {
await this.visitProperty(jValue[i], otherValue[i], `${key}[${i}]`);
if (!this.match) return j;
}
} else {
// Visit the property (which will handle wrappers, trees, primitives, etc.)
await this.visitProperty(jValue, otherValue, key);
if (!this.match) return j;
}
}
return j;
}
override async visit(j: Tree, p: J, parent?: Cursor): Promise {
// If we've already found a mismatch, abort further processing
if (!this.match) return j as R;
// Check if the nodes have the same kind
if (!this.hasSameKind(j as J, p)) {
return this.kindMismatch() as R;
}
// Update targetCursor to track the target node in parallel with the pattern cursor
// (Can be overridden by subclasses if they need cursor access before calling super)
const savedTargetCursor = this.targetCursor;
this.targetCursor = new Cursor(p, this.targetCursor);
try {
// Continue with normal visitation, passing the other node as context
return await super.visit(j, p);
} finally {
this.targetCursor = savedTargetCursor;
}
}
/**
* Override visitRightPadded to compare only the elements, not markers or spacing.
* The context parameter p contains the corresponding element from the other tree.
* Pushes the wrapper onto the cursor stack so captures can access it.
* Also updates targetCursor in parallel.
*/
public async visitRightPadded(right: J.RightPadded, p: J): Promise> {
if (!this.match) return right;
// Extract the other element if it's also a RightPadded
const isRightPadded = (p as any).kind === J.Kind.RightPadded;
const otherWrapper = isRightPadded ? (p as unknown) as J.RightPadded : undefined;
const otherElement = isRightPadded ? otherWrapper!.element : p;
// Push wrappers onto both cursors, then compare only the elements, not markers or spacing
const savedCursor = this.cursor;
const savedTargetCursor = this.targetCursor;
this.cursor = new Cursor(right, this.cursor);
this.targetCursor = otherWrapper ? new Cursor(otherWrapper, this.targetCursor) : this.targetCursor;
try {
// Call visitProperty without propertyName to avoid pushing spurious 'element' path entries
// The property context should be provided through visitRightPaddedProperty() if needed
await this.visitProperty(right.element, otherElement);
} finally {
this.cursor = savedCursor;
this.targetCursor = savedTargetCursor;
}
return right;
}
/**
* Override visitLeftPadded to compare only the elements, not markers or spacing.
* The context parameter p contains the corresponding element from the other tree.
* Pushes the wrapper onto the cursor stack so captures can access it.
* Also updates targetCursor in parallel.
*/
public async visitLeftPadded(left: J.LeftPadded, p: J): Promise> {
if (!this.match) return left;
// Extract the other element if it's also a LeftPadded
const isLeftPadded = (p as any).kind === J.Kind.LeftPadded;
const otherWrapper = isLeftPadded ? (p as unknown) as J.LeftPadded : undefined;
const otherElement = isLeftPadded ? otherWrapper!.element : p;
// Push wrappers onto both cursors, then compare only the elements, not markers or spacing
const savedCursor = this.cursor;
const savedTargetCursor = this.targetCursor;
this.cursor = new Cursor(left, this.cursor);
this.targetCursor = otherWrapper ? new Cursor(otherWrapper, this.targetCursor) : this.targetCursor;
try {
// Call visitProperty without propertyName to avoid pushing spurious 'element' path entries
// The property context should be provided through visitLeftPaddedProperty() if needed
await this.visitProperty(left.element, otherElement);
} finally {
this.cursor = savedCursor;
this.targetCursor = savedTargetCursor;
}
return left;
}
/**
* Override visitContainer to compare only the elements, not markers or spacing.
* The context parameter p contains the corresponding element from the other tree.
* Pushes the wrapper onto the cursor stack so captures can access it.
* Also updates targetCursor in parallel.
*/
public async visitContainer(container: J.Container, p: J): Promise> {
if (!this.match) return container;
// Extract the other elements if it's also a Container
const isContainer = (p as any).kind === J.Kind.Container;
const otherContainer = isContainer ? (p as unknown) as J.Container : undefined;
const otherElements: J.RightPadded[] = isContainer ? otherContainer!.elements : (p as any);
// Compare elements array length
if (container.elements.length !== otherElements.length) {
return this.arrayLengthMismatch('elements');
}
// Push wrappers onto both cursors, then compare each element
const savedCursor = this.cursor;
const savedTargetCursor = this.targetCursor;
this.cursor = new Cursor(container, this.cursor);
this.targetCursor = otherContainer ? new Cursor(otherContainer, this.targetCursor) : this.targetCursor;
try {
for (let i = 0; i < container.elements.length; i++) {
await this.visitProperty(container.elements[i], otherElements[i]);
if (!this.match) return container;
}
} finally {
this.cursor = savedCursor;
this.targetCursor = savedTargetCursor;
}
return container;
}
/**
* Overrides the visitBinary method to compare binary expressions.
*
* @param binary The binary expression to visit
* @param other The other binary expression to compare with
* @returns The visited binary expression, or undefined if the visit was aborted
*/
override async visitBinary(binary: J.Binary, other: J): Promise {
return this.visitElement(binary, other as J.Binary);
}
/**
* Overrides the visitIdentifier method to compare identifiers.
*
* @param identifier The identifier to visit
* @param other The other identifier to compare with
* @returns The visited identifier, or undefined if the visit was aborted
*/
override async visitIdentifier(identifier: J.Identifier, other: J): Promise {
return this.visitElement(identifier, other as J.Identifier);
}
/**
* Overrides the visitLiteral method to compare literals.
*
* @param literal The literal to visit
* @param other The other literal to compare with
* @returns The visited literal, or undefined if the visit was aborted
*/
override async visitLiteral(literal: J.Literal, other: J): Promise {
return this.visitElement(literal, other as J.Literal);
}
/**
* Overrides the visitBlock method to compare blocks.
*
* @param block The block to visit
* @param other The other block to compare with
* @returns The visited block, or undefined if the visit was aborted
*/
override async visitBlock(block: J.Block, other: J): Promise {
return this.visitElement(block, other as J.Block);
}
/**
* Overrides the visitJsCompilationUnit method to compare compilation units.
*
* @param compilationUnit The compilation unit to visit
* @param other The other compilation unit to compare with
* @returns The visited compilation unit, or undefined if the visit was aborted
*/
override async visitJsCompilationUnit(compilationUnit: JS.CompilationUnit, other: J): Promise {
return this.visitElement(compilationUnit, other as JS.CompilationUnit);
}
/**
* Overrides the visitAlias method to compare aliases.
*
* @param alias The alias to visit
* @param other The other alias to compare with
* @returns The visited alias, or undefined if the visit was aborted
*/
override async visitAlias(alias: JS.Alias, other: J): Promise {
return this.visitElement(alias, other as JS.Alias);
}
/**
* Overrides the visitArrowFunction method to compare arrow functions.
*
* @param arrowFunction The arrow function to visit
* @param other The other arrow function to compare with
* @returns The visited arrow function, or undefined if the visit was aborted
*/
override async visitArrowFunction(arrowFunction: JS.ArrowFunction, other: J): Promise {
return this.visitElement(arrowFunction, other as JS.ArrowFunction);
}
/**
* Overrides the visitAwait method to compare await expressions.
*
* @param await_ The await expression to visit
* @param other The other await expression to compare with
* @returns The visited await expression, or undefined if the visit was aborted
*/
override async visitAwait(await_: JS.Await, other: J): Promise {
return this.visitElement(await_, other as JS.Await);
}
/**
* Overrides the visitJsxTag method to compare JSX tags.
*
* @param element The JSX tag to visit
* @param other The other JSX tag to compare with
* @returns The visited JSX tag, or undefined if the visit was aborted
*/
override async visitJsxTag(element: JSX.Tag, other: J): Promise {
return this.visitElement(element, other as JSX.Tag);
}
/**
* Overrides the visitJsxAttribute method to compare JSX attributes.
*
* @param attribute The JSX attribute to visit
* @param other The other JSX attribute to compare with
* @returns The visited JSX attribute, or undefined if the visit was aborted
*/
override async visitJsxAttribute(attribute: JSX.Attribute, other: J): Promise {
return this.visitElement(attribute, other as JSX.Attribute);
}
/**
* Overrides the visitJsxSpreadAttribute method to compare JSX spread attributes.
*
* @param spread The JSX spread attribute to visit
* @param other The other JSX spread attribute to compare with
* @returns The visited JSX spread attribute, or undefined if the visit was aborted
*/
override async visitJsxSpreadAttribute(spread: JSX.SpreadAttribute, other: J): Promise {
return this.visitElement(spread, other as JSX.SpreadAttribute);
}
/**
* Overrides the visitJsxExpression method to compare JSX expressions.
*
* @param expr The JSX expression to visit
* @param other The other JSX expression to compare with
* @returns The visited JSX expression, or undefined if the visit was aborted
*/
override async visitJsxEmbeddedExpression(expr: JSX.EmbeddedExpression, other: J): Promise {
return this.visitElement(expr, other as JSX.EmbeddedExpression);
}
/**
* Overrides the visitJsxNamespacedName method to compare JSX namespaced names.
*
* @param ns The JSX namespaced name to visit
* @param other The other JSX namespaced name to compare with
* @returns The visited JSX namespaced name, or undefined if the visit was aborted
*/
override async visitJsxNamespacedName(ns: JSX.NamespacedName, other: J): Promise {
return this.visitElement(ns, other as JSX.NamespacedName);
}
/**
* Overrides the visitConditionalType method to compare conditional types.
*
* @param conditionalType The conditional type to visit
* @param other The other conditional type to compare with
* @returns The visited conditional type, or undefined if the visit was aborted
*/
override async visitConditionalType(conditionalType: JS.ConditionalType, other: J): Promise {
return this.visitElement(conditionalType, other as JS.ConditionalType);
}
/**
* Overrides the visitDelete method to compare delete expressions.
*
* @param delete_ The delete expression to visit
* @param other The other delete expression to compare with
* @returns The visited delete expression, or undefined if the visit was aborted
*/
override async visitDelete(delete_: JS.Delete, other: J): Promise {
return this.visitElement(delete_, other as JS.Delete);
}
/**
* Overrides the visitExpressionStatement method to compare expression statements.
*
* @param expressionStatement The expression statement to visit
* @param other The other expression statement to compare with
* @returns The visited expression statement, or undefined if the visit was aborted
*/
override async visitExpressionStatement(expressionStatement: JS.ExpressionStatement, other: J): Promise {
return this.visitElement(expressionStatement, other as JS.ExpressionStatement);
}
/**
* Overrides the visitExpressionWithTypeArguments method to compare expressions with type arguments.
*
* @param expressionWithTypeArguments The expression with type arguments to visit
* @param other The other expression with type arguments to compare with
* @returns The visited expression with type arguments, or undefined if the visit was aborted
*/
override async visitExpressionWithTypeArguments(expressionWithTypeArguments: JS.ExpressionWithTypeArguments, other: J): Promise {
return this.visitElement(expressionWithTypeArguments, other as JS.ExpressionWithTypeArguments);
}
/**
* Overrides the visitFunctionCall method to compare method invocations.
*
* @param functionCall The function call to visit
* @param other The other function call to compare with
* @returns The visited function call, or undefined if the visit was aborted
*/
override async visitFunctionCall(functionCall: JS.FunctionCall, other: J): Promise {
return this.visitElement(functionCall, other as JS.FunctionCall);
}
/**
* Overrides the visitFunctionType method to compare function types.
*
* @param functionType The function type to visit
* @param other The other function type to compare with
* @returns The visited function type, or undefined if the visit was aborted
*/
override async visitFunctionType(functionType: JS.FunctionType, other: J): Promise {
return this.visitElement(functionType, other as JS.FunctionType);
}
/**
* Overrides the visitInferType method to compare infer types.
*
* @param inferType The infer type to visit
* @param other The other infer type to compare with
* @returns The visited infer type, or undefined if the visit was aborted
*/
override async visitInferType(inferType: JS.InferType, other: J): Promise {
return this.visitElement(inferType, other as JS.InferType);
}
/**
* Overrides the visitImportType method to compare import types.
*
* @param importType The import type to visit
* @param other The other import type to compare with
* @returns The visited import type, or undefined if the visit was aborted
*/
override async visitImportType(importType: JS.ImportType, other: J): Promise {
return this.visitElement(importType, other as JS.ImportType);
}
/**
* Overrides the visitImportDeclaration method to compare import declarations.
*
* @param jsImport The import declaration to visit
* @param other The other import declaration to compare with
* @returns The visited import declaration, or undefined if the visit was aborted
*/
override async visitImportDeclaration(jsImport: JS.Import, other: J): Promise {
return this.visitElement(jsImport, other as JS.Import);
}
/**
* Overrides the visitImportClause method to compare import clauses.
*
* @param importClause The import clause to visit
* @param other The other import clause to compare with
* @returns The visited import clause, or undefined if the visit was aborted
*/
override async visitImportClause(importClause: JS.ImportClause, other: J): Promise {
return this.visitElement(importClause, other as JS.ImportClause);
}
/**
* Overrides the visitNamedImports method to compare named imports.
*
* @param namedImports The named imports to visit
* @param other The other named imports to compare with
* @returns The visited named imports, or undefined if the visit was aborted
*/
override async visitNamedImports(namedImports: JS.NamedImports, other: J): Promise {
return this.visitElement(namedImports, other as JS.NamedImports);
}
/**
* Overrides the visitImportSpecifier method to compare import specifiers.
*
* @param importSpecifier The import specifier to visit
* @param other The other import specifier to compare with
* @returns The visited import specifier, or undefined if the visit was aborted
*/
override async visitImportSpecifier(importSpecifier: JS.ImportSpecifier, other: J): Promise {
return this.visitElement(importSpecifier, other as JS.ImportSpecifier);
}
/**
* Overrides the visitImportAttributes method to compare import attributes.
*
* @param importAttributes The import attributes to visit
* @param other The other import attributes to compare with
* @returns The visited import attributes, or undefined if the visit was aborted
*/
override async visitImportAttributes(importAttributes: JS.ImportAttributes, other: J): Promise {
return this.visitElement(importAttributes, other as JS.ImportAttributes);
}
/**
* Overrides the visitImportTypeAttributes method to compare import type attributes.
*
* @param importTypeAttributes The import type attributes to visit
* @param other The other import type attributes to compare with
* @returns The visited import type attributes, or undefined if the visit was aborted
*/
override async visitImportTypeAttributes(importTypeAttributes: JS.ImportTypeAttributes, other: J): Promise {
return this.visitElement(importTypeAttributes, other as JS.ImportTypeAttributes);
}
/**
* Overrides the visitImportAttribute method to compare import attributes.
*
* @param importAttribute The import attribute to visit
* @param other The other import attribute to compare with
* @returns The visited import attribute, or undefined if the visit was aborted
*/
override async visitImportAttribute(importAttribute: JS.ImportAttribute, other: J): Promise {
return this.visitElement(importAttribute, other as JS.ImportAttribute);
}
/**
* Overrides the visitBinaryExtensions method to compare binary expressions.
*
* @param jsBinary The binary expression to visit
* @param other The other binary expression to compare with
* @returns The visited binary expression, or undefined if the visit was aborted
*/
override async visitBinaryExtensions(jsBinary: JS.Binary, other: J): Promise {
return this.visitElement(jsBinary, other as JS.Binary);
}
/**
* Overrides the visitLiteralType method to compare literal types.
*
* @param literalType The literal type to visit
* @param other The other literal type to compare with
* @returns The visited literal type, or undefined if the visit was aborted
*/
override async visitLiteralType(literalType: JS.LiteralType, other: J): Promise {
return this.visitElement(literalType, other as JS.LiteralType);
}
/**
* Overrides the visitMappedType method to compare mapped types.
*
* @param mappedType The mapped type to visit
* @param other The other mapped type to compare with
* @returns The visited mapped type, or undefined if the visit was aborted
*/
override async visitMappedType(mappedType: JS.MappedType, other: J): Promise {
return this.visitElement(mappedType, other as JS.MappedType);
}
/**
* Overrides the visitKeysRemapping method to compare keys remapping.
*
* @param keysRemapping The keys remapping to visit
* @param other The other keys remapping to compare with
* @returns The visited keys remapping, or undefined if the visit was aborted
*/
override async visitMappedTypeKeysRemapping(keysRemapping: JS.MappedType.KeysRemapping, other: J): Promise {
return this.visitElement(keysRemapping, other as JS.MappedType.KeysRemapping);
}
/**
* Overrides the visitMappedTypeParameter method to compare mapped type parameters.
*
* @param mappedTypeParameter The mapped type parameter to visit
* @param other The other mapped type parameter to compare with
* @returns The visited mapped type parameter, or undefined if the visit was aborted
*/
override async visitMappedTypeParameter(mappedTypeParameter: JS.MappedType.Parameter, other: J): Promise {
return this.visitElement(mappedTypeParameter, other as JS.MappedType.Parameter);
}
/**
* Overrides the visitObjectBindingPattern method to compare object binding declarations.
*
* @param objectBindingPattern The object binding declarations to visit
* @param other The other object binding declarations to compare with
* @returns The visited object binding declarations, or undefined if the visit was aborted
*/
override async visitObjectBindingPattern(objectBindingPattern: JS.ObjectBindingPattern, other: J): Promise {
return this.visitElement(objectBindingPattern, other as JS.ObjectBindingPattern);
}
/**
* Overrides the visitPropertyAssignment method to compare property assignments.
*
* @param propertyAssignment The property assignment to visit
* @param other The other property assignment to compare with
* @returns The visited property assignment, or undefined if the visit was aborted
*/
override async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, other: J): Promise {
return this.visitElement(propertyAssignment, other as JS.PropertyAssignment);
}
/**
* Overrides the visitSatisfiesExpression method to compare satisfies expressions.
*
* @param satisfiesExpression The satisfies expression to visit
* @param other The other satisfies expression to compare with
* @returns The visited satisfies expression, or undefined if the visit was aborted
*/
override async visitSatisfiesExpression(satisfiesExpression: JS.SatisfiesExpression, other: J): Promise {
return this.visitElement(satisfiesExpression, other as JS.SatisfiesExpression);
}
/**
* Overrides the visitScopedVariableDeclarations method to compare scoped variable declarations.
*
* @param scopedVariableDeclarations The scoped variable declarations to visit
* @param other The other scoped variable declarations to compare with
* @returns The visited scoped variable declarations, or undefined if the visit was aborted
*/
override async visitScopedVariableDeclarations(scopedVariableDeclarations: JS.ScopedVariableDeclarations, other: J): Promise {
return this.visitElement(scopedVariableDeclarations, other as JS.ScopedVariableDeclarations);
}
/**
* Overrides the visitShebang method to compare shebangs.
*
* @param shebang The shebang to visit
* @param other The other shebang to compare with
* @returns The visited shebang, or undefined if the visit was aborted
*/
override async visitShebang(shebang: JS.Shebang, other: J): Promise {
return this.visitElement(shebang, other as JS.Shebang);
}
/**
* Overrides the visitSpread method to compare spread expressions.
*
* @param spread The spread expression to visit
* @param other The other spread expression to compare with
* @returns The visited spread expression, or undefined if the visit was aborted
*/
override async visitSpread(spread: JS.Spread, other: J): Promise {
return this.visitElement(spread, other as JS.Spread);
}
/**
* Overrides the visitStatementExpression method to compare statement expressions.
*
* @param statementExpression The statement expression to visit
* @param other The other statement expression to compare with
* @returns The visited statement expression, or undefined if the visit was aborted
*/
override async visitStatementExpression(statementExpression: JS.StatementExpression, other: J): Promise {
return this.visitElement(statementExpression, other as JS.StatementExpression);
}
/**
* Overrides the visitTaggedTemplateExpression method to compare tagged template expressions.
*
* @param taggedTemplateExpression The tagged template expression to visit
* @param other The other tagged template expression to compare with
* @returns The visited tagged template expression, or undefined if the visit was aborted
*/
override async visitTaggedTemplateExpression(taggedTemplateExpression: JS.TaggedTemplateExpression, other: J): Promise {
return this.visitElement(taggedTemplateExpression, other as JS.TaggedTemplateExpression);
}
/**
* Overrides the visitTemplateExpression method to compare template expressions.
*
* @param templateExpression The template expression to visit
* @param other The other template expression to compare with
* @returns The visited template expression, or undefined if the visit was aborted
*/
override async visitTemplateExpression(templateExpression: JS.TemplateExpression, other: J): Promise {
return this.visitElement(templateExpression, other as JS.TemplateExpression);
}
/**
* Overrides the visitTemplateExpressionSpan method to compare template expression spans.
*
* @param span The template expression span to visit
* @param other The other template expression span to compare with
* @returns The visited template expression span, or undefined if the visit was aborted
*/
override async visitTemplateExpressionSpan(span: JS.TemplateExpression.Span, other: J): Promise {
return this.visitElement(span, other as JS.TemplateExpression.Span);
}
/**
* Overrides the visitTuple method to compare tuples.
*
* @param tuple The tuple to visit
* @param other The other tuple to compare with
* @returns The visited tuple, or undefined if the visit was aborted
*/
override async visitTuple(tuple: JS.Tuple, other: J): Promise {
return this.visitElement(tuple, other as JS.Tuple);
}
/**
* Overrides the visitTypeDeclaration method to compare type declarations.
*
* @param typeDeclaration The type declaration to visit
* @param other The other type declaration to compare with
* @returns The visited type declaration, or undefined if the visit was aborted
*/
override async visitTypeDeclaration(typeDeclaration: JS.TypeDeclaration, other: J): Promise {
return this.visitElement(typeDeclaration, other as JS.TypeDeclaration);
}
/**
* Overrides the visitTypeOf method to compare typeof expressions.
*
* @param typeOf The typeof expression to visit
* @param other The other typeof expression to compare with
* @returns The visited typeof expression, or undefined if the visit was aborted
*/
override async visitTypeOf(typeOf: JS.TypeOf, other: J): Promise {
return this.visitElement(typeOf, other as JS.TypeOf);
}
/**
* Overrides the visitTypeTreeExpression method to compare type tree expressions.
*
* @param typeTreeExpression The type tree expression to visit
* @param other The other type tree expression to compare with
* @returns The visited type tree expression, or undefined if the visit was aborted
*/
override async visitTypeTreeExpression(typeTreeExpression: JS.TypeTreeExpression, other: J): Promise {
return this.visitElement(typeTreeExpression, other as JS.TypeTreeExpression);
}
/**
* Overrides the visitAs method to compare as expressions.
*
* @param as_ The as expression to visit
* @param other The other as expression to compare with
* @returns The visited as expression, or undefined if the visit was aborted
*/
override async visitAs(as_: JS.As, other: J): Promise {
return this.visitElement(as_, other as JS.As);
}
/**
* Overrides the visitAssignmentOperationExtensions method to compare assignment operations.
*
* @param assignmentOperation The assignment operation to visit
* @param other The other assignment operation to compare with
* @returns The visited assignment operation, or undefined if the visit was aborted
*/
override async visitAssignmentOperationExtensions(assignmentOperation: JS.AssignmentOperation, other: J): Promise {
return this.visitElement(assignmentOperation, other as JS.AssignmentOperation);
}
/**
* Overrides the visitIndexedAccessType method to compare indexed access types.
*
* @param indexedAccessType The indexed access type to visit
* @param other The other indexed access type to compare with
* @returns The visited indexed access type, or undefined if the visit was aborted
*/
override async visitIndexedAccessType(indexedAccessType: JS.IndexedAccessType, other: J): Promise {
return this.visitElement(indexedAccessType, other as JS.IndexedAccessType);
}
/**
* Overrides the visitIndexType method to compare index types.
*
* @param indexType The index type to visit
* @param other The other index type to compare with
* @returns The visited index type, or undefined if the visit was aborted
*/
override async visitIndexedAccessTypeIndexType(indexType: JS.IndexedAccessType.IndexType, other: J): Promise {
return this.visitElement(indexType, other as JS.IndexedAccessType.IndexType);
}
/**
* Overrides the visitTypeQuery method to compare type queries.
*
* @param typeQuery The type query to visit
* @param other The other type query to compare with
* @returns The visited type query, or undefined if the visit was aborted
*/
override async visitTypeQuery(typeQuery: JS.TypeQuery, other: J): Promise {
return this.visitElement(typeQuery, other as JS.TypeQuery);
}
/**
* Overrides the visitTypeInfo method to compare type info.
*
* @param typeInfo The type info to visit
* @param other The other type info to compare with
* @returns The visited type info, or undefined if the visit was aborted
*/
override async visitTypeInfo(typeInfo: JS.TypeInfo, other: J): Promise {
return this.visitElement(typeInfo, other as JS.TypeInfo);
}
/**
* Overrides the visitComputedPropertyName method to compare computed property names.
*
* @param computedPropertyName The computed property name to visit
* @param other The other computed property name to compare with
* @returns The visited computed property name, or undefined if the visit was aborted
*/
override async visitComputedPropertyName(computedPropertyName: JS.ComputedPropertyName, other: J): Promise {
return this.visitElement(computedPropertyName, other as JS.ComputedPropertyName);
}
/**
* Overrides the visitTypeOperator method to compare type operators.
*
* @param typeOperator The type operator to visit
* @param other The other type operator to compare with
* @returns The visited type operator, or undefined if the visit was aborted
*/
override async visitTypeOperator(typeOperator: JS.TypeOperator, other: J): Promise {
return this.visitElement(typeOperator, other as JS.TypeOperator);
}
/**
* Overrides the visitTypePredicate method to compare type predicates.
*
* @param typePredicate The type predicate to visit
* @param other The other type predicate to compare with
* @returns The visited type predicate, or undefined if the visit was aborted
*/
override async visitTypePredicate(typePredicate: JS.TypePredicate, other: J): Promise {
return this.visitElement(typePredicate, other as JS.TypePredicate);
}
/**
* Overrides the visitUnion method to compare unions.
*
* @param union The union to visit
* @param other The other union to compare with
* @returns The visited union, or undefined if the visit was aborted
*/
override async visitUnion(union: JS.Union, other: J): Promise {
return this.visitElement(union, other as JS.Union);
}
/**
* Overrides the visitIntersection method to compare intersections.
*
* @param intersection The intersection to visit
* @param other The other intersection to compare with
* @returns The visited intersection, or undefined if the visit was aborted
*/
override async visitIntersection(intersection: JS.Intersection, other: J): Promise {
return this.visitElement(intersection, other as JS.Intersection);
}
/**
* Overrides the visitAnnotatedType method to compare annotated types.
*
* @param annotatedType The annotated type to visit
* @param other The other annotated type to compare with
* @returns The visited annotated type, or undefined if the visit was aborted
*/
override async visitAnnotatedType(annotatedType: J.AnnotatedType, other: J): Promise {
return this.visitElement(annotatedType, other as J.AnnotatedType);
}
/**
* Overrides the visitAnnotation method to compare annotations.
*
* @param annotation The annotation to visit
* @param other The other annotation to compare with
* @returns The visited annotation, or undefined if the visit was aborted
*/
override async visitAnnotation(annotation: J.Annotation, other: J): Promise {
return this.visitElement(annotation, other as J.Annotation);
}
/**
* Overrides the visitArrayAccess method to compare array access expressions.
*
* @param arrayAccess The array access expression to visit
* @param other The other array access expression to compare with
* @returns The visited array access expression, or undefined if the visit was aborted
*/
override async visitArrayAccess(arrayAccess: J.ArrayAccess, other: J): Promise {
return this.visitElement(arrayAccess, other as J.ArrayAccess);
}
/**
* Overrides the visitArrayDimension method to compare array dimensions.
*
* @param arrayDimension The array dimension to visit
* @param other The other array dimension to compare with
* @returns The visited array dimension, or undefined if the visit was aborted
*/
override async visitArrayDimension(arrayDimension: J.ArrayDimension, other: J): Promise {
return this.visitElement(arrayDimension, other as J.ArrayDimension);
}
/**
* Overrides the visitArrayType method to compare array types.
*
* @param arrayType The array type to visit
* @param other The other array type to compare with
* @returns The visited array type, or undefined if the visit was aborted
*/
override async visitArrayType(arrayType: J.ArrayType, other: J): Promise {
return this.visitElement(arrayType, other as J.ArrayType);
}
/**
* Overrides the visitAssert method to compare assert statements.
*
* @param anAssert The assert statement to visit
* @param other The other assert statement to compare with
* @returns The visited assert statement, or undefined if the visit was aborted
*/
override async visitAssert(anAssert: J.Assert, other: J): Promise {
return this.visitElement(anAssert, other as J.Assert);
}
/**
* Overrides the visitAssignment method to compare assignment expressions.
*
* @param assignment The assignment expression to visit
* @param other The other assignment expression to compare with
* @returns The visited assignment expression, or undefined if the visit was aborted
*/
override async visitAssignment(assignment: J.Assignment, other: J): Promise {
return this.visitElement(assignment, other as J.Assignment);
}
/**
* Overrides the visitAssignmentOperation method to compare assignment operation expressions.
*
* @param assignOp The assignment operation expression to visit
* @param other The other assignment operation expression to compare with
* @returns The visited assignment operation expression, or undefined if the visit was aborted
*/
override async visitAssignmentOperation(assignOp: J.AssignmentOperation, other: J): Promise {
return this.visitElement(assignOp, other as J.AssignmentOperation);
}
/**
* Overrides the visitBreak method to compare break statements.
*
* @param breakStatement The break statement to visit
* @param other The other break statement to compare with
* @returns The visited break statement, or undefined if the visit was aborted
*/
override async visitBreak(breakStatement: J.Break, other: J): Promise {
return this.visitElement(breakStatement, other as J.Break);
}
/**
* Overrides the visitCase method to compare case statements.
*
* @param aCase The case statement to visit
* @param other The other case statement to compare with
* @returns The visited case statement, or undefined if the visit was aborted
*/
override async visitCase(aCase: J.Case, other: J): Promise {
return this.visitElement(aCase, other as J.Case);
}
/**
* Overrides the visitClassDeclaration method to compare class declarations.
*
* @param classDecl The class declaration to visit
* @param other The other class declaration to compare with
* @returns The visited class declaration, or undefined if the visit was aborted
*/
override async visitClassDeclaration(classDecl: J.ClassDeclaration, other: J): Promise {
return this.visitElement(classDecl, other as J.ClassDeclaration);
}
/**
* Overrides the visitClassDeclarationKind method to compare class declaration kinds.
*
* @param kind The class declaration kind to visit
* @param other The other class declaration kind to compare with
* @returns The visited class declaration kind, or undefined if the visit was aborted
*/
override async visitClassDeclarationKind(kind: J.ClassDeclaration.Kind, other: J): Promise {
return this.visitElement(kind, other as J.ClassDeclaration.Kind);
}
/**
* Overrides the visitCompilationUnit method to compare compilation units.
*
* @param compilationUnit The compilation unit to visit
* @param other The other compilation unit to compare with
* @returns The visited compilation unit, or undefined if the visit was aborted
*/
override async visitCompilationUnit(compilationUnit: J.CompilationUnit, other: J): Promise {
return this.visitElement(compilationUnit, other as J.CompilationUnit);
}
/**
* Overrides the visitContinue method to compare continue statements.
*
* @param continueStatement The continue statement to visit
* @param other The other continue statement to compare with
* @returns The visited continue statement, or undefined if the visit was aborted
*/
override async visitContinue(continueStatement: J.Continue, other: J): Promise {
return this.visitElement(continueStatement, other as J.Continue);
}
/**
* Overrides the visitControlParentheses method to compare control parentheses.
*
* @param controlParens The control parentheses to visit
* @param other The other control parentheses to compare with
* @returns The visited control parentheses, or undefined if the visit was aborted
*/
override async visitControlParentheses(controlParens: J.ControlParentheses, other: J): Promise {
return this.visitElement(controlParens, other as J.ControlParentheses);
}
/**
* Overrides the visitDeconstructionPattern method to compare deconstruction patterns.
*
* @param pattern The deconstruction pattern to visit
* @param other The other deconstruction pattern to compare with
* @returns The visited deconstruction pattern, or undefined if the visit was aborted
*/
override async visitDeconstructionPattern(pattern: J.DeconstructionPattern, other: J): Promise {
return this.visitElement(pattern, other as J.DeconstructionPattern);
}
/**
* Overrides the visitDoWhileLoop method to compare do-while loops.
*
* @param doWhileLoop The do-while loop to visit
* @param other The other do-while loop to compare with
* @returns The visited do-while loop, or undefined if the visit was aborted
*/
override async visitDoWhileLoop(doWhileLoop: J.DoWhileLoop, other: J): Promise {
return this.visitElement(doWhileLoop, other as J.DoWhileLoop);
}
/**
* Overrides the visitEmpty method to compare empty statements.
*
* @param empty The empty statement to visit
* @param other The other empty statement to compare with
* @returns The visited empty statement, or undefined if the visit was aborted
*/
override async visitEmpty(empty: J.Empty, other: J): Promise {
return this.visitElement(empty, other as J.Empty);
}
/**
* Overrides the visitEnumValue method to compare enum values.
*
* @param enumValue The enum value to visit
* @param other The other enum value to compare with
* @returns The visited enum value, or undefined if the visit was aborted
*/
override async visitEnumValue(enumValue: J.EnumValue, other: J): Promise {
return this.visitElement(enumValue, other as J.EnumValue);
}
/**
* Overrides the visitEnumValueSet method to compare enum value sets.
*
* @param enumValueSet The enum value set to visit
* @param other The other enum value set to compare with
* @returns The visited enum value set, or undefined if the visit was aborted
*/
override async visitEnumValueSet(enumValueSet: J.EnumValueSet, other: J): Promise {
return this.visitElement(enumValueSet, other as J.EnumValueSet);
}
/**
* Overrides the visitErroneous method to compare erroneous nodes.
*
* @param erroneous The erroneous node to visit
* @param other The other erroneous node to compare with
* @returns The visited erroneous node, or undefined if the visit was aborted
*/
override async visitErroneous(erroneous: J.Erroneous, other: J): Promise {
return this.visitElement(erroneous, other as J.Erroneous);
}
/**
* Overrides the visitFieldAccess method to compare field access expressions.
*
* @param fieldAccess The field access expression to visit
* @param other The other field access expression to compare with
* @returns The visited field access expression, or undefined if the visit was aborted
*/
override async visitFieldAccess(fieldAccess: J.FieldAccess, other: J): Promise {
return this.visitElement(fieldAccess, other as J.FieldAccess);
}
/**
* Overrides the visitForEachLoop method to compare for-each loops.
*
* @param forEachLoop The for-each loop to visit
* @param other The other for-each loop to compare with
* @returns The visited for-each loop, or undefined if the visit was aborted
*/
override async visitForEachLoop(forEachLoop: J.ForEachLoop, other: J): Promise {
return this.visitElement(forEachLoop, other as J.ForEachLoop);
}
/**
* Overrides the visitForEachLoopControl method to compare for-each loop controls.
*
* @param control The for-each loop control to visit
* @param other The other for-each loop control to compare with
* @returns The visited for-each loop control, or undefined if the visit was aborted
*/
override async visitForEachLoopControl(control: J.ForEachLoop.Control, other: J): Promise {
return this.visitElement(control, other as J.ForEachLoop.Control);
}
/**
* Overrides the visitForLoop method to compare for loops.
*
* @param forLoop The for loop to visit
* @param other The other for loop to compare with
* @returns The visited for loop, or undefined if the visit was aborted
*/
override async visitForLoop(forLoop: J.ForLoop, other: J): Promise {
return this.visitElement(forLoop, other as J.ForLoop);
}
/**
* Overrides the visitForLoopControl method to compare for loop controls.
*
* @param control The for loop control to visit
* @param other The other for loop control to compare with
* @returns The visited for loop control, or undefined if the visit was aborted
*/
override async visitForLoopControl(control: J.ForLoop.Control, other: J): Promise {
return this.visitElement(control, other as J.ForLoop.Control);
}
/**
* Overrides the visitIf method to compare if statements.
*
* @param ifStatement The if statement to visit
* @param other The other if statement to compare with
* @returns The visited if statement, or undefined if the visit was aborted
*/
override async visitIf(ifStatement: J.If, other: J): Promise {
return this.visitElement(ifStatement, other as J.If);
}
/**
* Overrides the visitElse method to compare else statements.
*
* @param elseStatement The else statement to visit
* @param other The other else statement to compare with
* @returns The visited else statement, or undefined if the visit was aborted
*/
override async visitElse(elseStatement: J.If.Else, other: J): Promise {
return this.visitElement(elseStatement, other as J.If.Else);
}
/**
* Overrides the visitImport method to compare import statements.
*
* @param importStatement The import statement to visit
* @param other The other import statement to compare with
* @returns The visited import statement, or undefined if the visit was aborted
*/
override async visitImport(importStatement: J.Import, other: J): Promise {
return this.visitElement(importStatement, other as J.Import);
}
/**
* Overrides the visitInstanceOf method to compare instanceof expressions.
*
* @param instanceOf The instanceof expression to visit
* @param other The other instanceof expression to compare with
* @returns The visited instanceof expression, or undefined if the visit was aborted
*/
override async visitInstanceOf(instanceOf: J.InstanceOf, other: J): Promise {
return this.visitElement(instanceOf, other as J.InstanceOf);
}
/**
* Overrides the visitIntersectionType method to compare intersection types.
*
* @param intersectionType The intersection type to visit
* @param other The other intersection type to compare with
* @returns The visited intersection type, or undefined if the visit was aborted
*/
override async visitIntersectionType(intersectionType: J.IntersectionType, other: J): Promise {
return this.visitElement(intersectionType, other as J.IntersectionType);
}
/**
* Overrides the visitLabel method to compare label statements.
*
* @param label The label statement to visit
* @param other The other label statement to compare with
* @returns The visited label statement, or undefined if the visit was aborted
*/
override async visitLabel(label: J.Label, other: J): Promise {
return this.visitElement(label, other as J.Label);
}
/**
* Overrides the visitLambda method to compare lambda expressions.
*
* @param lambda The lambda expression to visit
* @param other The other lambda expression to compare with
* @returns The visited lambda expression, or undefined if the visit was aborted
*/
override async visitLambda(lambda: J.Lambda, other: J): Promise {
return this.visitElement(lambda, other as J.Lambda);
}
/**
* Overrides the visitLambdaParameters method to compare lambda parameters.
*
* @param parameters The lambda parameters to visit
* @param other The other lambda parameters to compare with
* @returns The visited lambda parameters, or undefined if the visit was aborted
*/
override async visitLambdaParameters(parameters: J.Lambda.Parameters, other: J): Promise {
return this.visitElement(parameters, other as J.Lambda.Parameters);
}
/**
* Overrides the visitMemberReference method to compare member references.
*
* @param memberReference The member reference to visit
* @param other The other member reference to compare with
* @returns The visited member reference, or undefined if the visit was aborted
*/
override async visitMemberReference(memberReference: J.MemberReference, other: J): Promise {
return this.visitElement(memberReference, other as J.MemberReference);
}
/**
* Overrides the visitMethodDeclaration method to compare method declarations.
*
* @param methodDeclaration The method declaration to visit
* @param other The other method declaration to compare with
* @returns The visited method declaration, or undefined if the visit was aborted
*/
override async visitMethodDeclaration(methodDeclaration: J.MethodDeclaration, other: J): Promise {
return this.visitElement(methodDeclaration, other as J.MethodDeclaration);
}
/**
* Overrides the visitMethodInvocation method to compare method invocations.
*
* @param methodInvocation The method invocation to visit
* @param other The other method invocation to compare with
* @returns The visited method invocation, or undefined if the visit was aborted
*/
override async visitMethodInvocation(methodInvocation: J.MethodInvocation, other: J): Promise {
return this.visitElement(methodInvocation, other as J.MethodInvocation);
}
/**
* Overrides the visitModifier method to compare modifiers.
*
* @param modifier The modifier to visit
* @param other The other modifier to compare with
* @returns The visited modifier, or undefined if the visit was aborted
*/
override async visitModifier(modifier: J.Modifier, other: J): Promise {
return this.visitElement(modifier, other as J.Modifier);
}
/**
* Overrides the visitMultiCatch method to compare multi-catch expressions.
*
* @param multiCatch The multi-catch expression to visit
* @param other The other multi-catch expression to compare with
* @returns The visited multi-catch expression, or undefined if the visit was aborted
*/
override async visitMultiCatch(multiCatch: J.MultiCatch, other: J): Promise {
return this.visitElement(multiCatch, other as J.MultiCatch);
}
/**
* Overrides the visitNewArray method to compare new array expressions.
*
* @param newArray The new array expression to visit
* @param other The other new array expression to compare with
* @returns The visited new array expression, or undefined if the visit was aborted
*/
override async visitNewArray(newArray: J.NewArray, other: J): Promise {
return this.visitElement(newArray, other as J.NewArray);
}
/**
* Overrides the visitNewClass method to compare new class expressions.
*
* @param newClass The new class expression to visit
* @param other The other new class expression to compare with
* @returns The visited new class expression, or undefined if the visit was aborted
*/
override async visitNewClass(newClass: J.NewClass, other: J): Promise {
return this.visitElement(newClass, other as J.NewClass);
}
/**
* Overrides the visitNullableType method to compare nullable types.
*
* @param nullableType The nullable type to visit
* @param other The other nullable type to compare with
* @returns The visited nullable type, or undefined if the visit was aborted
*/
override async visitNullableType(nullableType: J.NullableType, other: J): Promise {
return this.visitElement(nullableType, other as J.NullableType);
}
/**
* Overrides the visitPackage method to compare package declarations.
*
* @param packageDeclaration The package declaration to visit
* @param other The other package declaration to compare with
* @returns The visited package declaration, or undefined if the visit was aborted
*/
override async visitPackage(packageDeclaration: J.Package, other: J): Promise {
return this.visitElement(packageDeclaration, other as J.Package);
}
/**
* Overrides the visitParameterizedType method to compare parameterized types.
*
* @param parameterizedType The parameterized type to visit
* @param other The other parameterized type to compare with
* @returns The visited parameterized type, or undefined if the visit was aborted
*/
override async visitParameterizedType(parameterizedType: J.ParameterizedType, other: J): Promise {
return this.visitElement(parameterizedType, other as J.ParameterizedType);
}
/**
* Overrides the visitParentheses method to compare parentheses expressions.
*
* @param parentheses The parentheses expression to visit
* @param other The other parentheses expression to compare with
* @returns The visited parentheses expression, or undefined if the visit was aborted
*/
override async visitParentheses(parentheses: J.Parentheses, other: J): Promise {
return this.visitElement(parentheses, other as J.Parentheses);
}
/**
* Overrides the visitParenthesizedTypeTree method to compare parenthesized type trees.
*
* @param parenthesizedTypeTree The parenthesized type tree to visit
* @param other The other parenthesized type tree to compare with
* @returns The visited parenthesized type tree, or undefined if the visit was aborted
*/
override async visitParenthesizedTypeTree(parenthesizedTypeTree: J.ParenthesizedTypeTree, other: J): Promise {
return this.visitElement(parenthesizedTypeTree, other as J.ParenthesizedTypeTree);
}
/**
* Overrides the visitPrimitive method to compare primitive types.
*
* @param primitive The primitive type to visit
* @param other The other primitive type to compare with
* @returns The visited primitive type, or undefined if the visit was aborted
*/
override async visitPrimitive(primitive: J.Primitive, other: J): Promise {
return this.visitElement(primitive, other as J.Primitive);
}
/**
* Overrides the visitReturn method to compare return statements.
*
* @param returnStatement The return statement to visit
* @param other The other return statement to compare with
* @returns The visited return statement, or undefined if the visit was aborted
*/
override async visitReturn(returnStatement: J.Return, other: J): Promise {
return this.visitElement(returnStatement, other as J.Return);
}
/**
* Overrides the visitSwitch method to compare switch statements.
*
* @param switchStatement The switch statement to visit
* @param other The other switch statement to compare with
* @returns The visited switch statement, or undefined if the visit was aborted
*/
override async visitSwitch(switchStatement: J.Switch, other: J): Promise {
return this.visitElement(switchStatement, other as J.Switch);
}
/**
* Overrides the visitSwitchExpression method to compare switch expressions.
*
* @param switchExpression The switch expression to visit
* @param other The other switch expression to compare with
* @returns The visited switch expression, or undefined if the visit was aborted
*/
override async visitSwitchExpression(switchExpression: J.SwitchExpression, other: J): Promise {
return this.visitElement(switchExpression, other as J.SwitchExpression);
}
/**
* Overrides the visitSynchronized method to compare synchronized statements.
*
* @param synchronizedStatement The synchronized statement to visit
* @param other The other synchronized statement to compare with
* @returns The visited synchronized statement, or undefined if the visit was aborted
*/
override async visitSynchronized(synchronizedStatement: J.Synchronized, other: J): Promise {
return this.visitElement(synchronizedStatement, other as J.Synchronized);
}
/**
* Overrides the visitTernary method to compare ternary expressions.
*
* @param ternary The ternary expression to visit
* @param other The other ternary expression to compare with
* @returns The visited ternary expression, or undefined if the visit was aborted
*/
override async visitTernary(ternary: J.Ternary, other: J): Promise {
return this.visitElement(ternary, other as J.Ternary);
}
/**
* Overrides the visitThrow method to compare throw statements.
*
* @param throwStatement The throw statement to visit
* @param other The other throw statement to compare with
* @returns The visited throw statement, or undefined if the visit was aborted
*/
override async visitThrow(throwStatement: J.Throw, other: J): Promise {
return this.visitElement(throwStatement, other as J.Throw);
}
/**
* Overrides the visitTry method to compare try statements.
*
* @param tryStatement The try statement to visit
* @param other The other try statement to compare with
* @returns The visited try statement, or undefined if the visit was aborted
*/
override async visitTry(tryStatement: J.Try, other: J): Promise {
return this.visitElement(tryStatement, other as J.Try);
}
/**
* Overrides the visitTryResource method to compare try resources.
*
* @param resource The try resource to visit
* @param other The other try resource to compare with
* @returns The visited try resource, or undefined if the visit was aborted
*/
override async visitTryResource(resource: J.Try.Resource, other: J): Promise {
return this.visitElement(resource, other as J.Try.Resource);
}
/**
* Overrides the visitTryCatch method to compare try catch blocks.
*
* @param tryCatch The try catch block to visit
* @param other The other try catch block to compare with
* @returns The visited try catch block, or undefined if the visit was aborted
*/
override async visitTryCatch(tryCatch: J.Try.Catch, other: J): Promise {
return this.visitElement(tryCatch, other as J.Try.Catch);
}
/**
* Overrides the visitTypeCast method to compare type cast expressions.
*
* @param typeCast The type cast expression to visit
* @param other The other type cast expression to compare with
* @returns The visited type cast expression, or undefined if the visit was aborted
*/
override async visitTypeCast(typeCast: J.TypeCast, other: J): Promise {
return this.visitElement(typeCast, other as J.TypeCast);
}
/**
* Overrides the visitTypeParameter method to compare type parameters.
*
* @param typeParameter The type parameter to visit
* @param other The other type parameter to compare with
* @returns The visited type parameter, or undefined if the visit was aborted
*/
override async visitTypeParameter(typeParameter: J.TypeParameter, other: J): Promise {
return this.visitElement(typeParameter, other as J.TypeParameter);
}
/**
* Overrides the visitTypeParameters method to compare type parameters.
*
* @param typeParameters The type parameters to visit
* @param other The other type parameters to compare with
* @returns The visited type parameters, or undefined if the visit was aborted
*/
override async visitTypeParameters(typeParameters: J.TypeParameters, other: J): Promise {
return this.visitElement(typeParameters, other as J.TypeParameters);
}
/**
* Overrides the visitUnary method to compare unary expressions.
*
* @param unary The unary expression to visit
* @param other The other unary expression to compare with
* @returns The visited unary expression, or undefined if the visit was aborted
*/
override async visitUnary(unary: J.Unary, other: J): Promise {
return this.visitElement(unary, other as J.Unary);
}
/**
* Overrides the visitUnknown method to compare unknown nodes.
*
* @param unknown The unknown node to visit
* @param other The other unknown node to compare with
* @returns The visited unknown node, or undefined if the visit was aborted
*/
override async visitUnknown(unknown: J.Unknown, other: J): Promise {
return this.visitElement(unknown, other as J.Unknown);
}
/**
* Overrides the visitUnknownSource method to compare unknown sources.
*
* @param unknownSource The unknown source to visit
* @param other The other unknown source to compare with
* @returns The visited unknown source, or undefined if the visit was aborted
*/
override async visitUnknownSource(unknownSource: J.UnknownSource, other: J): Promise {
return this.visitElement(unknownSource, other as J.UnknownSource);
}
/**
* Overrides the visitVariableDeclarations method to compare variable declarations.
*
* @param variableDeclarations The variable declarations to visit
* @param other The other variable declarations to compare with
* @returns The visited variable declarations, or undefined if the visit was aborted
*/
override async visitVariableDeclarations(variableDeclarations: J.VariableDeclarations, other: J): Promise {
return this.visitElement(variableDeclarations, other as J.VariableDeclarations);
}
/**
* Overrides the visitVariable method to compare variable declarations.
*
* @param variable The variable declaration to visit
* @param other The other variable declaration to compare with
* @returns The visited variable declaration, or undefined if the visit was aborted
*/
override async visitVariable(variable: J.VariableDeclarations.NamedVariable, other: J): Promise {
return this.visitElement(variable, other as J.VariableDeclarations.NamedVariable);
}
/**
* Overrides the visitWhileLoop method to compare while loops.
*
* @param whileLoop The while loop to visit
* @param other The other while loop to compare with
* @returns The visited while loop, or undefined if the visit was aborted
*/
override async visitWhileLoop(whileLoop: J.WhileLoop, other: J): Promise {
return this.visitElement(whileLoop, other as J.WhileLoop);
}
/**
* Overrides the visitWildcard method to compare wildcards.
*
* @param wildcard The wildcard to visit
* @param other The other wildcard to compare with
* @returns The visited wildcard, or undefined if the visit was aborted
*/
override async visitWildcard(wildcard: J.Wildcard, other: J): Promise {
return this.visitElement(wildcard, other as J.Wildcard);
}
/**
* Overrides the visitYield method to compare yield statements.
*
* @param yieldStatement The yield statement to visit
* @param other The other yield statement to compare with
* @returns The visited yield statement, or undefined if the visit was aborted
*/
override async visitYield(yieldStatement: J.Yield, other: J): Promise {
return this.visitElement(yieldStatement, other as J.Yield);
}
/**
* Overrides the visitVoid method to compare void expressions.
*
* @param void_ The void expression to visit
* @param other The other void expression to compare with
* @returns The visited void expression, or undefined if the visit was aborted
*/
override async visitVoid(void_: JS.Void, other: J): Promise {
return this.visitElement(void_, other as JS.Void);
}
/**
* Overrides the visitWithStatement method to compare with statements.
*
* @param withStatement The with statement to visit
* @param other The other with statement to compare with
* @returns The visited with statement, or undefined if the visit was aborted
*/
override async visitWithStatement(withStatement: JS.WithStatement, other: J): Promise {
return this.visitElement(withStatement, other as JS.WithStatement);
}
/**
* Overrides the visitIndexSignatureDeclaration method to compare index signature declarations.
*
* @param indexSignatureDeclaration The index signature declaration to visit
* @param other The other index signature declaration to compare with
* @returns The visited index signature declaration, or undefined if the visit was aborted
*/
override async visitIndexSignatureDeclaration(indexSignatureDeclaration: JS.IndexSignatureDeclaration, other: J): Promise {
return this.visitElement(indexSignatureDeclaration, other as JS.IndexSignatureDeclaration);
}
/**
* Overrides the visitForOfLoop method to compare for-of loops.
*
* @param forOfLoop The for-of loop to visit
* @param other The other for-of loop to compare with
* @returns The visited for-of loop, or undefined if the visit was aborted
*/
override async visitForOfLoop(forOfLoop: JS.ForOfLoop, other: J): Promise {
return this.visitElement(forOfLoop, other as JS.ForOfLoop);
}
/**
* Overrides the visitForInLoop method to compare for-in loops.
*
* @param forInLoop The for-in loop to visit
* @param other The other for-in loop to compare with
* @returns The visited for-in loop, or undefined if the visit was aborted
*/
override async visitForInLoop(forInLoop: JS.ForInLoop, other: J): Promise {
return this.visitElement(forInLoop, other as JS.ForInLoop);
}
/**
* Overrides the visitNamespaceDeclaration method to compare namespace declarations.
*
* @param namespaceDeclaration The namespace declaration to visit
* @param other The other namespace declaration to compare with
* @returns The visited namespace declaration, or undefined if the visit was aborted
*/
override async visitNamespaceDeclaration(namespaceDeclaration: JS.NamespaceDeclaration, other: J): Promise {
return this.visitElement(namespaceDeclaration, other as JS.NamespaceDeclaration);
}
/**
* Overrides the visitTypeLiteral method to compare type literals.
*
* @param typeLiteral The type literal to visit
* @param other The other type literal to compare with
* @returns The visited type literal, or undefined if the visit was aborted
*/
override async visitTypeLiteral(typeLiteral: JS.TypeLiteral, other: J): Promise {
return this.visitElement(typeLiteral, other as JS.TypeLiteral);
}
/**
* Overrides the visitBindingElement method to compare binding elements.
*
* @param bindingElement The binding element to visit
* @param other The other binding element to compare with
* @returns The visited binding element, or undefined if the visit was aborted
*/
override async visitBindingElement(bindingElement: JS.BindingElement, other: J): Promise {
return this.visitElement(bindingElement, other as JS.BindingElement);
}
/**
* Overrides the visitArrayBindingPattern method to compare array binding patterns.
*
* @param arrayBindingPattern The array binding pattern to visit
* @param other The other array binding pattern to compare with
* @returns The visited array binding pattern, or undefined if the visit was aborted
*/
override async visitArrayBindingPattern(arrayBindingPattern: JS.ArrayBindingPattern, other: J): Promise {
return this.visitElement(arrayBindingPattern, other as JS.ArrayBindingPattern);
}
/**
* Overrides the visitExportDeclaration method to compare export declarations.
*
* @param exportDeclaration The export declaration to visit
* @param other The other export declaration to compare with
* @returns The visited export declaration, or undefined if the visit was aborted
*/
override async visitExportDeclaration(exportDeclaration: JS.ExportDeclaration, other: J): Promise {
return this.visitElement(exportDeclaration, other as JS.ExportDeclaration);
}
/**
* Overrides the visitExportAssignment method to compare export assignments.
*
* @param exportAssignment The export assignment to visit
* @param other The other export assignment to compare with
* @returns The visited export assignment, or undefined if the visit was aborted
*/
override async visitExportAssignment(exportAssignment: JS.ExportAssignment, other: J): Promise {
return this.visitElement(exportAssignment, other as JS.ExportAssignment);
}
/**
* Overrides the visitNamedExports method to compare named exports.
*
* @param namedExports The named exports to visit
* @param other The other named exports to compare with
* @returns The visited named exports, or undefined if the visit was aborted
*/
override async visitNamedExports(namedExports: JS.NamedExports, other: J): Promise {
return this.visitElement(namedExports, other as JS.NamedExports);
}
/**
* Overrides the visitExportSpecifier method to compare export specifiers.
*
* @param exportSpecifier The export specifier to visit
* @param other The other export specifier to compare with
* @returns The visited export specifier, or undefined if the visit was aborted
*/
override async visitExportSpecifier(exportSpecifier: JS.ExportSpecifier, other: J): Promise {
return this.visitElement(exportSpecifier, other as JS.ExportSpecifier);
}
/**
* Overrides the visitComputedPropertyMethodDeclaration method to compare computed property method declarations.
*
* @param computedPropMethod The computed property method declaration to visit
* @param other The other computed property method declaration to compare with
* @returns The visited computed property method declaration, or undefined if the visit was aborted
*/
override async visitComputedPropertyMethodDeclaration(computedPropMethod: JS.ComputedPropertyMethodDeclaration, other: J): Promise {
return this.visitElement(computedPropMethod, other as JS.ComputedPropertyMethodDeclaration);
}
}
/**
* A comparator visitor that checks semantic equality including type attribution.
* This ensures comparisons account for type information, allowing semantically
* equivalent code to match even when structurally different (e.g., `foo()` vs `module.foo()`
* when both refer to the same method).
*/
export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
/**
* When true, allows patterns without type annotations to match code with type annotations.
* This enables lenient matching where undefined types on either side are considered compatible.
*/
protected readonly lenientTypeMatching: boolean;
/**
* Creates a new semantic comparator visitor.
*
* @param lenientTypeMatching If true, allows matching between nodes with and without type annotations
*/
constructor(lenientTypeMatching: boolean = false) {
super();
this.lenientTypeMatching = lenientTypeMatching;
}
/**
* Unwraps parentheses from a tree node recursively.
* This allows comparing expressions with and without redundant parentheses.
*
* @param tree The tree to unwrap
* @returns The unwrapped tree
*/
protected unwrap(tree: Tree | undefined): Tree | undefined {
if (!tree) {
return tree;
}
// Unwrap J.Parentheses nodes recursively
if ((tree as any).kind === J.Kind.Parentheses) {
const parens = tree as J.Parentheses;
return this.unwrap(parens.tree.element as Tree);
}
// Unwrap J.ControlParentheses nodes recursively
if ((tree as any).kind === J.Kind.ControlParentheses) {
const controlParens = tree as J.ControlParentheses;
return this.unwrap(controlParens.tree.element as Tree);
}
return tree;
}
override async visit(j: Tree, p: J, parent?: Cursor): Promise {
// If we've already found a mismatch, abort further processing
if (!this.match) return j as R;
// Unwrap parentheses from both trees before comparing
const unwrappedJ = this.unwrap(j) || j;
const unwrappedP = this.unwrap(p) || p;
// Skip the kind check that the base class does - semantic matching allows different kinds
// (e.g., undefined identifier matching void expression)
// Update targetCursor to track the target node in parallel with the pattern cursor
const savedTargetCursor = this.targetCursor;
this.targetCursor = new Cursor(unwrappedP, this.targetCursor);
try {
// Call the grandparent's visit to do actual visitation without the kind check
return await JavaScriptVisitor.prototype.visit.call(this, unwrappedJ, unwrappedP) as R | undefined;
} finally {
this.targetCursor = savedTargetCursor;
}
}
/**
* Override visitArrowFunction to allow semantic equivalence between expression body
* and block with single return statement forms.
*
* Examples:
* - `x => x + 1` matches `x => { return x + 1; }`
* - `(x, y) => x + y` matches `(x, y) => { return x + y; }`
*/
override async visitArrowFunction(arrowFunction: JS.ArrowFunction, other: J): Promise {
if (!this.match) return arrowFunction;
if (other.kind !== JS.Kind.ArrowFunction) {
return this.kindMismatch();
}
const otherArrow = other as JS.ArrowFunction;
// Compare all properties reflectively except lambda (handled specially below)
for (const key of Object.keys(arrowFunction)) {
if (key.startsWith('_') || key === 'id' || key === 'markers' || key === 'lambda') {
continue;
}
const jValue = (arrowFunction as any)[key];
const otherValue = (otherArrow as any)[key];
// Handle arrays
if (Array.isArray(jValue)) {
if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
return this.arrayLengthMismatch(key);
}
for (let i = 0; i < jValue.length; i++) {
await this.visitProperty(jValue[i], otherValue[i]);
if (!this.match) return arrowFunction;
}
} else {
await this.visitProperty(jValue, otherValue);
if (!this.match) return arrowFunction;
}
}
// Compare lambda parameters
const params1 = arrowFunction.lambda.parameters.parameters;
const params2 = otherArrow.lambda.parameters.parameters;
if (params1.length !== params2.length) {
return this.arrayLengthMismatch('lambda.parameters.parameters');
}
for (let i = 0; i < params1.length; i++) {
await this.visitProperty(params1[i], params2[i]);
if (!this.match) return arrowFunction;
}
// Handle semantic equivalence for lambda bodies
const body1 = arrowFunction.lambda.body;
const body2 = otherArrow.lambda.body;
// Try to extract the expression from each body
const expr1 = this.extractExpression(body1);
const expr2 = this.extractExpression(body2);
if (expr1 && expr2) {
// Both have extractable expressions - compare them
await this.visit(expr1, expr2);
} else {
// At least one is not a simple expression or block-with-return
// Fall back to exact comparison
await this.visit(body1, body2);
}
return arrowFunction;
}
/**
* Override visitLambdaParameters to allow semantic equivalence between
* arrow functions with and without parentheses around single parameters.
*
* Examples:
* - `x => x + 1` matches `(x) => x + 1`
*/
override async visitLambdaParameters(parameters: J.Lambda.Parameters, other: J): Promise {
if (!this.match) return parameters;
if (other.kind !== J.Kind.LambdaParameters) {
return this.kindMismatch();
}
const otherParams = other as J.Lambda.Parameters;
// Compare all properties except 'parenthesized' using reflection
for (const key of Object.keys(parameters)) {
if (key.startsWith('_') || key === 'id' || key === 'markers' || key === 'parenthesized') {
continue;
}
const jValue = (parameters as any)[key];
const otherValue = (otherParams as any)[key];
// Handle arrays
if (Array.isArray(jValue)) {
if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
return this.arrayLengthMismatch(key);
}
for (let i = 0; i < jValue.length; i++) {
await this.visitProperty(jValue[i], otherValue[i]);
if (!this.match) return parameters;
}
} else {
await this.visitProperty(jValue, otherValue);
if (!this.match) return parameters;
}
}
return parameters;
}
/**
* Override visitPropertyAssignment to allow semantic equivalence between
* object property shorthand and longhand forms.
*
* Examples:
* - `{ x }` matches `{ x: x }`
* - `{ x: x, y: y }` matches `{ x, y }`
*/
override async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, other: J): Promise {
if (!this.match) return propertyAssignment;
if (other.kind !== JS.Kind.PropertyAssignment) {
return this.kindMismatch();
}
const otherProp = other as JS.PropertyAssignment;
// Extract property names for semantic comparison
const propName = this.getPropertyName(propertyAssignment);
const otherPropName = this.getPropertyName(otherProp);
// Names must match
if (!propName || !otherPropName || propName !== otherPropName) {
// Can't do semantic comparison without identifiers, fall back to exact comparison
return await super.visitPropertyAssignment(propertyAssignment, other);
}
// Detect shorthand (no initializer) vs longhand (has initializer)
const isShorthand1 = !propertyAssignment.initializer;
const isShorthand2 = !otherProp.initializer;
if (isShorthand1 === isShorthand2) {
// Both shorthand or both longhand - use base comparison
return await super.visitPropertyAssignment(propertyAssignment, other);
}
// One is shorthand, one is longhand - check semantic equivalence
const longhandProp = isShorthand1 ? otherProp : propertyAssignment;
// Check if the longhand's initializer is an identifier with the same name as the property
if (this.isIdentifierWithName(longhandProp.initializer, propName)) {
// Semantically equivalent!
return propertyAssignment;
} else {
// Not equivalent (e.g., { x: y })
return this.structuralMismatch('initializer');
}
}
/**
* Extracts the property name from a PropertyAssignment.
* Returns the simple name if the property is an identifier, undefined otherwise.
*/
private getPropertyName(prop: JS.PropertyAssignment): string | undefined {
const nameExpr = prop.name.element;
return isIdentifier(nameExpr) ? nameExpr.simpleName : undefined;
}
/**
* Checks if an expression is an identifier with the given name.
*/
private isIdentifierWithName(expr: Expression | undefined, name: string): boolean | undefined {
return expr && isIdentifier(expr) && expr.simpleName === name;
}
/**
* Extracts the expression from an arrow function body.
* Returns the expression if:
* - body is already an Expression, OR
* - body is a Block with exactly one Return statement
* Otherwise returns undefined.
*/
private extractExpression(body: Statement | Expression): Expression | undefined {
// If it's already an expression, return it
if ((body as any).kind !== J.Kind.Block) {
return body as Expression;
}
// It's a block - check if it contains exactly one return statement
const block = body as J.Block;
if (block.statements.length !== 1) {
return undefined;
}
// Unwrap the RightPadded wrapper from the statement
const stmtWrapper = block.statements[0];
const stmt = stmtWrapper.element;
if ((stmt as any).kind !== J.Kind.Return) {
return undefined;
}
const returnStmt = stmt as J.Return;
return returnStmt.expression;
}
/**
* Override visitProperty to allow lenient type matching.
* When lenientTypeMatching is enabled, null vs Type comparisons are allowed
* (where one value is null/undefined and the other is a Type object).
*/
protected override async visitProperty(j: any, other: any, propertyName?: string): Promise {
// Handle null/undefined with lenient type matching
if (this.lenientTypeMatching && (j == null || other == null)) {
if (j !== other) {
// Don't abort if one is null and the other is a Type
if (!Type.isType(j) && !Type.isType(other)) {
this.structuralMismatch(propertyName!);
}
}
return j;
}
// Otherwise, use base class behavior
return super.visitProperty(j, other);
}
/**
* Checks if two types are semantically equal.
* For method types, this checks that the declaring type and method name match.
* With lenient type matching, undefined types on either side are considered compatible.
*/
private isOfType(target?: Type, source?: Type): boolean {
if (!target || !source) {
// Lenient mode: if either type is undefined, allow the match
return this.lenientTypeMatching ? true : target === source;
}
if (target.kind !== source.kind && (target.kind == Type.Kind.Unknown || source.kind == Type.Kind.Unknown)) {
// In lenient mode, allow kind mismatches (e.g., Unknown vs proper type)
// This handles cases where pattern has unresolved types
return this.lenientTypeMatching;
}
// For method types, check declaring type
// Note: We don't check the name field because it might not be fully resolved in patterns
// The method invocation visitor already checks that simple names match
if (target.kind === Type.Kind.Method && source.kind === Type.Kind.Method) {
const targetMethod = target as Type.Method;
const sourceMethod = source as Type.Method;
// Check if declaring types match
const declaringTypesMatch = this.isOfType(targetMethod.declaringType, sourceMethod.declaringType);
if (declaringTypesMatch) {
return true;
}
// If declaring types don't match exactly, check if they might be semantically equivalent
// (e.g., 'react' module vs 'React' namespace importing from 'react')
// In this case, we check if the method signatures are otherwise identical
if (targetMethod.declaringType && sourceMethod.declaringType &&
Type.isFullyQualified(targetMethod.declaringType) && Type.isFullyQualified(sourceMethod.declaringType)) {
const targetDeclType = targetMethod.declaringType as Type.FullyQualified;
const sourceDeclType = sourceMethod.declaringType as Type.FullyQualified;
// Check if the declaring type names could represent the same module
// (e.g., 'react' and 'React', where React is a namespace alias)
const targetFQN = Type.FullyQualified.getFullyQualifiedName(targetDeclType);
const sourceFQN = Type.FullyQualified.getFullyQualifiedName(sourceDeclType);
// If the names differ only in case and one appears to be a module name
// (all lowercase) while the other is capitalized (namespace alias),
// check if the method signatures match
if (targetFQN.toLowerCase() === sourceFQN.toLowerCase()) {
// Method signatures should match: return type and parameters
if (!this.isOfType(targetMethod.returnType, sourceMethod.returnType)) {
return false;
}
if (targetMethod.parameterTypes.length !== sourceMethod.parameterTypes.length) {
return false;
}
for (let i = 0; i < targetMethod.parameterTypes.length; i++) {
if (!this.isOfType(targetMethod.parameterTypes[i], sourceMethod.parameterTypes[i])) {
return false;
}
}
return true;
}
}
return false;
}
// For fully qualified types, check the fully qualified name
if (Type.isFullyQualified(target) && Type.isFullyQualified(source)) {
return Type.FullyQualified.getFullyQualifiedName(target) ===
Type.FullyQualified.getFullyQualifiedName(source);
}
// Default: types are equal if they're the same kind
return true;
}
/**
* Override method invocation comparison to include type attribution checking.
* When types match semantically, we allow matching even if one has a receiver
* and the other doesn't (e.g., `isDate(x)` vs `util.isDate(x)`).
*/
override async visitMethodInvocation(method: J.MethodInvocation, other: J): Promise {
if (other.kind !== J.Kind.MethodInvocation) {
return this.kindMismatch();
}
const otherMethod = other as J.MethodInvocation;
// Fast path: if both have method types with known names, compare canonical method names first.
// This catches most mismatches (e.g., jest.fn() vs jest.mock()) before the
// more expensive FQ declaring type analysis below.
// Skip when either name is the placeholder "unknown" (incomplete type attribution).
if (method.methodType && otherMethod.methodType &&
method.methodType.name !== otherMethod.methodType.name &&
method.methodType.name !== 'unknown' && otherMethod.methodType.name !== 'unknown') {
return this.valueMismatch('methodType.name', method.methodType.name, otherMethod.methodType.name);
}
// Check if we can skip name checking based on type attribution
// We can only skip the name check if both have method types AND they represent the SAME method
// (not just type-compatible methods, but the actual same function with same FQN)
let canSkipNameCheck = false;
if (method.methodType && otherMethod.methodType) {
// Check if both method types have fully qualified declaring types with the same FQN
// This indicates they're the same method from the same module (possibly aliased)
const methodDeclaringType = method.methodType.declaringType;
const otherDeclaringType = otherMethod.methodType.declaringType;
if (methodDeclaringType && otherDeclaringType &&
Type.isFullyQualified(methodDeclaringType) && Type.isFullyQualified(otherDeclaringType)) {
const methodFQN = Type.FullyQualified.getFullyQualifiedName(methodDeclaringType as Type.FullyQualified);
const otherFQN = Type.FullyQualified.getFullyQualifiedName(otherDeclaringType as Type.FullyQualified);
// Same module/class AND same method name in the type = same method (can be aliased)
if (methodFQN === otherFQN && method.methodType.name === otherMethod.methodType.name) {
canSkipNameCheck = true;
}
// If FQNs or method names don't match, we can't skip name check - fall through to name checking
}
// If one or both don't have fully qualified types, we can't safely skip name checking
// Fall through to normal name comparison below
}
// Check names unless we determined we can skip based on type FQN matching
if (!canSkipNameCheck) {
if (method.name.simpleName !== otherMethod.name.simpleName) {
return this.valueMismatch('name.simpleName', method.name.simpleName, otherMethod.name.simpleName);
}
// In strict mode, check type attribution requirements
if (!this.lenientTypeMatching) {
// Strict mode: if one has type but the other doesn't, they don't match
if ((method.methodType && !otherMethod.methodType) ||
(!method.methodType && otherMethod.methodType)) {
return this.typeMismatch('methodType');
}
}
// If neither has type, use structural comparison
if (!method.methodType && !otherMethod.methodType) {
return super.visitMethodInvocation(method, other);
}
// If both have types with FQ declaring types, verify they're compatible
// (This prevents matching completely different methods like util.isArray vs util.isBoolean)
if (method.methodType && otherMethod.methodType) {
const methodDeclaringType = method.methodType.declaringType;
const otherDeclaringType = otherMethod.methodType.declaringType;
if (methodDeclaringType && otherDeclaringType &&
Type.isFullyQualified(methodDeclaringType) && Type.isFullyQualified(otherDeclaringType)) {
const methodFQN = Type.FullyQualified.getFullyQualifiedName(methodDeclaringType as Type.FullyQualified);
const otherFQN = Type.FullyQualified.getFullyQualifiedName(otherDeclaringType as Type.FullyQualified);
// Different declaring types = different methods, even with same name
if (methodFQN !== otherFQN) {
return this.valueMismatch('methodType.declaringType');
}
}
}
}
// When types match (canSkipNameCheck = true), we can skip select comparison entirely.
// This allows matching forwardRef() vs React.forwardRef() where types indicate same method.
if (!canSkipNameCheck) {
// Types didn't provide a match - must compare receivers structurally
if ((method.select === undefined) !== (otherMethod.select === undefined)) {
return this.structuralMismatch('select');
}
if (method.select && otherMethod.select) {
await this.visitRightPaddedProperty('select', method.select, otherMethod.select as any);
if (!this.match) return method;
}
}
// else: types matched, skip select comparison (allows namespace vs named imports)
// Compare type parameters
if ((method.typeParameters === undefined) !== (otherMethod.typeParameters === undefined)) {
return this.structuralMismatch('typeParameters');
}
if (method.typeParameters && otherMethod.typeParameters) {
await this.visitContainerProperty('typeParameters', method.typeParameters, otherMethod.typeParameters);
if (!this.match) return method;
}
// Compare name
// If we determined we can skip name check (same FQN method, possibly aliased), skip it
// This allows matching aliased imports where names differ but types are the same
if (!canSkipNameCheck) {
await this.visit(method.name, otherMethod.name);
if (!this.match) return method;
}
// Compare arguments
await this.visitContainerProperty('arguments', method.arguments, otherMethod.arguments);
if (!this.match) return method;
return method;
}
/**
* Override identifier comparison to include:
* 1. Type checking for field access
* 2. Semantic equivalence between `undefined` identifier and void expressions
*/
override async visitIdentifier(identifier: J.Identifier, other: J): Promise {
// Check if this identifier is "undefined" and the other is a void expression
if (identifier.simpleName === 'undefined' && (other as any).kind === JS.Kind.Void) {
// Both evaluate to undefined, so they match
return identifier;
}
if (other.kind !== J.Kind.Identifier) {
return this.kindMismatch();
}
const otherIdentifier = other as J.Identifier;
// Check name matches
if (identifier.simpleName !== otherIdentifier.simpleName) {
return this.valueMismatch('simpleName');
}
// For identifiers with field types, check type attribution
if (identifier.fieldType && otherIdentifier.fieldType) {
if (!this.isOfType(identifier.fieldType, otherIdentifier.fieldType)) {
return this.typeMismatch('fieldType');
}
} else if (identifier.fieldType || otherIdentifier.fieldType) {
// Lenient mode: if either has no type, allow structural matching
if (!this.lenientTypeMatching) {
// Strict mode: if only one has a type, they don't match
return this.typeMismatch('fieldType');
}
}
return super.visitIdentifier(identifier, other);
}
/**
* Override variable declarations comparison to handle lenient type matching.
* When lenientTypeMatching is true, patterns without typeExpression can match
* code with typeExpression.
*/
override async visitVariableDeclarations(variableDeclarations: J.VariableDeclarations, other: J): Promise {
const otherVariableDeclarations = other as J.VariableDeclarations;
// Visit leading annotations
await this.visitArrayProperty(
variableDeclarations,
'leadingAnnotations',
variableDeclarations.leadingAnnotations,
otherVariableDeclarations.leadingAnnotations,
async (ann1, ann2) => { await this.visit(ann1, ann2); }
);
if (!this.match) return variableDeclarations;
// Visit modifiers
await this.visitArrayProperty(
variableDeclarations,
'modifiers',
variableDeclarations.modifiers,
otherVariableDeclarations.modifiers,
async (mod1, mod2) => { await this.visit(mod1, mod2); }
);
if (!this.match) return variableDeclarations;
// Compare typeExpression - lenient matching allows one to be undefined
if ((variableDeclarations.typeExpression === undefined) !== (otherVariableDeclarations.typeExpression === undefined)) {
if (!this.lenientTypeMatching) {
return this.structuralMismatch('typeExpression');
}
// In lenient mode, skip type comparison and continue
} else if (variableDeclarations.typeExpression && otherVariableDeclarations.typeExpression) {
// Both have typeExpression, visit them
await this.visit(variableDeclarations.typeExpression, otherVariableDeclarations.typeExpression);
if (!this.match) return variableDeclarations;
}
// Compare varargs
if ((variableDeclarations.varargs === undefined) !== (otherVariableDeclarations.varargs === undefined)) {
return this.structuralMismatch('varargs');
}
// Compare variables
await this.visitArrayProperty(
variableDeclarations,
'variables',
variableDeclarations.variables,
otherVariableDeclarations.variables,
async (var1, var2) => { await this.visitRightPadded(var1, var2 as any); }
);
if (!this.match) return variableDeclarations;
return variableDeclarations;
}
/**
* Override method declaration comparison to handle lenient type matching.
* When lenientTypeMatching is true, patterns without returnTypeExpression can match
* code with returnTypeExpression.
*/
override async visitMethodDeclaration(methodDeclaration: J.MethodDeclaration, other: J): Promise {
const otherMethodDeclaration = other as J.MethodDeclaration;
// Visit leading annotations
await this.visitArrayProperty(
methodDeclaration,
'leadingAnnotations',
methodDeclaration.leadingAnnotations,
otherMethodDeclaration.leadingAnnotations,
async (ann1, ann2) => { await this.visit(ann1, ann2); }
);
if (!this.match) return methodDeclaration;
// Visit modifiers
await this.visitArrayProperty(
methodDeclaration,
'modifiers',
methodDeclaration.modifiers,
otherMethodDeclaration.modifiers,
async (mod1, mod2) => { await this.visit(mod1, mod2); }
);
if (!this.match) return methodDeclaration;
// Visit type parameters if present
if (!!methodDeclaration.typeParameters !== !!otherMethodDeclaration.typeParameters) {
return this.structuralMismatch('typeParameters');
}
if (methodDeclaration.typeParameters && otherMethodDeclaration.typeParameters) {
await this.visit(methodDeclaration.typeParameters, otherMethodDeclaration.typeParameters);
if (!this.match) return methodDeclaration;
}
// Compare returnTypeExpression - lenient matching allows one to be undefined
if ((methodDeclaration.returnTypeExpression === undefined) !== (otherMethodDeclaration.returnTypeExpression === undefined)) {
if (!this.lenientTypeMatching) {
return this.typeMismatch('returnTypeExpression');
}
// In lenient mode, skip type comparison and continue
} else if (methodDeclaration.returnTypeExpression && otherMethodDeclaration.returnTypeExpression) {
// Both have returnTypeExpression, visit them
await this.visit(methodDeclaration.returnTypeExpression, otherMethodDeclaration.returnTypeExpression);
if (!this.match) return methodDeclaration;
}
// Visit name
await this.visit(methodDeclaration.name, otherMethodDeclaration.name);
if (!this.match) return methodDeclaration;
// Compare parameters
await this.visitContainer(methodDeclaration.parameters, otherMethodDeclaration.parameters as any);
if (!this.match) return methodDeclaration;
// Visit throws if present
if (!!methodDeclaration.throws !== !!otherMethodDeclaration.throws) {
return this.structuralMismatch('throws');
}
if (methodDeclaration.throws && otherMethodDeclaration.throws) {
await this.visitContainer(methodDeclaration.throws, otherMethodDeclaration.throws as any);
if (!this.match) return methodDeclaration;
}
// Visit body if present
if (!!methodDeclaration.body !== !!otherMethodDeclaration.body) {
return this.structuralMismatch('body');
}
if (methodDeclaration.body && otherMethodDeclaration.body) {
await this.visit(methodDeclaration.body, otherMethodDeclaration.body);
if (!this.match) return methodDeclaration;
}
return methodDeclaration;
}
/**
* Override visitVoid to allow semantic equivalence with undefined identifier.
* This handles the reverse case where the pattern is a void expression
* and the source is the undefined identifier.
*
* Examples:
* - `void 0` matches `undefined`
* - `void(0)` matches `undefined`
* - `void 1` matches `undefined`
*/
override async visitVoid(voidExpr: JS.Void, other: J): Promise {
if (!this.match) return voidExpr;
// Check if the other is an undefined identifier
if ((other as any).kind === J.Kind.Identifier) {
const identifier = other as J.Identifier;
if (identifier.simpleName === 'undefined') {
// Both evaluate to undefined, so they match
return voidExpr;
}
}
// Otherwise delegate to parent
return super.visitVoid(voidExpr, other as any);
}
/**
* Override visitLiteral to allow semantic equivalence between
* different numeric literal formats.
*
* Examples:
* - `255` matches `0xFF`
* - `255` matches `0o377`
* - `255` matches `0b11111111`
* - `1000` matches `1e3`
*/
override async visitLiteral(literal: J.Literal, other: J): Promise {
if (!this.match) return literal;
if ((other as any).kind !== J.Kind.Literal) {
return await super.visitLiteral(literal, other);
}
const otherLiteral = other as J.Literal;
// Only compare value and type, ignoring valueSource (text representation) and unicodeEscapes
await this.visitProperty(literal.value, otherLiteral.value, 'value');
if (!this.match) return literal;
await this.visitProperty(literal.type, otherLiteral.type, 'type');
if (!this.match) return literal;
return literal;
}
}