( typeName: string, methodName: string, ref: UnresolvedRef, context: ResolutionContext, confidence: number, resolvedBy: ResolvedRef['resolvedBy'], /** * Optional FQN that identifies WHICH class declaration `typeName` * refers to in the caller's file. When multiple candidates share * the same qualifiedName (`FooConverter::convert` in both * `dao/converter/` and `service/converter/`), the FQN's * file-path-suffix picks the right one — the disambiguation * signal Java imports carry but the call site doesn't (#314). */ preferredFqn?: string, /** Recursion guard for the supertype/conformance walk. */ depth = 0, )
| 480 | // Exported for the precedence unit tests (#1079): they assert the |
| 481 | // preferredFqn → same-file → matches[0] ordering directly. |
| 482 | export function resolveMethodOnType( |
| 483 | typeName: string, |
| 484 | methodName: string, |
| 485 | ref: UnresolvedRef, |
| 486 | context: ResolutionContext, |
| 487 | confidence: number, |
| 488 | resolvedBy: ResolvedRef['resolvedBy'], |
| 489 | /** |
| 490 | * Optional FQN that identifies WHICH class declaration `typeName` |
| 491 | * refers to in the caller's file. When multiple candidates share |
| 492 | * the same qualifiedName (`FooConverter::convert` in both |
| 493 | * `dao/converter/` and `service/converter/`), the FQN's |
| 494 | * file-path-suffix picks the right one — the disambiguation |
| 495 | * signal Java imports carry but the call site doesn't (#314). |
| 496 | */ |
| 497 | preferredFqn?: string, |
| 498 | /** Recursion guard for the supertype/conformance walk. */ |
| 499 | depth = 0, |
| 500 | ): ResolvedRef | null { |
| 501 | // Look up methods by name and match by qualifiedName ending in |
| 502 | // `<typeName>::<methodName>`. This works whether the method is defined |
| 503 | // in-class (`class Foo { int bar() { ... } }`) or out-of-line in a separate |
| 504 | // file (`int Foo::bar() { ... }` in foo.cpp while class Foo is in foo.hpp). |
| 505 | // The previous same-file approach missed the latter — the typical C++ layout. |
| 506 | const methodCandidates = context.getNodesByName(methodName); |
| 507 | const want = `${typeName}::${methodName}`; |
| 508 | const matches: Node[] = []; |
| 509 | for (const m of methodCandidates) { |
| 510 | if (m.kind !== 'method') continue; |
| 511 | if (m.language !== ref.language) continue; |
| 512 | const qn = m.qualifiedName; |
| 513 | if (qn === want || qn.endsWith(`::${want}`)) { |
| 514 | matches.push(m); |
| 515 | } |
| 516 | } |
| 517 | if (matches.length === 0) { |
| 518 | // Conformance fallback: the method may be defined on a supertype `typeName` |
| 519 | // extends, or on a protocol / trait it conforms to (e.g. a Swift protocol- |
| 520 | // extension method, a C# default-interface or extension method, a Kotlin |
| 521 | // extension on a supertype). Walk supertypes transitively (depth-capped) via |
| 522 | // the resolved implements/extends edges — empty in the first resolution pass, |
| 523 | // populated in the conformance pass. Still VALIDATED (the method must exist on |
| 524 | // a supertype), so a wrong inference produces no edge. |
| 525 | if (depth < 4 && context.getSupertypes) { |
| 526 | for (const supertype of context.getSupertypes(typeName, ref.language)) { |
| 527 | const via = resolveMethodOnType( |
| 528 | supertype, methodName, ref, context, confidence, resolvedBy, preferredFqn, depth + 1, |
| 529 | ); |
| 530 | if (via) return via; |
| 531 | } |
| 532 | } |
| 533 | return null; |
| 534 | } |
| 535 | |
| 536 | if (matches.length > 1 && preferredFqn) { |
| 537 | const ext = ref.language === 'kotlin' ? '.kt' : '.java'; |
| 538 | const fqnPath = preferredFqn.replace(/\./g, '/') + ext; |
| 539 | const chosen = matches.find((m) => { |
no test coverage detected