fix(scala): resolve chained static-factory/apply calls Foo.create().bar() (#750) (#761)

Ports the #645 (C++) / #608 (PHP) chained-receiver mechanism to Scala. A call
whose receiver is itself a call — `Foo.create().bar()` (companion factory),
`Builder(cfg).bar()` (case-class apply), or a fluent chain — used to drop the
receiver to a bare `bar`, which name-matched a same-named method on an unrelated
type. The most common wrong edge was a stdlib `Option`/`Iterator` `.map`/`.flatMap`/
`.foreach` mis-attributed onto the project's own same-named class.

- scala.ts: `getReturnType` reads the `return_type` field — generic `List[Foo]`
  → container `List`, qualified `pkg.Foo` → `Foo`, `this.type` left undefined.
- tree-sitter.ts: re-encode `Foo.create().bar` when the inner call's receiver chain
  starts with a capital (companion factory / case-class apply); instance chains
  (`list.map().filter()`) stay bare.
- name-matcher.ts: `scala` joins the dotted-chain gate + CONSTRUCTS_VIA_BARE_CALL
  (case-class `apply` constructs the class); resolveMethodOnType validates, so a
  non-conventional `apply` returning another type yields no edge, not a wrong one.
- index.ts: `scala` joins CHAIN_LANGUAGES so trait-inherited methods resolve via
  the conformance second pass.

Validation: 4 synthetic tests (factory+decoy, case-class apply, trait conformance,
absent-method safety). Real-repo A/B on gatling (750 Scala files): +14 / -59 unique
edges — all corrections. The +14 are retargets (e.g. `HttpProtocolBuilder(cfg).baseUrl`
now resolves to HttpProtocolBuilder::baseUrl, not the same-named private BaseUrlSupport
helper); the -59 are wrong edges removed (stdlib Option/Iterator monad calls
mis-tied to the project's Validation::*, self-loops, decoy collisions) — zero genuine
factory chains dropped (verified: gatling has no real Validation.success().map() chains).
db stable at 40 MB. EXTRACTION_VERSION 12→13. Full suite green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-09 12:09:33 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent ccced9e358
commit 2f96f58cbb
7 changed files with 142 additions and 9 deletions
+11 -6
View File
@@ -600,9 +600,12 @@ export function matchScopedCallChain(
/**
* Languages where an unprefixed capitalized call `Foo(args)` constructs the
* class (so a `Foo(args).method()` receiver's type is `Foo`). Java/C# need `new`,
* so a bare `Foo()` there is a method call, not construction — excluded.
* so a bare `Foo()` there is a method call, not construction — excluded. Scala's
* `Foo(args)` is a case-class / companion `apply`, which conventionally returns
* `Foo` — and resolveMethodOnType validates, so a non-conventional `apply` that
* returns another type simply yields no edge rather than a wrong one.
*/
const CONSTRUCTS_VIA_BARE_CALL = new Set(['kotlin', 'swift']);
const CONSTRUCTS_VIA_BARE_CALL = new Set(['kotlin', 'swift', 'scala']);
/**
* Resolve a dotted chained call whose receiver is a static factory / fluent call —
@@ -1120,15 +1123,17 @@ export function matchReference(
}
// 1d. Dotted chained static-factory / fluent call (Java / Kotlin / C# / Swift /
// Go) — `Foo.getInstance().bar()` encoded as `Foo.getInstance().bar`, or Go's
// bare-factory `New().Method()` as `New().Method` (#645/#608 mechanism). Resolve
// the method's class from the inner call's declared return type, then validate it.
// Go / Scala) — `Foo.getInstance().bar()` encoded as `Foo.getInstance().bar`,
// Go's bare-factory `New().Method()` as `New().Method`, or Scala's companion
// factory `Foo.create().bar()` (#645/#608 mechanism). Resolve the method's class
// from the inner call's declared return type, then validate it.
if (
ref.language === 'java' ||
ref.language === 'kotlin' ||
ref.language === 'csharp' ||
ref.language === 'swift' ||
ref.language === 'go'
ref.language === 'go' ||
ref.language === 'scala'
) {
result = matchDottedCallChain(ref, context);
if (result) return result;