mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-06 02:49:56 +00:00
* feat(ts,js): MethodExtractor config for TypeScript and JavaScript (#570) Add per-language method extraction config following the established JVM and C# patterns. Shared config base mirrors the field extractor's typescript-javascript.ts pattern — TS-only node types are harmless no-ops for JS. Key features: - isAbstract for abstract class methods and interface methods - Parameter extraction with isOptional (?:, defaults) and isVariadic (...) - Decorator extraction from preceding body-level siblings - isAsync and isOverride detection - Visibility via accessibility_modifier two-pass pattern - Return type extraction unwrapping type_annotation * test(ts,js): add override, getter/setter, destructured param tests Address code review findings: - Add override method detection test - Add getter/setter extraction test - Add destructured parameter with type annotation test - Tighten constructor and private method assertions * refactor(ts,js): address code review findings - Replace O(M*N) decorator index scan with previousNamedSibling walk - Remove dead findVisibility 'modifiers' fallback (TS uses accessibility_modifier, not a modifiers wrapper) - Document call_signature/construct_signature as known gaps - Document that TS constructors are method_definition nodes - Remove unused findVisibility import * fix(ts,js): type guard before cast, add generator/computed/overload tests - Use type guard pattern (Set.has check before as-cast) in visibility extraction to ensure string is validated before narrowing - Add generator method test (*items()) — confirms extraction works - Add computed property name test ([Symbol.iterator]) — documents bracket-in-name behavior as intentional - Add class-level method overload test — verifies overload signatures + implementation are all extracted * fix(ts,js): detect #private methods as visibility 'private' ES2022 private class methods (#name) use private_property_identifier as their name node type. Detect this and return 'private' visibility instead of the default 'public'. * fix(ts,js): address review findings + close ingestion gaps - hasKeyword/findVisibility: skip name field child to prevent false positives on soft-keyword method names (e.g. `abstract()`, `static()`) - extractTsJsParameters: filter TS `this` parameter (compile-time only) - extractMethodSignature: mirror `this`-param skip in fallback path - tree-sitter queries: capture abstract_method_signature, method_signature, and private_property_identifier for TS; add private_property_identifier for JS - Remove dead childForFieldName('name') fallbacks and typeFromAnnotation fallback - Add 10+ unit tests, 4 integration tests through query pipeline * test(ts): update HAS_METHOD count for interface method_signature capture The new method_signature query now captures ILogger.log() as a Method node with a HAS_METHOD edge, increasing the expected count from 4 to 5. * fix(ts,js): address second review — async generator test, declare module gap - Add async generator method test (async *values() → isAsync: true) - Document declare module/global augmentation as known gap
523 lines
18 KiB
TypeScript
523 lines
18 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { extractMethodSignature } from '../../src/core/ingestion/utils/ast-helpers.js';
|
|
import Parser from 'tree-sitter';
|
|
import TypeScript from 'tree-sitter-typescript';
|
|
import Python from 'tree-sitter-python';
|
|
import Java from 'tree-sitter-java';
|
|
import CSharp from 'tree-sitter-c-sharp';
|
|
import Kotlin from 'tree-sitter-kotlin';
|
|
import CPP from 'tree-sitter-cpp';
|
|
import Go from 'tree-sitter-go';
|
|
import Rust from 'tree-sitter-rust';
|
|
|
|
describe('extractMethodSignature', () => {
|
|
const parser = new Parser();
|
|
|
|
it('returns zero params and no return type for null node', () => {
|
|
const sig = extractMethodSignature(null);
|
|
expect(sig.parameterCount).toBe(0);
|
|
expect(sig.returnType).toBeUndefined();
|
|
});
|
|
|
|
describe('TypeScript', () => {
|
|
it('extracts params and return type from a typed method', () => {
|
|
parser.setLanguage(TypeScript.typescript);
|
|
const code = `class Foo {
|
|
greet(name: string, age: number): boolean { return true; }
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBe(2);
|
|
expect(sig.returnType).toBe('boolean');
|
|
});
|
|
|
|
it('extracts zero params from a method with no parameters', () => {
|
|
parser.setLanguage(TypeScript.typescript);
|
|
const code = `class Foo {
|
|
run(): void {}
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBe(0);
|
|
expect(sig.returnType).toBe('void');
|
|
});
|
|
|
|
it('extracts params without return type annotation', () => {
|
|
parser.setLanguage(TypeScript.typescript);
|
|
const code = `class Foo {
|
|
process(x: number) { return x + 1; }
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBe(1);
|
|
expect(sig.returnType).toBeUndefined();
|
|
});
|
|
|
|
it('skips TypeScript this-parameter (compile-time constraint)', () => {
|
|
parser.setLanguage(TypeScript.typescript);
|
|
const code = `class Handler {
|
|
handle(this: void, event: Event): void {}
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
// 'this' is not a real parameter — only 'event' should be counted
|
|
expect(sig.parameterCount).toBe(1);
|
|
});
|
|
|
|
it('skips this-parameter in top-level function', () => {
|
|
parser.setLanguage(TypeScript.typescript);
|
|
const code = `function onClick(this: HTMLElement, ev: MouseEvent): void {}`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.child(0)!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.parameterCount).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('Python', () => {
|
|
it('skips self parameter', () => {
|
|
parser.setLanguage(Python);
|
|
const code = `class Foo:
|
|
def bar(self, x, y):
|
|
pass`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBe(2);
|
|
expect(sig.returnType).toBeUndefined();
|
|
});
|
|
|
|
it('handles method with only self', () => {
|
|
parser.setLanguage(Python);
|
|
const code = `class Foo:
|
|
def noop(self):
|
|
pass`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBe(0);
|
|
});
|
|
|
|
it('handles Python return type annotation', () => {
|
|
parser.setLanguage(Python);
|
|
const code = `class Foo:
|
|
def bar(self, x: int) -> bool:
|
|
return True`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBe(1);
|
|
// The important thing is parameterCount is correct; returnType may vary.
|
|
});
|
|
});
|
|
|
|
describe('Java', () => {
|
|
it('extracts params from a Java method', () => {
|
|
parser.setLanguage(Java);
|
|
const code = `class Foo {
|
|
public int add(int a, int b) { return a + b; }
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBe(2);
|
|
});
|
|
|
|
it('extracts zero params from no-arg Java method', () => {
|
|
parser.setLanguage(Java);
|
|
const code = `class Foo {
|
|
public void run() {}
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBe(0);
|
|
});
|
|
|
|
it('extracts parameterTypes for Java overloaded methods', () => {
|
|
parser.setLanguage(Java);
|
|
const code = `class Svc {
|
|
public User lookup(int id) { return null; }
|
|
public User lookup(String name) { return null; }
|
|
public void process(int code, String msg) {}
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classBody = tree.rootNode.child(0)!.childForFieldName('body')!;
|
|
|
|
const sig0 = extractMethodSignature(classBody.namedChild(0)!);
|
|
expect(sig0.parameterCount).toBe(1);
|
|
expect(sig0.parameterTypes).toEqual(['int']);
|
|
|
|
const sig1 = extractMethodSignature(classBody.namedChild(1)!);
|
|
expect(sig1.parameterCount).toBe(1);
|
|
expect(sig1.parameterTypes).toEqual(['String']);
|
|
|
|
const sig2 = extractMethodSignature(classBody.namedChild(2)!);
|
|
expect(sig2.parameterCount).toBe(2);
|
|
expect(sig2.parameterTypes).toEqual(['int', 'String']);
|
|
});
|
|
});
|
|
|
|
describe('Kotlin', () => {
|
|
it('extracts params from a Kotlin function declaration', () => {
|
|
parser.setLanguage(Kotlin);
|
|
const code = `object OneArg {
|
|
fun writeAudit(message: String): String {
|
|
return message
|
|
}
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const objectNode = tree.rootNode.child(0)!;
|
|
const classBody = objectNode.namedChild(1)!;
|
|
const functionNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(functionNode);
|
|
expect(sig.parameterCount).toBe(1);
|
|
});
|
|
|
|
it('extracts zero params from a no-arg Kotlin function', () => {
|
|
parser.setLanguage(Kotlin);
|
|
const code = `object ZeroArg {
|
|
fun writeAudit(): String {
|
|
return "zero"
|
|
}
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const objectNode = tree.rootNode.child(0)!;
|
|
const classBody = objectNode.namedChild(1)!;
|
|
const functionNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(functionNode);
|
|
expect(sig.parameterCount).toBe(0);
|
|
});
|
|
|
|
it('extracts parameterTypes for Kotlin overloaded functions', () => {
|
|
parser.setLanguage(Kotlin);
|
|
const code = `class Svc {
|
|
fun lookup(id: Int): User? { return null }
|
|
fun lookup(name: String): User? { return null }
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classBody = tree.rootNode.child(0)!.namedChild(1)!;
|
|
|
|
const sig0 = extractMethodSignature(classBody.namedChild(0)!);
|
|
expect(sig0.parameterCount).toBe(1);
|
|
expect(sig0.parameterTypes).toEqual(['Int']);
|
|
|
|
const sig1 = extractMethodSignature(classBody.namedChild(1)!);
|
|
expect(sig1.parameterCount).toBe(1);
|
|
expect(sig1.parameterTypes).toEqual(['String']);
|
|
});
|
|
});
|
|
|
|
describe('C++', () => {
|
|
it('extracts params from a nested C++ declarator', () => {
|
|
parser.setLanguage(CPP);
|
|
const code = `inline const char* write_audit(const char* message) {
|
|
return message;
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const functionNode = tree.rootNode.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(functionNode);
|
|
expect(sig.parameterCount).toBe(1);
|
|
});
|
|
|
|
it('extracts zero params from a no-arg C++ function', () => {
|
|
parser.setLanguage(CPP);
|
|
const code = `inline const char* write_audit() {
|
|
return "zero";
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const functionNode = tree.rootNode.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(functionNode);
|
|
expect(sig.parameterCount).toBe(0);
|
|
});
|
|
|
|
it('extracts parameterTypes for C++ overloaded functions', () => {
|
|
parser.setLanguage(CPP);
|
|
const code = `User* lookup(int id) { return nullptr; }
|
|
User* lookup(string name) { return nullptr; }`;
|
|
const tree = parser.parse(code);
|
|
|
|
const sig0 = extractMethodSignature(tree.rootNode.namedChild(0)!);
|
|
expect(sig0.parameterCount).toBe(1);
|
|
expect(sig0.parameterTypes).toEqual(['int']);
|
|
|
|
const sig1 = extractMethodSignature(tree.rootNode.namedChild(1)!);
|
|
expect(sig1.parameterCount).toBe(1);
|
|
expect(sig1.parameterTypes).toEqual(['string']);
|
|
});
|
|
});
|
|
|
|
describe('C#', () => {
|
|
it('extracts params from a C# method', () => {
|
|
parser.setLanguage(CSharp);
|
|
const code = `class Foo {
|
|
public bool Check(string name, int count) { return true; }
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBe(2);
|
|
});
|
|
|
|
it('extracts parameterTypes for C# overloaded methods', () => {
|
|
parser.setLanguage(CSharp);
|
|
const code = `class Svc {
|
|
public User Lookup(int id) { return null; }
|
|
public User Lookup(string name) { return null; }
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classBody = tree.rootNode.child(0)!.childForFieldName('body')!;
|
|
|
|
const sig0 = extractMethodSignature(classBody.namedChild(0)!);
|
|
expect(sig0.parameterCount).toBe(1);
|
|
expect(sig0.parameterTypes).toEqual(['int']);
|
|
|
|
const sig1 = extractMethodSignature(classBody.namedChild(1)!);
|
|
expect(sig1.parameterCount).toBe(1);
|
|
expect(sig1.parameterTypes).toEqual(['string']);
|
|
});
|
|
|
|
it('handles C# method with no params', () => {
|
|
parser.setLanguage(CSharp);
|
|
const code = `class Foo {
|
|
public void Execute() {}
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBe(0);
|
|
});
|
|
|
|
it('extracts return type from C# method', () => {
|
|
parser.setLanguage(CSharp);
|
|
const code = `class Svc {
|
|
public User GetUser(string name) { return null; }
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.returnType).toBe('User');
|
|
});
|
|
});
|
|
|
|
describe('Go', () => {
|
|
it('extracts params and single return type', () => {
|
|
parser.setLanguage(Go);
|
|
const code = `package main
|
|
func add(a int, b int) int { return a + b }`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.namedChildren.find((c) => c.type === 'function_declaration')!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.parameterCount).toBe(2);
|
|
expect(sig.returnType).toBe('int');
|
|
});
|
|
|
|
it('extracts multi-return type', () => {
|
|
parser.setLanguage(Go);
|
|
const code = `package main
|
|
func parse(s string) (string, error) { return s, nil }`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.namedChildren.find((c) => c.type === 'function_declaration')!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.parameterCount).toBe(1);
|
|
expect(sig.returnType).toBe('string');
|
|
});
|
|
|
|
it('handles no return type', () => {
|
|
parser.setLanguage(Go);
|
|
const code = `package main
|
|
func doSomething(x int) { }`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.namedChildren.find((c) => c.type === 'function_declaration')!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.parameterCount).toBe(1);
|
|
expect(sig.returnType).toBeUndefined();
|
|
});
|
|
|
|
it('marks variadic function with undefined parameterCount', () => {
|
|
parser.setLanguage(Go);
|
|
const code = `package main
|
|
func log(args ...string) int { return 0 }`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.namedChildren.find((c) => c.type === 'function_declaration')!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.parameterCount).toBeUndefined();
|
|
expect(sig.returnType).toBe('int');
|
|
});
|
|
});
|
|
|
|
describe('Rust', () => {
|
|
it('extracts return type from function', () => {
|
|
parser.setLanguage(Rust);
|
|
const code = `fn add(a: i32, b: i32) -> i32 { a + b }`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.parameterCount).toBe(2);
|
|
expect(sig.returnType).toBe('i32');
|
|
});
|
|
});
|
|
|
|
describe('C++ return types', () => {
|
|
it('extracts primitive return type', () => {
|
|
parser.setLanguage(CPP);
|
|
const code = `int add(int a, int b) { return a + b; }`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.parameterCount).toBe(2);
|
|
expect(sig.returnType).toBe('int');
|
|
});
|
|
|
|
it('extracts qualified return type', () => {
|
|
parser.setLanguage(CPP);
|
|
const code = `std::string getName() { return ""; }`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.parameterCount).toBe(0);
|
|
expect(sig.returnType).toBe('std::string');
|
|
});
|
|
|
|
it('returns undefined returnType for void', () => {
|
|
parser.setLanguage(CPP);
|
|
const code = `void doNothing() { }`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.returnType).toBeUndefined();
|
|
});
|
|
|
|
it('marks variadic function with undefined parameterCount', () => {
|
|
parser.setLanguage(CPP);
|
|
const code = `int printf(const char* fmt, ...) { return 0; }`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.parameterCount).toBeUndefined();
|
|
expect(sig.returnType).toBe('int');
|
|
});
|
|
});
|
|
|
|
describe('variadic params', () => {
|
|
it('Java: marks varargs with undefined parameterCount', () => {
|
|
parser.setLanguage(Java);
|
|
const code = `class Foo {
|
|
public void log(String fmt, Object... args) {}
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBeUndefined();
|
|
});
|
|
|
|
it('Python: marks *args with undefined parameterCount', () => {
|
|
parser.setLanguage(Python);
|
|
const code = `class Foo:
|
|
def log(self, fmt, *args):
|
|
pass`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBeUndefined();
|
|
});
|
|
|
|
it('Python: marks **kwargs with undefined parameterCount', () => {
|
|
parser.setLanguage(Python);
|
|
const code = `class Foo:
|
|
def config(self, **kwargs):
|
|
pass`;
|
|
const tree = parser.parse(code);
|
|
const classNode = tree.rootNode.child(0)!;
|
|
const classBody = classNode.childForFieldName('body')!;
|
|
const methodNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(methodNode);
|
|
expect(sig.parameterCount).toBeUndefined();
|
|
});
|
|
|
|
it('TypeScript: marks rest params with undefined parameterCount', () => {
|
|
parser.setLanguage(TypeScript.typescript);
|
|
const code = `function logEntry(...messages: string[]): void {}`;
|
|
const tree = parser.parse(code);
|
|
const funcNode = tree.rootNode.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(funcNode);
|
|
expect(sig.parameterCount).toBeUndefined();
|
|
});
|
|
|
|
it('Kotlin: marks vararg with undefined parameterCount', () => {
|
|
parser.setLanguage(Kotlin);
|
|
const code = `object Foo {
|
|
fun log(vararg args: String) {}
|
|
}`;
|
|
const tree = parser.parse(code);
|
|
const objectNode = tree.rootNode.child(0)!;
|
|
const classBody = objectNode.namedChild(1)!;
|
|
const functionNode = classBody.namedChild(0)!;
|
|
|
|
const sig = extractMethodSignature(functionNode);
|
|
expect(sig.parameterCount).toBeUndefined();
|
|
});
|
|
});
|
|
});
|