A Maven multi-module project where `dao/converter/FooConverter` and `service/converter/FooConverter` both expose a `convert` method used to resolve by file-path proximity — picking whichever class was closer to the caller, which is wrong any time the caller lives in an equidistant cross-cutting module. `extractImportMappings` had no Java branch at all, so the FQN signal Java imports carry — `import com.example.dao.converter.FooConverter;` — was thrown away. - `extractJavaImports` parses regular and `import static` directives; wildcard imports (`*`) are intentionally skipped. - `resolveViaImport` has a new Java/Kotlin cross-file branch that converts the imported FQN to a file-path suffix (`com/example/dao/converter/FooConverter.java`, or `.kt`) and resolves the symbol against the file whose path matches by suffix. - For the field-receiver pattern (`@Autowired private FooConverter fooConverter; fooConverter.convert(...)`), `matchMethodCall` now looks up the receiver's inferred type in the caller file's imports and threads the resulting FQN through to `resolveMethodOnType`. When two `FooConverter::convert` candidates exist, the import — not iteration order — picks the right one. Validated with a synthetic 3-module repro: swapping only the import line on the caller swaps the resolved target between dao and service. spring-petclinic (47 .java files): +15 newly import-resolved edges, +2 references, no regression elsewhere. Closes #314.
This commit is contained in:
@@ -802,6 +802,64 @@ export async function finaliseRecording(recorder: RecorderHandle) {
|
||||
expect(handleStop!.kind).toBe('method');
|
||||
});
|
||||
|
||||
it('Java import disambiguates same-name classes across modules (#314)', async () => {
|
||||
// Pre-#314 the import resolver had no Java branch at all, so a
|
||||
// multi-module Maven repo where `dao/converter/FooConverter` and
|
||||
// `service/converter/FooConverter` both export a `convert` method
|
||||
// resolved by file-path proximity — picking whichever class was
|
||||
// closer to the caller, which is wrong any time the caller lives
|
||||
// in an equidistant cross-cutting module.
|
||||
const daoDir = path.join(tempDir, 'dao/src/main/java/com/example/dao/converter');
|
||||
const serviceDir = path.join(tempDir, 'service/src/main/java/com/example/service/converter');
|
||||
const webDir = path.join(tempDir, 'web/src/main/java/com/example/web');
|
||||
fs.mkdirSync(daoDir, { recursive: true });
|
||||
fs.mkdirSync(serviceDir, { recursive: true });
|
||||
fs.mkdirSync(webDir, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(daoDir, 'FooConverter.java'),
|
||||
`package com.example.dao.converter;
|
||||
public class FooConverter { public String convert(String x) { return "dao:" + x; } }
|
||||
`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(serviceDir, 'FooConverter.java'),
|
||||
`package com.example.service.converter;
|
||||
public class FooConverter { public String convert(String x) { return "svc:" + x; } }
|
||||
`
|
||||
);
|
||||
// The caller imports the SERVICE version — even though dao is
|
||||
// alphabetically/lexically first in the candidate list, the
|
||||
// import must trump that order.
|
||||
fs.writeFileSync(
|
||||
path.join(webDir, 'Handler.java'),
|
||||
`package com.example.web;
|
||||
|
||||
import com.example.service.converter.FooConverter;
|
||||
|
||||
public class Handler {
|
||||
private FooConverter fooConverter;
|
||||
public String use() { return fooConverter.convert("input"); }
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
|
||||
const use = cg
|
||||
.getNodesByKind('method')
|
||||
.find((n) => n.qualifiedName === 'Handler::use');
|
||||
expect(use).toBeDefined();
|
||||
const calls = cg.getOutgoingEdges(use!.id).filter((e) => e.kind === 'calls');
|
||||
expect(calls.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const target = cg.getNode(calls[0]!.target);
|
||||
expect(target?.name).toBe('convert');
|
||||
expect(target?.filePath.replace(/\\/g, '/')).toBe(
|
||||
'service/src/main/java/com/example/service/converter/FooConverter.java'
|
||||
);
|
||||
});
|
||||
|
||||
it('C# extracts references from method/property/field types (#381)', async () => {
|
||||
// Pre-#381, every C# project produced ZERO `references` edges:
|
||||
// csharp.ts was missing returnField, and the type-leaf walker
|
||||
|
||||
Reference in New Issue
Block a user