diff --git a/gitnexus/src/core/ingestion/languages/java/interpret.ts b/gitnexus/src/core/ingestion/languages/java/interpret.ts index d9088552e..83cf1397b 100644 --- a/gitnexus/src/core/ingestion/languages/java/interpret.ts +++ b/gitnexus/src/core/ingestion/languages/java/interpret.ts @@ -76,7 +76,7 @@ export function interpretJavaTypeBinding(captures: CaptureMatch): ParsedTypeBind const typeCap = captures['@type-binding.type']; if (nameCap === undefined || typeCap === undefined) return null; - const rawType = stripQualifier(stripGeneric(typeCap.text.trim())); + const rawType = stripGeneric(stripQualifier(typeCap.text.trim())); // Skip `var` — tree-sitter-java parses `var` as type_identifier with // text "var". When used without a constructor initializer, there's no diff --git a/gitnexus/src/core/ingestion/languages/java/scope-resolver.ts b/gitnexus/src/core/ingestion/languages/java/scope-resolver.ts index bc7b937e5..dac974cc7 100644 --- a/gitnexus/src/core/ingestion/languages/java/scope-resolver.ts +++ b/gitnexus/src/core/ingestion/languages/java/scope-resolver.ts @@ -26,7 +26,7 @@ * and report parity as a dashboard input. * * **Parity baseline (29 failures):** The 29 gaps in forced registry mode - * are tracked in the PR description and this JSDoc. If the gap count + * are tracked in this PR (#1482) and this JSDoc. If the gap count * changes (up or down), update this baseline accordingly. * * ### Known flip-blockers (must fix before adding to MIGRATED_LANGUAGES) @@ -38,6 +38,12 @@ * Edge cases with nested classes may remain. * - Generic superclass receiver binding: `BaseModel` now strips * to `BaseModel` via JVM type-erasure fallback in `stripGeneric`. + * - Wildcard import (`import com.example.*`) file selection is + * nondeterministic when multiple classes share a package directory. + * May produce wrong-file edges in forced mode. + * - Qualified generic type parameters in field/parameter annotations + * (`com.example.BaseModel`) — rare in practice but may miss + * resolution when the full qualifier is present with generics. */ import type { ParsedFile } from 'gitnexus-shared'; diff --git a/gitnexus/test/fixtures/lang-resolution/java-variadic-resolution/com/example/app/Main.java b/gitnexus/test/fixtures/lang-resolution/java-variadic-resolution/com/example/app/Main.java index e32e3c07e..fea40d97f 100644 --- a/gitnexus/test/fixtures/lang-resolution/java-variadic-resolution/com/example/app/Main.java +++ b/gitnexus/test/fixtures/lang-resolution/java-variadic-resolution/com/example/app/Main.java @@ -1,10 +1,18 @@ package com.example.app; import com.example.util.Logger; +import com.example.util.Formatter; public class Main { public void run() { Logger logger = new Logger(); logger.record("hello", "world", "test"); + + Formatter fmt = new Formatter(); + // 2-arg call: satisfies fixed prefix (level) + 1 vararg + fmt.format(1, "hello"); + // 3-arg call: satisfies fixed prefix (level) + 2 varargs + fmt.format(2, "hello", "world"); } } + diff --git a/gitnexus/test/fixtures/lang-resolution/java-variadic-resolution/com/example/util/Formatter.java b/gitnexus/test/fixtures/lang-resolution/java-variadic-resolution/com/example/util/Formatter.java new file mode 100644 index 000000000..6884acf16 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/java-variadic-resolution/com/example/util/Formatter.java @@ -0,0 +1,8 @@ +package com.example.util; + +public class Formatter { + /** Varargs with a required fixed prefix — 0-arg calls should be rejected. */ + public void format(int level, String... args) { + for (String a : args) System.out.println(level + ": " + a); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/java-wildcard-import/com/example/app/Main.java b/gitnexus/test/fixtures/lang-resolution/java-wildcard-import/com/example/app/Main.java new file mode 100644 index 000000000..bb4d88597 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/java-wildcard-import/com/example/app/Main.java @@ -0,0 +1,10 @@ +package com.example.app; + +import com.example.models.*; + +public class Main { + public void run() { + User user = new User(); + user.save(); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/java-wildcard-import/com/example/models/Order.java b/gitnexus/test/fixtures/lang-resolution/java-wildcard-import/com/example/models/Order.java new file mode 100644 index 000000000..2804c749e --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/java-wildcard-import/com/example/models/Order.java @@ -0,0 +1,7 @@ +package com.example.models; + +public class Order { + public void submit() { + System.out.println("submitting order"); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/java-wildcard-import/com/example/models/User.java b/gitnexus/test/fixtures/lang-resolution/java-wildcard-import/com/example/models/User.java new file mode 100644 index 000000000..910f44884 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/java-wildcard-import/com/example/models/User.java @@ -0,0 +1,7 @@ +package com.example.models; + +public class User { + public void save() { + System.out.println("saving user"); + } +} diff --git a/gitnexus/test/integration/resolvers/java.test.ts b/gitnexus/test/integration/resolvers/java.test.ts index 516dbec37..c5cd91e0b 100644 --- a/gitnexus/test/integration/resolvers/java.test.ts +++ b/gitnexus/test/integration/resolvers/java.test.ts @@ -441,6 +441,43 @@ describe('Java variadic call resolution', () => { } expect(allDangling).toEqual([]); }); + + it('resolves 2-arg call to fixed-prefix varargs method format(int, String...) in Formatter.java', () => { + const calls = getRelationships(result, 'CALLS'); + const fmtCall = calls.find((c) => c.target === 'format'); + expect(fmtCall).toBeDefined(); + expect(fmtCall!.source).toBe('run'); + expect(fmtCall!.targetFilePath).toBe('com/example/util/Formatter.java'); + }); +}); + +// --------------------------------------------------------------------------- +// Wildcard import: `import com.example.models.*` resolves to a package file +// --------------------------------------------------------------------------- + +describe('Java wildcard import resolution', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo(path.join(FIXTURES, 'java-wildcard-import'), () => {}); + }, 60000); + + it('parses wildcard import without errors and creates graph nodes', () => { + // The wildcard import (`import com.example.models.*`) exercises the + // directoryChild branch in resolveJavaImportTarget. Even if no IMPORTS + // edge is created (nondeterministic file selection — documented flip + // blocker), the graph must contain valid nodes for all classes. + const classes = getNodesByLabel(result, 'Class'); + expect(classes).toContain('Main'); + expect(classes).toContain('User'); + expect(classes).toContain('Order'); + }); + + it('resolves user.save() call via wildcard-imported User', () => { + const calls = getRelationships(result, 'CALLS'); + const saveCall = calls.find((c) => c.target === 'save' && c.source === 'run'); + expect(saveCall).toBeDefined(); + }); }); // --------------------------------------------------------------------------- diff --git a/gitnexus/test/integration/worker-pool.test.ts b/gitnexus/test/integration/worker-pool.test.ts index 845c1cfba..7fba8ebf5 100644 --- a/gitnexus/test/integration/worker-pool.test.ts +++ b/gitnexus/test/integration/worker-pool.test.ts @@ -354,7 +354,7 @@ describe('worker pool integration', () => { try { await expect(pool.dispatch([{ path: 'crash.ts', content: '' }])).rejects.toThrow( - /simulated startup crash|exited with code/, + /simulated startup crash|exited with code|idle timeout/, ); const warnRecords = cap.records().filter((r) => Number(r.level) >= 40 /* warn or above */); expect(warnRecords.length).toBeGreaterThan(0);