@@ -1957,54 +1957,93 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
19571957
19581958 const otherMethod = other as J . MethodInvocation ;
19591959
1960- // Check basic structural equality first
1961- if ( method . name . simpleName !== otherMethod . name . simpleName ||
1962- method . arguments . elements . length !== otherMethod . arguments . elements . length ) {
1960+ // Check argument length (always required)
1961+ if ( method . arguments . elements . length !== otherMethod . arguments . elements . length ) {
19631962 return this . abort ( method ) ;
19641963 }
19651964
1966- // Check type attribution
1967- // Both must have method types for semantic equality
1968- if ( ! method . methodType || ! otherMethod . methodType ) {
1969- // Lenient mode: if either has no type, allow structural matching
1970- if ( this . lenientTypeMatching ) {
1971- return super . visitMethodInvocation ( method , other ) ;
1965+ // Check if we can skip name checking based on type attribution
1966+ // We can only skip the name check if both have method types AND they represent the SAME method
1967+ // (not just type-compatible methods, but the actual same function with same FQN)
1968+ let canSkipNameCheck = false ;
1969+ if ( method . methodType && otherMethod . methodType ) {
1970+ // Check if both method types have fully qualified declaring types with the same FQN
1971+ // This indicates they're the same method from the same module (possibly aliased)
1972+ const methodDeclaringType = method . methodType . declaringType ;
1973+ const otherDeclaringType = otherMethod . methodType . declaringType ;
1974+
1975+ if ( methodDeclaringType && otherDeclaringType &&
1976+ Type . isFullyQualified ( methodDeclaringType ) && Type . isFullyQualified ( otherDeclaringType ) ) {
1977+
1978+ const methodFQN = Type . FullyQualified . getFullyQualifiedName ( methodDeclaringType as Type . FullyQualified ) ;
1979+ const otherFQN = Type . FullyQualified . getFullyQualifiedName ( otherDeclaringType as Type . FullyQualified ) ;
1980+
1981+ // Same module/class AND same method name in the type = same method (can be aliased)
1982+ if ( methodFQN === otherFQN && method . methodType . name === otherMethod . methodType . name ) {
1983+ canSkipNameCheck = true ;
1984+ }
1985+ // If FQNs or method names don't match, we can't skip name check - fall through to name checking
19721986 }
1973- // Strict mode: if one has type but the other doesn't, they don't match
1974- if ( method . methodType || otherMethod . methodType ) {
1987+ // If one or both don't have fully qualified types, we can't safely skip name checking
1988+ // Fall through to normal name comparison below
1989+ }
1990+
1991+ // Check names unless we determined we can skip based on type FQN matching
1992+ if ( ! canSkipNameCheck ) {
1993+ if ( method . name . simpleName !== otherMethod . name . simpleName ) {
19751994 return this . abort ( method ) ;
19761995 }
1977- // If neither has type, fall through to structural comparison
1978- return super . visitMethodInvocation ( method , other ) ;
1979- }
19801996
1981- // Both have types - check they match semantically
1982- const typesMatch = this . isOfType ( method . methodType , otherMethod . methodType ) ;
1983- if ( ! typesMatch ) {
1984- // Types don't match - abort comparison
1985- return this . abort ( method ) ;
1986- }
1997+ // In strict mode, check type attribution requirements
1998+ if ( ! this . lenientTypeMatching ) {
1999+ // Strict mode: if one has type but the other doesn't, they don't match
2000+ if ( ( method . methodType && ! otherMethod . methodType ) ||
2001+ ( ! method . methodType && otherMethod . methodType ) ) {
2002+ return this . abort ( method ) ;
2003+ }
2004+ }
2005+
2006+ // If neither has type, use structural comparison
2007+ if ( ! method . methodType && ! otherMethod . methodType ) {
2008+ return super . visitMethodInvocation ( method , other ) ;
2009+ }
19872010
1988- // Types match! Now check if we can ignore receiver differences.
1989- // We can only ignore receiver differences when one or both receivers are identifiers
1990- // that represent module/namespace imports (e.g., `util` in `util.isDate()`).
1991- // For other receivers (e.g., variables, expressions), we must compare them.
2011+ // If both have types with FQ declaring types, verify they're compatible
2012+ // (This prevents matching completely different methods like util.isArray vs util.isBoolean)
2013+ if ( method . methodType && otherMethod . methodType ) {
2014+ const methodDeclaringType = method . methodType . declaringType ;
2015+ const otherDeclaringType = otherMethod . methodType . declaringType ;
19922016
1993- const canIgnoreReceiverDifference =
1994- // Case 1: One has no select (direct call like `forwardRef()`), other has select (namespace like `React.forwardRef()`)
1995- ( ! method . select && otherMethod . select ) ||
1996- ( method . select && ! otherMethod . select ) ;
2017+ if ( methodDeclaringType && otherDeclaringType &&
2018+ Type . isFullyQualified ( methodDeclaringType ) && Type . isFullyQualified ( otherDeclaringType ) ) {
19972019
1998- if ( ! canIgnoreReceiverDifference ) {
1999- // Both have selects or both don't - must compare them structurally
2020+ const methodFQN = Type . FullyQualified . getFullyQualifiedName ( methodDeclaringType as Type . FullyQualified ) ;
2021+ const otherFQN = Type . FullyQualified . getFullyQualifiedName ( otherDeclaringType as Type . FullyQualified ) ;
2022+
2023+ // Different declaring types = different methods, even with same name
2024+ if ( methodFQN !== otherFQN ) {
2025+ return this . abort ( method ) ;
2026+ }
2027+ }
2028+ }
2029+ }
2030+
2031+ // When types match (canSkipNameCheck = true), we can skip select comparison entirely.
2032+ // This allows matching forwardRef() vs React.forwardRef() where types indicate same method.
2033+ if ( ! canSkipNameCheck ) {
2034+ // Types didn't provide a match - must compare receivers structurally
20002035 if ( ( method . select === undefined ) !== ( otherMethod . select === undefined ) ) {
20012036 return this . abort ( method ) ;
20022037 }
20032038
20042039 if ( method . select && otherMethod . select ) {
20052040 await this . visitRightPadded ( method . select , otherMethod . select as any ) ;
2041+ if ( ! this . match ) {
2042+ return this . abort ( method ) ;
2043+ }
20062044 }
20072045 }
2046+ // else: types matched, skip select comparison (allows namespace vs named imports)
20082047
20092048 // Compare type parameters
20102049 if ( ( method . typeParameters === undefined ) !== ( otherMethod . typeParameters === undefined ) ) {
@@ -2023,10 +2062,14 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
20232062 }
20242063 }
20252064
2026- // Compare name (already checked simpleName above, but visit for markers/prefix)
2027- await this . visit ( method . name , otherMethod . name ) ;
2028- if ( ! this . match ) {
2029- return this . abort ( method ) ;
2065+ // Compare name
2066+ // If we determined we can skip name check (same FQN method, possibly aliased), skip it
2067+ // This allows matching aliased imports where names differ but types are the same
2068+ if ( ! canSkipNameCheck ) {
2069+ await this . visit ( method . name , otherMethod . name ) ;
2070+ if ( ! this . match ) {
2071+ return this . abort ( method ) ;
2072+ }
20302073 }
20312074
20322075 // Compare arguments (visit RightPadded to check for markers)
0 commit comments