Files
codegraph/__tests__/java-outside-imports.test.ts
Colby MchenryandClaude Opus 5.5 da1312e83b fix(kotlin): read Kotlin imports; an outside import owns its name (#2196)
The JVM import reader required a `;`, so a Kotlin file had no import
mappings at all. It now reads `import a.b.C` and `import a.b.C as D`.

- The outside-import rule from #2195 applies to Kotlin: `import
  java.net.Proxy`, `import org.mockito.Mockito.mock`, `import okio.buffer`
  own their names. A declaration the file itself makes comes first, in its
  own namespace (a callable for a call, a type otherwise) - Exposed's
  `toLocalDateTime(value)` beside `import kotlinx.datetime.toLocalDateTime`
  is the column type's member; gson's `new URI(...)` stays `java.net.URI`
  beside `TypeAdapters`' field `URI`.
- The mappings pick among same-named project classes: okhttp's
  `MockResponse.Builder()` (642) and `Request.Builder()` reach their classes,
  Exposed's `TestDB`/`SchemaUtils` the module the file imports, moshi's
  `Moshi.Builder()` (102).
- A renaming import binds its alias by FQN: kotlinx.coroutines' `import
  ...internal.unsafeFlow as flow` makes its 28 `flow { }` builders
  `unsafeFlow`, not the public `flow`.

okhttp -486 +1303, Exposed -1685 +1351 (duplicate-module reroutes; losses
are kotlin.test/kotlinx/okio/clikt/Gradle functions), moshi -21 +121, okio
-128 +51, koin -22 +11, coroutines -68 +47, nowinandroid -44 (Gradle DSL,
Compose). Java repos (gson, commons-lang, retrofit, jsoup, halo)
unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:45:40 +00:00

124 lines
3.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* A Java single-type import names the class it imports: after
* `import java.lang.reflect.Field;` the file's `Field` is the JDK's — gson's
* production code bound it to a test's nested `ParameterizedTypesTest.Field`,
* jsoup's W3C converter (`import org.w3c.dom.Document;`) to jsoup's own
* `Document`. A static import (`import static org.junit.Assert.assertEquals;`)
* likewise owns its name. Imports of the project's own classes — nested ones
* included — resolve as before. Kotlin imports (no `;`, optional `as`) are read
* the same way; a member the file itself declares still comes first.
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
let root = '';
let cg: CodeGraph;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-java-imports-'));
const files: Record<string, string> = {
'src/test/java/app/functional/TypesTest.java': `package app.functional;
public class TypesTest {
static class Field {
String name;
}
static void assertEquals(Object a, Object b) { }
}
`,
'src/main/java/app/model/Outer.java': `package app.model;
public class Outer {
public static class Inner {
public static Inner create() { return new Inner(); }
}
}
`,
'src/main/java/app/Reflect.java': `package app;
import java.lang.reflect.Field;
import app.model.Outer;
import app.model.Outer.Inner;
public class Reflect {
String nameOf(Field field) {
return field.getName();
}
Outer.Inner make() {
return Inner.create();
}
}
`,
'src/main/kotlin/app/Dates.kt': `package app
import java.lang.reflect.Field
import kotlinx.datetime.toLocalDateTime
import app.model.Outer.Inner as Made
class Dates {
private fun toLocalDateTime(value: Long): Long = value
fun parse(value: Long, field: Field): Long {
field.getName()
Made.create()
return toLocalDateTime(value)
}
}
`,
'src/test/java/app/ReflectTest.java': `package app;
import static org.junit.Assert.assertEquals;
public class ReflectTest {
void names() {
assertEquals("a", "a");
}
}
`,
};
for (const [rel, content] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
});
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
/** `kind qualified-name` of every non-structural edge out of a file. */
function edgesFrom(file: string): string[] {
const ids = cg.getNodesInFile(file).map((n) => n.id);
return cg
.getOutgoingEdgesFrom(ids)
.filter((e) => e.kind !== 'contains' && e.kind !== 'imports')
.map((e) => `${e.kind} ${cg.getNode(e.target)!.qualifiedName}`)
.sort();
}
describe('Java single-type and static imports from outside the project', () => {
it('own their names; the project’s own imports still resolve', () => {
const edges = edgesFrom('src/main/java/app/Reflect.java');
expect(edges).not.toContain('references app.functional::TypesTest::Field');
expect(edges).toContain('calls app.model::Outer::Inner::create');
});
it('Kotlin: an outside import owns its name, but a member the file declares comes first; `as` aliases bind', () => {
const edges = edgesFrom('src/main/kotlin/app/Dates.kt');
expect(edges).not.toContain('references app.functional::TypesTest::Field');
expect(edges).toContain('calls app::Dates::toLocalDateTime');
expect(edges).toContain('calls app.model::Outer::Inner::create');
});
it('a static import’s method is not a project method of that name', () => {
expect(edgesFrom('src/test/java/app/ReflectTest.java')).not.toContain('calls app.functional::TypesTest::assertEquals');
});
});