A Java method called through a static factory or fluent chain — `Foo.getInstance().bar()`, `Config.create(opts).build()` — lost the receiver's type, so the chained method either didn't resolve at all or (when a same-named method existed on an unrelated class) attached to whichever class was indexed first. Ports the #645 (C++) / #608 (PHP) 3-part mechanism: - Part 1: capture Java return types in the extractor (skip void/primitives/arrays, unwrap generics, strip package qualifier). - Part 2: encode a chained-call receiver as `inner().method` with normalized empty parens, so factory calls that take arguments still split. - Part 3: matchJavaCallChain resolves the chained method on the factory's return type, validated via resolveMethodOnType so a wrong inference yields NO edge (never a wrong one). Validated: synthetic decoy + absent-method safety tests; real-repo A/B on google/guava (3,227 files) — node count identical (no explosion), 0 edges lost, +1,507 unique chained edges recovered, precision spot-checked verbatim (Splitter.on().split(), CacheBuilder.newBuilder().recordStats(), GraphBuilder.directed().build(), nested MultimapBuilder.linkedHashKeys().arrayListValues()). EXTRACTION_VERSION 5 -> 6. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
eb5960b535
commit
7f6bdf7ad1
@@ -2195,4 +2195,72 @@ void wrong() { WidgetFactory::create().onlyOther(); }
|
||||
expect(callerNamesOf('Other::onlyOther')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Java chained static-factory call resolution (#645/#608 mechanism)', () => {
|
||||
function callerNamesOf(qualifiedName: string): string[] {
|
||||
const target = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName);
|
||||
if (!target) return [];
|
||||
const names = cg
|
||||
.getIncomingEdges(target.id)
|
||||
.filter((e) => e.kind === 'calls')
|
||||
.map((e) => cg.getNode(e.source)?.name)
|
||||
.filter((n): n is string => !!n);
|
||||
return [...new Set(names)].sort();
|
||||
}
|
||||
|
||||
it('resolves Foo.getInstance().bar() via the factory return type, never a same-named decoy', async () => {
|
||||
// Aaa sorts first and has a same-named bar() — it must never win the chain.
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'Main.java'),
|
||||
`class Aaa { void bar() {} }
|
||||
class Foo {
|
||||
static Foo getInstance() { return new Foo(); }
|
||||
void bar() {}
|
||||
}
|
||||
class Caller {
|
||||
void run() { Foo.getInstance().bar(); }
|
||||
}
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
expect(callerNamesOf('Foo::bar')).toEqual(['run']);
|
||||
expect(callerNamesOf('Aaa::bar')).toEqual([]);
|
||||
});
|
||||
|
||||
it('resolves a factory chain that passes arguments — Foo.create(cfg).build()', async () => {
|
||||
// The factory call carries an argument; the extractor must normalize the
|
||||
// receiver to empty parens (`Foo.create().build`) so the chain still splits.
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'Main.java'),
|
||||
`class Config {}
|
||||
class Foo {
|
||||
static Foo create(Config c) { return new Foo(); }
|
||||
void build() {}
|
||||
}
|
||||
class Caller {
|
||||
void run() { Foo.create(new Config()).build(); }
|
||||
}
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
expect(callerNamesOf('Foo::build')).toEqual(['run']);
|
||||
});
|
||||
|
||||
it('creates NO edge when the factory return type lacks the method (silent miss, not a wrong edge)', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'Main.java'),
|
||||
`class Foo {
|
||||
static Foo getInstance() { return new Foo(); }
|
||||
}
|
||||
class Other { void onlyOther() {} }
|
||||
class Caller {
|
||||
void run() { Foo.getInstance().onlyOther(); }
|
||||
}
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
// Foo has no onlyOther() — must not mis-attach to the same-named Other::onlyOther.
|
||||
expect(callerNamesOf('Other::onlyOther')).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user