mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-21 00:21:30 +00:00
test: METHOD_IMPLEMENTS integration tests for C#, TypeScript, Kotlin, Dart
Add interface dispatch fixtures and METHOD_IMPLEMENTS edge assertions for the 4 remaining languages that support interfaces: - C#: IRepository interface with Find/Save, SqlRepository implements - TypeScript: IRepository interface with find/save, SqlRepository implements - Kotlin: Repository interface with find/save, SqlRepository implements - Dart: abstract Repository with find/save, SqlRepository implements Fix: Kotlin interfaces use class_declaration AST node type — added keyword-based detection in findEnclosingClassInfo to correctly label them as Interface (not Class), enabling METHOD_IMPLEMENTS edge emission. Total METHOD_IMPLEMENTS integration coverage: 9 languages (Java, PHP, Rust, Swift, Python, C#, TypeScript, Kotlin, Dart)
This commit is contained in:
parent
1966242675
commit
deabfcb046
17 changed files with 277 additions and 1 deletions
|
|
@ -337,7 +337,17 @@ export const findEnclosingClassInfo = (
|
|||
c.type === 'constant',
|
||||
);
|
||||
if (nameNode) {
|
||||
const label = CONTAINER_TYPE_TO_LABEL[current.type] || 'Class';
|
||||
let label = CONTAINER_TYPE_TO_LABEL[current.type] || 'Class';
|
||||
// Kotlin: class_declaration with an anonymous "interface" keyword child
|
||||
// is actually an interface, not a class. Refine the label to match the
|
||||
// node ID generated from the tree-sitter query capture (@definition.interface).
|
||||
if (
|
||||
current.type === 'class_declaration' &&
|
||||
label === 'Class' &&
|
||||
current.children?.some((c: SyntaxNode) => c.type === 'interface')
|
||||
) {
|
||||
label = 'Interface';
|
||||
}
|
||||
return {
|
||||
classId: generateId(label, `${filePath}:${nameNode.text}`),
|
||||
className: nameNode.text,
|
||||
|
|
|
|||
7
gitnexus/test/fixtures/lang-resolution/csharp-interface-dispatch/App.cs
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/csharp-interface-dispatch/App.cs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
public class App {
|
||||
public static void Main() {
|
||||
IRepository repo = new SqlRepository();
|
||||
repo.Find(1);
|
||||
repo.Save("test");
|
||||
}
|
||||
}
|
||||
4
gitnexus/test/fixtures/lang-resolution/csharp-interface-dispatch/IRepository.cs
vendored
Normal file
4
gitnexus/test/fixtures/lang-resolution/csharp-interface-dispatch/IRepository.cs
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
public interface IRepository {
|
||||
string Find(int id);
|
||||
bool Save(string entity);
|
||||
}
|
||||
9
gitnexus/test/fixtures/lang-resolution/csharp-interface-dispatch/SqlRepository.cs
vendored
Normal file
9
gitnexus/test/fixtures/lang-resolution/csharp-interface-dispatch/SqlRepository.cs
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
public class SqlRepository : IRepository {
|
||||
public string Find(int id) {
|
||||
return "found";
|
||||
}
|
||||
|
||||
public bool Save(string entity) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/dart-interface-dispatch/app.dart
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/dart-interface-dispatch/app.dart
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import 'sql_repository.dart';
|
||||
|
||||
void main() {
|
||||
final repo = SqlRepository();
|
||||
repo.find(1);
|
||||
repo.save("test");
|
||||
}
|
||||
4
gitnexus/test/fixtures/lang-resolution/dart-interface-dispatch/repository.dart
vendored
Normal file
4
gitnexus/test/fixtures/lang-resolution/dart-interface-dispatch/repository.dart
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
abstract class Repository {
|
||||
String find(int id);
|
||||
bool save(String entity);
|
||||
}
|
||||
13
gitnexus/test/fixtures/lang-resolution/dart-interface-dispatch/sql_repository.dart
vendored
Normal file
13
gitnexus/test/fixtures/lang-resolution/dart-interface-dispatch/sql_repository.dart
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import 'repository.dart';
|
||||
|
||||
class SqlRepository implements Repository {
|
||||
@override
|
||||
String find(int id) {
|
||||
return "found";
|
||||
}
|
||||
|
||||
@override
|
||||
bool save(String entity) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/kotlin-interface-dispatch/App.kt
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/kotlin-interface-dispatch/App.kt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
fun main() {
|
||||
val repo: Repository = SqlRepository()
|
||||
repo.find(1)
|
||||
repo.save("test")
|
||||
}
|
||||
4
gitnexus/test/fixtures/lang-resolution/kotlin-interface-dispatch/Repository.kt
vendored
Normal file
4
gitnexus/test/fixtures/lang-resolution/kotlin-interface-dispatch/Repository.kt
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
interface Repository {
|
||||
fun find(id: Int): String
|
||||
fun save(entity: String): Boolean
|
||||
}
|
||||
9
gitnexus/test/fixtures/lang-resolution/kotlin-interface-dispatch/SqlRepository.kt
vendored
Normal file
9
gitnexus/test/fixtures/lang-resolution/kotlin-interface-dispatch/SqlRepository.kt
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
class SqlRepository : Repository {
|
||||
override fun find(id: Int): String {
|
||||
return "found"
|
||||
}
|
||||
|
||||
override fun save(entity: String): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/typescript-interface-dispatch/app.ts
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/typescript-interface-dispatch/app.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { SqlRepository } from './sql-repository';
|
||||
|
||||
const repo = new SqlRepository();
|
||||
repo.find(1);
|
||||
repo.save("test");
|
||||
4
gitnexus/test/fixtures/lang-resolution/typescript-interface-dispatch/repository.ts
vendored
Normal file
4
gitnexus/test/fixtures/lang-resolution/typescript-interface-dispatch/repository.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export interface IRepository {
|
||||
find(id: number): string;
|
||||
save(entity: string): boolean;
|
||||
}
|
||||
11
gitnexus/test/fixtures/lang-resolution/typescript-interface-dispatch/sql-repository.ts
vendored
Normal file
11
gitnexus/test/fixtures/lang-resolution/typescript-interface-dispatch/sql-repository.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { IRepository } from './repository';
|
||||
|
||||
export class SqlRepository implements IRepository {
|
||||
find(id: number): string {
|
||||
return "found";
|
||||
}
|
||||
|
||||
save(entity: string): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1722,3 +1722,48 @@ describe('C# method enrichment', () => {
|
|||
expect(classifyCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interface dispatch: METHOD_IMPLEMENTS edges
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('C# interface dispatch (METHOD_IMPLEMENTS)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'csharp-interface-dispatch'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('detects IRepository interface and SqlRepository class', () => {
|
||||
const classes = getNodesByLabel(result, 'Class');
|
||||
const ifaces = getNodesByLabel(result, 'Interface');
|
||||
expect(classes).toContain('SqlRepository');
|
||||
expect(ifaces).toContain('IRepository');
|
||||
});
|
||||
|
||||
it('emits IMPLEMENTS edge SqlRepository → IRepository', () => {
|
||||
const impl = getRelationships(result, 'IMPLEMENTS');
|
||||
const edge = impl.find((e) => e.source === 'SqlRepository' && e.target === 'IRepository');
|
||||
expect(edge).toBeDefined();
|
||||
});
|
||||
|
||||
it('emits METHOD_IMPLEMENTS edges for Find and Save', () => {
|
||||
const mi = getRelationships(result, 'METHOD_IMPLEMENTS');
|
||||
const findEdge = mi.find(
|
||||
(e) =>
|
||||
e.source === 'Find' &&
|
||||
e.target === 'Find' &&
|
||||
e.sourceFilePath.includes('SqlRepository') &&
|
||||
e.targetFilePath.includes('IRepository'),
|
||||
);
|
||||
const saveEdge = mi.find(
|
||||
(e) =>
|
||||
e.source === 'Save' &&
|
||||
e.target === 'Save' &&
|
||||
e.sourceFilePath.includes('SqlRepository') &&
|
||||
e.targetFilePath.includes('IRepository'),
|
||||
);
|
||||
expect(findEdge).toBeDefined();
|
||||
expect(saveEdge).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -429,3 +429,48 @@ describe.skipIf(!dartAvailable)('Dart async method detection', () => {
|
|||
expect(formatName!.properties.returnType).toBe('String');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interface dispatch: METHOD_IMPLEMENTS edges from concrete → abstract methods
|
||||
// abstract Repository with find/save, SqlRepository implements them
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe.skipIf(!dartAvailable)('Dart interface dispatch (METHOD_IMPLEMENTS)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'dart-interface-dispatch'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('detects Repository class and SqlRepository class', () => {
|
||||
const classes = getNodesByLabel(result, 'Class');
|
||||
expect(classes).toContain('Repository');
|
||||
expect(classes).toContain('SqlRepository');
|
||||
});
|
||||
|
||||
it('emits IMPLEMENTS edge SqlRepository → Repository', () => {
|
||||
const impl = getRelationships(result, 'IMPLEMENTS');
|
||||
const edge = impl.find((e) => e.source === 'SqlRepository' && e.target === 'Repository');
|
||||
expect(edge).toBeDefined();
|
||||
});
|
||||
|
||||
it('emits METHOD_IMPLEMENTS edges for find and save', () => {
|
||||
const mi = getRelationships(result, 'METHOD_IMPLEMENTS');
|
||||
const findEdge = mi.find(
|
||||
(e) =>
|
||||
e.source === 'find' &&
|
||||
e.target === 'find' &&
|
||||
e.sourceFilePath.includes('sql_repository') &&
|
||||
e.targetFilePath.includes('repository'),
|
||||
);
|
||||
const saveEdge = mi.find(
|
||||
(e) =>
|
||||
e.source === 'save' &&
|
||||
e.target === 'save' &&
|
||||
e.sourceFilePath.includes('sql_repository') &&
|
||||
e.targetFilePath.includes('repository'),
|
||||
);
|
||||
expect(findEdge).toBeDefined();
|
||||
expect(saveEdge).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1811,3 +1811,49 @@ describe('Kotlin method enrichment', () => {
|
|||
expect(classifyCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interface dispatch: METHOD_IMPLEMENTS edges from concrete → interface methods
|
||||
// Repository interface with find/save, SqlRepository implements them
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Kotlin interface dispatch (METHOD_IMPLEMENTS)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'kotlin-interface-dispatch'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('detects Repository interface and SqlRepository class', () => {
|
||||
const classes = getNodesByLabel(result, 'Class');
|
||||
const ifaces = getNodesByLabel(result, 'Interface');
|
||||
expect(classes).toContain('SqlRepository');
|
||||
expect(ifaces).toContain('Repository');
|
||||
});
|
||||
|
||||
it('emits IMPLEMENTS edge SqlRepository → Repository', () => {
|
||||
const impl = getRelationships(result, 'IMPLEMENTS');
|
||||
const edge = impl.find((e) => e.source === 'SqlRepository' && e.target === 'Repository');
|
||||
expect(edge).toBeDefined();
|
||||
});
|
||||
|
||||
it('emits METHOD_IMPLEMENTS edges for find and save', () => {
|
||||
const mi = getRelationships(result, 'METHOD_IMPLEMENTS');
|
||||
const findEdge = mi.find(
|
||||
(e) =>
|
||||
e.source === 'find' &&
|
||||
e.target === 'find' &&
|
||||
e.sourceFilePath.includes('SqlRepository') &&
|
||||
e.targetFilePath.includes('Repository'),
|
||||
);
|
||||
const saveEdge = mi.find(
|
||||
(e) =>
|
||||
e.source === 'save' &&
|
||||
e.target === 'save' &&
|
||||
e.sourceFilePath.includes('SqlRepository') &&
|
||||
e.targetFilePath.includes('Repository'),
|
||||
);
|
||||
expect(findEdge).toBeDefined();
|
||||
expect(saveEdge).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2395,3 +2395,51 @@ describe('TypeScript method enrichment', () => {
|
|||
expect(classifyCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interface dispatch: METHOD_IMPLEMENTS edges
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('TypeScript interface dispatch (METHOD_IMPLEMENTS)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'typescript-interface-dispatch'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('detects IRepository interface and SqlRepository class', () => {
|
||||
const classes = getNodesByLabel(result, 'Class');
|
||||
const ifaces = getNodesByLabel(result, 'Interface');
|
||||
expect(classes).toContain('SqlRepository');
|
||||
expect(ifaces).toContain('IRepository');
|
||||
});
|
||||
|
||||
it('emits IMPLEMENTS edge SqlRepository → IRepository', () => {
|
||||
const impl = getRelationships(result, 'IMPLEMENTS');
|
||||
const edge = impl.find((e) => e.source === 'SqlRepository' && e.target === 'IRepository');
|
||||
expect(edge).toBeDefined();
|
||||
});
|
||||
|
||||
it('emits METHOD_IMPLEMENTS edges for find and save', () => {
|
||||
const mi = getRelationships(result, 'METHOD_IMPLEMENTS');
|
||||
const findEdge = mi.find(
|
||||
(e) =>
|
||||
e.source === 'find' &&
|
||||
e.target === 'find' &&
|
||||
e.sourceFilePath.includes('sql-repository') &&
|
||||
e.targetFilePath.includes('repository'),
|
||||
);
|
||||
const saveEdge = mi.find(
|
||||
(e) =>
|
||||
e.source === 'save' &&
|
||||
e.target === 'save' &&
|
||||
e.sourceFilePath.includes('sql-repository') &&
|
||||
e.targetFilePath.includes('repository'),
|
||||
);
|
||||
expect(findEdge).toBeDefined();
|
||||
expect(saveEdge).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue