( ref: UnresolvedRef, context: ResolutionContext, )
| 944 | * (Java, Kotlin, C#, Swift) — same receiver shape, same `Class::method` qualified names. |
| 945 | */ |
| 946 | export function matchDottedCallChain( |
| 947 | ref: UnresolvedRef, |
| 948 | context: ResolutionContext, |
| 949 | ): ResolvedRef | null { |
| 950 | const m = ref.referenceName.match(/^(.+)\(\)\.(\w+)$/); |
| 951 | if (!m || !m[1] || !m[2]) return null; |
| 952 | const inner = m[1]; // `Foo.getInstance` |
| 953 | const method = m[2]; // `bar` |
| 954 | const lastDot = inner.lastIndexOf('.'); |
| 955 | |
| 956 | if (lastDot <= 0) { |
| 957 | // Go: bare package-level factory FUNCTION `New().method()` — the receiver's |
| 958 | // type is what `New` returns; resolve the method on that. |
| 959 | if (ref.language === 'go') { |
| 960 | const ret = lookupCalleeReturnType(inner, ref, context); |
| 961 | if (ret) { |
| 962 | return resolveMethodOnType(ret, method, ref, context, 0.85, 'instance-method', importedFqnOf(ret, ref, context)); |
| 963 | } |
| 964 | // `inner` isn't a function with a captured return type — typically a |
| 965 | // package-level VARIABLE holding a function value (e.g. gin's `engine()`), |
| 966 | // whose type we can't recover. Fall back to bare-name resolution of the |
| 967 | // method so we don't DROP an edge the un-re-encoded bare path would have |
| 968 | // found. (When `inner` IS a real factory function but the method doesn't |
| 969 | // exist on its return type, `ret` is truthy and we returned no edge above — |
| 970 | // the absent-method safety guarantee is preserved.) |
| 971 | // |
| 972 | // CRITICAL: resolve the TARGET via a synthetic bare-name ref, but return the |
| 973 | // match tied to the ORIGINAL `ref` (referenceName `inner().method`). The |
| 974 | // batched resolver (resolveAndPersistBatched) reads unresolved rows from |
| 975 | // offset 0 every pass and relies on the post-batch cleanup (row-id delete |
| 976 | // for DB-loaded refs, referenceName-keyed delete otherwise, #1269) to |
| 977 | // clear each resolved row so the batch empties. If we propagated the |
| 978 | // synthetic ref's bare `method` as `.original`, a key-based delete |
| 979 | // would never match the stored `inner().method` row, the batch would |
| 980 | // never drain, and the loop would re-resolve + re-insert forever (a runaway |
| 981 | // that grew gin's graph to 5M edges / 1.4 GB before this fix). |
| 982 | const bareRef = { ...ref, referenceName: method }; |
| 983 | const bareMatch = matchByExactName(bareRef, context) ?? matchFuzzy(bareRef, context); |
| 984 | return bareMatch ? { ...bareMatch, original: ref } : null; |
| 985 | } |
| 986 | // Constructor receiver `Foo(args).method()` (encoded `Foo().method`): a bare, |
| 987 | // capitalized inner is a class construction, so the receiver's type is the |
| 988 | // class itself — resolve the method on it. Only in languages where an |
| 989 | // unprefixed capitalized call constructs the class (Kotlin, Swift); in Java/C# |
| 990 | // a bare `Foo()` is a method call (constructors need `new`), so we must not |
| 991 | // assume construction. A lowercase bare inner is a top-level `factory().method()` |
| 992 | // whose type we can't recover — bail. |
| 993 | if (!CONSTRUCTS_VIA_BARE_CALL.has(ref.language) || !/^[A-Z]/.test(inner)) return null; |
| 994 | return resolveMethodOnType(inner, method, ref, context, 0.85, 'instance-method', importedFqnOf(inner, ref, context)); |
| 995 | } |
| 996 | |
| 997 | // Factory/fluent receiver `Receiver.factory(args).method()`: the receiver's |
| 998 | // type is what `Receiver.factory` returns (its declared return type). |
| 999 | const factoryClass = inner.slice(0, lastDot).split('.').pop(); // simple class name |
| 1000 | const factoryMethod = inner.slice(lastDot + 1); |
| 1001 | if (!factoryClass || !factoryMethod) return null; |
| 1002 | const ret = lookupCalleeReturnType(`${factoryClass}::${factoryMethod}`, ref, context); |
| 1003 | if (!ret) { |
no test coverage detected