mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-28 06:22:22 +00:00
* fix(ingestion): re-point Go struct member edges when struct is declared in a sibling file (#3558) findEnclosingClassInfo mints a Go method's owner id from the file containing the method (Struct:<methodFile>:<Type>), while the Struct node is keyed by the file declaring the struct (parse-worker uses declFile). Same package, different file => the HAS_METHOD edge's sourceId points at a node that does not exist and the edge is silently dropped at COPY into LadybugDB. Add reconcileGoStructOwnerEdges, run at the end of the parse phase after ALL worker chunks are merged so every package's structs are visible. It rewrites only edges that would otherwise dangle, and only when exactly one Struct node shares the dangling id's package directory and simple name; 0 or >=2 candidates keep the old drop behavior (a wrong owner is worse than a dropped edge). Index build is lazy: dangling Struct sources are rare (struct fields never dangle — they live in the struct's own file), so the common non-Go or fully-same-file case pays two per-type edge scans and no whole-node pass. Regression test: test/integration/go-cross-file-struct-owner.test.ts (fails-before: 1 HAS_METHOD edge; passes-after: 3). * fix(ingestion): scope Go struct-owner reconcile by language and package Address PR #3559 review findings: - Scope the pass to Go: dangling HAS_METHOD/HAS_PROPERTY owner ids whose Struct:<file>:<name> file is not Go source (Rust, Swift, ...) are never re-pointed, and only Struct nodes stamped language 'go' (the marker the worker sets on every declared symbol) are candidates. Negative test pins a Rust-shaped dangling edge staying untouched beside a same-name Go struct. - Key candidate structs by (dir, declared package clause, name) instead of (dir, name): one directory can carry 'package pkg' and external-test 'package pkg_test' files declaring the same type name, which the old key saw as ambiguous and dropped fail-closed. Clauses are read via the shared inferGoPackageName derivation with a call-scoped cache. Integration test covers the 3-file layout (pkg decl, pkg_test decl, pkg_test methods file) and asserts the edge lands on the pkg_test.Widget node. - Keep the direct call in parse-impl with a TODO: the pass needs a graph-level post-merge hook, which no LanguageProvider/ScopeResolver hook provides today. * refactor(ingestion): remove Go graph repair from shared parse phase * fix(ingestion): resolve Go ownership during ingestion (language-provider.ts) * Add resolveDetachedOwnerName function to Go config * Refactor method owner population logic in Go * Refactor receiver binding to handle unnamed receivers * Enhance route handler method resolution logic Refactor route handler to improve ownership resolution and method indexing. * Add emitPostResolutionEdges to scope resolver * fix(ingestion): resolve Go ownership during ingestion (ownership.ts) * fix(ingestion): resolve Go ownership during ingestion (ast-helpers.ts) * Add resolveDetachedOwnerName to provider methods * Update SCHEMA_BUMP to 141 Increment schema version to 141 for new features and fixes. * fix(ingestion): resolve Go ownership during ingestion (decl.go) * fix(ingestion): resolve Go ownership during ingestion (external_decl_test.go) * fix(ingestion): resolve Go ownership during ingestion (external_methods_test.go) * fix(ingestion): resolve Go ownership during ingestion (foreign.rs) * fix(ingestion): resolve Go ownership during ingestion (go.mod) * fix(ingestion): resolve Go ownership during ingestion (local.go) * fix(ingestion): resolve Go ownership during ingestion (methods.go) * fix(ingestion): resolve Go ownership during ingestion (go-cross-file-struct-owner.test.ts) * Refactor comments and method registration in tests Updated comments for clarity and modified method registration to include qualified names and ownership. * fix(ingestion): resolve Go ownership during ingestion (has-method.test.ts) * Update SCHEMA_BUMP to 141 in tests * refactor(ingestion): remove obsolete Go struct owner reconciliation * fix(go): model build constraints for method ownership * test(go): cover build constraint grammar and platform variants * fix(go): resolve method owners across compatible build variants * test(go): verify variant ownership through ingestion pipeline This test verifies that build-variant owners are consistent across different workers, the semantic registry, and the graph. It sets up various scenarios to ensure that ownership is correctly resolved and that there are no dangling edges in the ownership graph. * fix(go): reject impossible known constraints when peer is unknown Address PR #3559 review feedback. Regression coverage follows in this update. * feat(ingestion): allow providers to group incremental resolution files Add the optional language-neutral hook used by the Go ownership writeback fix for PR #3559. * fix(go): group sibling files for incremental ownership resolution Use the existing Go package-directory key for the provider hook. * fix(incremental): collect sibling resolution groups for changed paths Keep group keys local to each provider and include current siblings of deleted files. * fix(analyze): persist ownership changes in unchanged Go siblings Expand changed and deleted resolution groups before importer traversal. Use the same effective file set for deletion and writeback so package-wide ownership changes reach the database. * test(incremental): cover provider-local resolution groups Cover changed, added, deleted and unrelated paths for the new sibling expansion helper. * test(go): compare incremental ownership writes with cold rebuilds Reproduce ownership changes after deleting and restoring a Windows declaration while the shared method file stays unchanged. Verify incremental writes and persisted ownership parity with forced rebuilds. * test(go): exclude impossible owners with unknown method constraints Use real parsing to cover impossible owner edges and valid owners hidden by impossible competitors. * test(go): pin filename rules and unknown constraint overlap Document Go first-dot filename behavior and verify impossible known constraints in both operand orders. * test(go): register incremental ownership coverage across platforms Include the new database-backed Go ownership suite in the cross-platform test list. * test(go): schedule incremental ownership suite with native database tests Complete PR #3559 review fixes. Local validation: 47 focused tests across five suites passed; build and source/test type checks passed. The full suite was interrupted without a result. * refactor(ingestion): centralize worker owner hook forwarding Pass the language provider into the private cached owner lookup instead of repeating four owner hooks at five call sites. Keep qualified-name selection explicit and preserve the exported helper signature. Validation: CLI/shared build and test typecheck pass; repository lint has no errors; 591 tests pass with one skipped across 13 ownership and worker suites. Web typecheck is blocked by uninstalled web dependencies. Graph checks are unavailable because this checkout has no registered index. * test(rename): add Go cross-file fixture module Part of the cross-file method ownership regression for PR #3559. The completed fixture uses real indexing, public rename preview/apply, warm cache replay and post-rename reindexing. All 14 Go rename integration cases pass locally. * test(rename): add cross-file fixture before/types.go * test(rename): add cross-file fixture before/methods.go * test(rename): add cross-file fixture before/callers.go * test(rename): add cross-file fixture after/go.mod * test(rename): add cross-file fixture after/types.go * test(rename): add cross-file fixture after/methods.go * test(rename): add cross-file fixture after/callers.go * Add test case for renaming pointer receiver method Complete the real persisted-index regression fixture for PR #3559. The type, pointer receiver method and callers are in separate Go files. Public rename preview/apply must make exactly four edits across the structural interface family while preserving an unrelated same-name method, comments and strings. The fixture checks six exact ownership, interface and call edges before rename, after warm parse-cache replay and after reindexing the edited sources. All 14 Go rename integration cases pass. Formatting and the CLI typecheck pass. Graph impact/detect-changes could not run because this checkout has no registered index. --------- Co-authored-by: System administrator <root@nixos-infra.home> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
534 lines
16 KiB
TypeScript
534 lines
16 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import Parser from 'tree-sitter';
|
|
import TypeScript from 'tree-sitter-typescript';
|
|
import Python from 'tree-sitter-python';
|
|
import Java from 'tree-sitter-java';
|
|
import CPP from 'tree-sitter-cpp';
|
|
import Rust from 'tree-sitter-rust';
|
|
import CSharp from 'tree-sitter-c-sharp/bindings/node/index.js';
|
|
import Go from 'tree-sitter-go';
|
|
import {
|
|
findEnclosingClassId,
|
|
findEnclosingClassInfo,
|
|
CLASS_CONTAINER_TYPES,
|
|
CONTAINER_TYPE_TO_LABEL,
|
|
} from '../../src/core/ingestion/utils/ast-helpers.js';
|
|
import { goProvider } from '../../src/core/ingestion/languages/go.js';
|
|
|
|
function parseCode(language: any, code: string): Parser.Tree {
|
|
const parser = new Parser();
|
|
parser.setLanguage(language);
|
|
return parser.parse(code);
|
|
}
|
|
|
|
/** Find the first descendant node matching a predicate (BFS). */
|
|
function findNode(
|
|
root: Parser.SyntaxNode,
|
|
predicate: (n: Parser.SyntaxNode) => boolean,
|
|
): Parser.SyntaxNode | null {
|
|
const queue: Parser.SyntaxNode[] = [root];
|
|
while (queue.length > 0) {
|
|
const node = queue.shift()!;
|
|
if (predicate(node)) return node;
|
|
for (let i = 0; i < node.childCount; i++) {
|
|
queue.push(node.child(i)!);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
describe('CLASS_CONTAINER_TYPES', () => {
|
|
it('contains expected class-like AST node types', () => {
|
|
expect(CLASS_CONTAINER_TYPES.has('class_declaration')).toBe(true);
|
|
expect(CLASS_CONTAINER_TYPES.has('interface_declaration')).toBe(true);
|
|
expect(CLASS_CONTAINER_TYPES.has('struct_declaration')).toBe(true);
|
|
expect(CLASS_CONTAINER_TYPES.has('impl_item')).toBe(true);
|
|
expect(CLASS_CONTAINER_TYPES.has('class_specifier')).toBe(true);
|
|
expect(CLASS_CONTAINER_TYPES.has('class_definition')).toBe(true);
|
|
});
|
|
|
|
it('does not contain function types', () => {
|
|
expect(CLASS_CONTAINER_TYPES.has('function_declaration')).toBe(false);
|
|
expect(CLASS_CONTAINER_TYPES.has('function_definition')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('CONTAINER_TYPE_TO_LABEL', () => {
|
|
it('maps class-like types to correct labels', () => {
|
|
expect(CONTAINER_TYPE_TO_LABEL['class_declaration']).toBe('Class');
|
|
expect(CONTAINER_TYPE_TO_LABEL['interface_declaration']).toBe('Interface');
|
|
expect(CONTAINER_TYPE_TO_LABEL['struct_declaration']).toBe('Struct');
|
|
expect(CONTAINER_TYPE_TO_LABEL['impl_item']).toBe('Impl');
|
|
expect(CONTAINER_TYPE_TO_LABEL['trait_item']).toBe('Trait');
|
|
expect(CONTAINER_TYPE_TO_LABEL['record_declaration']).toBe('Record');
|
|
expect(CONTAINER_TYPE_TO_LABEL['protocol_declaration']).toBe('Interface');
|
|
});
|
|
});
|
|
|
|
describe('findEnclosingClassId', () => {
|
|
const filePath = 'test/example.ts';
|
|
|
|
describe('TypeScript', () => {
|
|
it('finds enclosing class for a method', () => {
|
|
const tree = parseCode(
|
|
TypeScript.typescript,
|
|
`
|
|
class MyService {
|
|
getData() {
|
|
return 42;
|
|
}
|
|
}
|
|
`,
|
|
);
|
|
// Find the method_definition node for getData
|
|
const methodNode = findNode(tree.rootNode, (n) => n.type === 'method_definition');
|
|
expect(methodNode).not.toBeNull();
|
|
|
|
// Find the identifier 'getData' inside the method
|
|
const nameNode = findNode(
|
|
methodNode!,
|
|
(n) => n.type === 'property_identifier' && n.text === 'getData',
|
|
);
|
|
expect(nameNode).not.toBeNull();
|
|
|
|
const result = findEnclosingClassId(nameNode!, filePath);
|
|
expect(result).toBe('Class:test/example.ts:MyService');
|
|
});
|
|
|
|
it('finds enclosing interface for a method signature', () => {
|
|
const tree = parseCode(
|
|
TypeScript.typescript,
|
|
`
|
|
interface MyInterface {
|
|
doSomething(): void;
|
|
}
|
|
`,
|
|
);
|
|
// In TS, interface methods are method_signature nodes — find method name
|
|
const nameNode = findNode(
|
|
tree.rootNode,
|
|
(n) => n.type === 'property_identifier' && n.text === 'doSomething',
|
|
);
|
|
if (nameNode) {
|
|
const result = findEnclosingClassId(nameNode, filePath);
|
|
expect(result).toBe('Interface:test/example.ts:MyInterface');
|
|
}
|
|
});
|
|
|
|
it('returns null for a top-level function', () => {
|
|
const tree = parseCode(
|
|
TypeScript.typescript,
|
|
`
|
|
function topLevel() {
|
|
return 1;
|
|
}
|
|
`,
|
|
);
|
|
const nameNode = findNode(
|
|
tree.rootNode,
|
|
(n) => n.type === 'identifier' && n.text === 'topLevel',
|
|
);
|
|
expect(nameNode).not.toBeNull();
|
|
|
|
const result = findEnclosingClassId(nameNode!, filePath);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('returns null when node has no parent', () => {
|
|
// Root node's parent is null
|
|
const tree = parseCode(TypeScript.typescript, 'const x = 1;');
|
|
const result = findEnclosingClassId(tree.rootNode, filePath);
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('Python', () => {
|
|
it('finds enclosing class for a method', () => {
|
|
const tree = parseCode(
|
|
Python,
|
|
`
|
|
class Calculator:
|
|
def add(self, a, b):
|
|
return a + b
|
|
`,
|
|
);
|
|
const methodNode = findNode(tree.rootNode, (n) => n.type === 'function_definition');
|
|
expect(methodNode).not.toBeNull();
|
|
|
|
const nameNode = findNode(methodNode!, (n) => n.type === 'identifier' && n.text === 'add');
|
|
expect(nameNode).not.toBeNull();
|
|
|
|
const result = findEnclosingClassId(nameNode!, 'test/calc.py');
|
|
expect(result).toBe('Class:test/calc.py:Calculator');
|
|
});
|
|
});
|
|
|
|
describe('Java', () => {
|
|
it('finds enclosing class for a method', () => {
|
|
const tree = parseCode(
|
|
Java,
|
|
`
|
|
class UserService {
|
|
public void save(User user) {}
|
|
}
|
|
`,
|
|
);
|
|
const methodNode = findNode(tree.rootNode, (n) => n.type === 'method_declaration');
|
|
expect(methodNode).not.toBeNull();
|
|
|
|
const nameNode = findNode(methodNode!, (n) => n.type === 'identifier' && n.text === 'save');
|
|
expect(nameNode).not.toBeNull();
|
|
|
|
const result = findEnclosingClassId(nameNode!, 'test/UserService.java');
|
|
expect(result).toBe('Class:test/UserService.java:UserService');
|
|
});
|
|
|
|
it('finds enclosing interface for a method', () => {
|
|
const tree = parseCode(
|
|
Java,
|
|
`
|
|
interface Repository {
|
|
void findById(int id);
|
|
}
|
|
`,
|
|
);
|
|
const methodNode = findNode(tree.rootNode, (n) => n.type === 'method_declaration');
|
|
expect(methodNode).not.toBeNull();
|
|
|
|
const nameNode = findNode(
|
|
methodNode!,
|
|
(n) => n.type === 'identifier' && n.text === 'findById',
|
|
);
|
|
expect(nameNode).not.toBeNull();
|
|
|
|
const result = findEnclosingClassId(nameNode!, 'test/Repository.java');
|
|
expect(result).toBe('Interface:test/Repository.java:Repository');
|
|
});
|
|
});
|
|
|
|
describe('C++', () => {
|
|
it('finds enclosing class_specifier for a method', () => {
|
|
const tree = parseCode(
|
|
CPP,
|
|
`
|
|
class Vector {
|
|
public:
|
|
int size() { return _size; }
|
|
private:
|
|
int _size;
|
|
};
|
|
`,
|
|
);
|
|
// In C++ tree-sitter, the class is a class_specifier
|
|
const classNode = findNode(tree.rootNode, (n) => n.type === 'class_specifier');
|
|
expect(classNode).not.toBeNull();
|
|
|
|
// Find a function_definition inside the class
|
|
const funcDef = findNode(classNode!, (n) => n.type === 'function_definition');
|
|
expect(funcDef).not.toBeNull();
|
|
|
|
const nameNode = findNode(funcDef!, (n) => n.type === 'identifier' && n.text === 'size');
|
|
if (nameNode) {
|
|
const result = findEnclosingClassId(nameNode, 'test/vector.h');
|
|
expect(result).toBe('Class:test/vector.h:Vector');
|
|
}
|
|
});
|
|
|
|
it('finds enclosing struct_specifier for a method', () => {
|
|
const tree = parseCode(
|
|
CPP,
|
|
`
|
|
struct Point {
|
|
double distance() { return 0; }
|
|
};
|
|
`,
|
|
);
|
|
const funcDef = findNode(tree.rootNode, (n) => n.type === 'function_definition');
|
|
if (funcDef) {
|
|
const nameNode = findNode(funcDef, (n) => n.type === 'identifier' && n.text === 'distance');
|
|
if (nameNode) {
|
|
const result = findEnclosingClassId(nameNode, 'test/point.h');
|
|
expect(result).toBe('Struct:test/point.h:Point');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Rust', () => {
|
|
it('finds enclosing impl_item for a method', () => {
|
|
const tree = parseCode(
|
|
Rust,
|
|
`
|
|
struct Counter {
|
|
count: u32,
|
|
}
|
|
impl Counter {
|
|
fn increment(&mut self) {
|
|
self.count += 1;
|
|
}
|
|
}
|
|
`,
|
|
);
|
|
const implNode = findNode(tree.rootNode, (n) => n.type === 'impl_item');
|
|
expect(implNode).not.toBeNull();
|
|
|
|
const funcItem = findNode(implNode!, (n) => n.type === 'function_item');
|
|
expect(funcItem).not.toBeNull();
|
|
|
|
const nameNode = findNode(
|
|
funcItem!,
|
|
(n) => n.type === 'identifier' && n.text === 'increment',
|
|
);
|
|
expect(nameNode).not.toBeNull();
|
|
|
|
const result = findEnclosingClassId(nameNode!, 'test/counter.rs');
|
|
expect(result).toBe('Impl:test/counter.rs:Counter');
|
|
});
|
|
|
|
it('picks struct name (not trait name) for impl Trait for Struct', () => {
|
|
const tree = parseCode(
|
|
Rust,
|
|
`
|
|
struct MyStruct {
|
|
value: i32,
|
|
}
|
|
|
|
trait MyTrait {
|
|
fn do_something(&self);
|
|
}
|
|
|
|
impl MyTrait for MyStruct {
|
|
fn do_something(&self) {
|
|
println!("{}", self.value);
|
|
}
|
|
}
|
|
`,
|
|
);
|
|
// Find the impl_item that has a `for` keyword (impl Trait for Struct)
|
|
const implNode = findNode(
|
|
tree.rootNode,
|
|
(n) => n.type === 'impl_item' && n.children?.some((c: any) => c.text === 'for'),
|
|
);
|
|
expect(implNode).not.toBeNull();
|
|
|
|
const funcItem = findNode(implNode!, (n) => n.type === 'function_item');
|
|
expect(funcItem).not.toBeNull();
|
|
|
|
const nameNode = findNode(
|
|
funcItem!,
|
|
(n) => n.type === 'identifier' && n.text === 'do_something',
|
|
);
|
|
expect(nameNode).not.toBeNull();
|
|
|
|
const result = findEnclosingClassId(nameNode!, 'test/my_struct.rs');
|
|
// Should resolve to MyStruct (the implementing type), NOT MyTrait.
|
|
// Uses Struct label because trait impls don't create Impl nodes in the graph
|
|
// (the definition.impl query uses !trait filter).
|
|
expect(result).toBe('Struct:test/my_struct.rs:MyStruct');
|
|
});
|
|
|
|
it('still picks struct name for plain impl Struct (no trait)', () => {
|
|
const tree = parseCode(
|
|
Rust,
|
|
`
|
|
struct Counter {
|
|
count: u32,
|
|
}
|
|
impl Counter {
|
|
fn increment(&mut self) {
|
|
self.count += 1;
|
|
}
|
|
}
|
|
`,
|
|
);
|
|
const implNode = findNode(tree.rootNode, (n) => n.type === 'impl_item');
|
|
expect(implNode).not.toBeNull();
|
|
|
|
const funcItem = findNode(implNode!, (n) => n.type === 'function_item');
|
|
expect(funcItem).not.toBeNull();
|
|
|
|
const nameNode = findNode(
|
|
funcItem!,
|
|
(n) => n.type === 'identifier' && n.text === 'increment',
|
|
);
|
|
expect(nameNode).not.toBeNull();
|
|
|
|
const result = findEnclosingClassId(nameNode!, 'test/counter.rs');
|
|
expect(result).toBe('Impl:test/counter.rs:Counter');
|
|
});
|
|
|
|
it('finds enclosing trait_item for a method', () => {
|
|
const tree = parseCode(
|
|
Rust,
|
|
`
|
|
trait Drawable {
|
|
fn draw(&self);
|
|
}
|
|
`,
|
|
);
|
|
const traitNode = findNode(tree.rootNode, (n) => n.type === 'trait_item');
|
|
expect(traitNode).not.toBeNull();
|
|
|
|
const funcItem = findNode(
|
|
traitNode!,
|
|
(n) => n.type === 'function_signature_item' || n.type === 'function_item',
|
|
);
|
|
if (funcItem) {
|
|
const nameNode = findNode(funcItem, (n) => n.type === 'identifier' && n.text === 'draw');
|
|
if (nameNode) {
|
|
const result = findEnclosingClassId(nameNode, 'test/draw.rs');
|
|
expect(result).toBe('Trait:test/draw.rs:Drawable');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('C#', () => {
|
|
it('finds enclosing class for a method', () => {
|
|
const tree = parseCode(
|
|
CSharp,
|
|
`
|
|
class OrderService {
|
|
public void Process() {}
|
|
}
|
|
`,
|
|
);
|
|
const methodNode = findNode(tree.rootNode, (n) => n.type === 'method_declaration');
|
|
expect(methodNode).not.toBeNull();
|
|
|
|
const nameNode = findNode(
|
|
methodNode!,
|
|
(n) => n.type === 'identifier' && n.text === 'Process',
|
|
);
|
|
expect(nameNode).not.toBeNull();
|
|
|
|
const result = findEnclosingClassId(nameNode!, 'test/OrderService.cs');
|
|
expect(result).toBe('Class:test/OrderService.cs:OrderService');
|
|
});
|
|
|
|
it('finds enclosing record for a method', () => {
|
|
const tree = parseCode(
|
|
CSharp,
|
|
`
|
|
record Person {
|
|
public string GetName() { return ""; }
|
|
}
|
|
`,
|
|
);
|
|
const methodNode = findNode(tree.rootNode, (n) => n.type === 'method_declaration');
|
|
if (methodNode) {
|
|
const nameNode = findNode(
|
|
methodNode,
|
|
(n) => n.type === 'identifier' && n.text === 'GetName',
|
|
);
|
|
if (nameNode) {
|
|
const result = findEnclosingClassId(nameNode, 'test/Person.cs');
|
|
expect(result).toBe('Record:test/Person.cs:Person');
|
|
}
|
|
}
|
|
});
|
|
|
|
it('finds enclosing struct for a method', () => {
|
|
const tree = parseCode(
|
|
CSharp,
|
|
`
|
|
struct Vector2 {
|
|
public float Length() { return 0; }
|
|
}
|
|
`,
|
|
);
|
|
const methodNode = findNode(tree.rootNode, (n) => n.type === 'method_declaration');
|
|
if (methodNode) {
|
|
const nameNode = findNode(
|
|
methodNode,
|
|
(n) => n.type === 'identifier' && n.text === 'Length',
|
|
);
|
|
if (nameNode) {
|
|
const result = findEnclosingClassId(nameNode, 'test/Vector2.cs');
|
|
expect(result).toBe('Struct:test/Vector2.cs:Vector2');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Go', () => {
|
|
it('retains the receiver name while deferring its owner ID to semantic resolution', () => {
|
|
const tree = parseCode(
|
|
Go,
|
|
`
|
|
package main
|
|
|
|
type Server struct {
|
|
port int
|
|
}
|
|
|
|
func (s *Server) Start() {}
|
|
`,
|
|
);
|
|
const methodNode = findNode(tree.rootNode, (n) => n.type === 'method_declaration');
|
|
expect(methodNode).not.toBeNull();
|
|
|
|
const nameNode = findNode(
|
|
methodNode!,
|
|
(n) => n.type === 'field_identifier' && n.text === 'Start',
|
|
);
|
|
expect(nameNode).not.toBeNull();
|
|
expect(findEnclosingClassId(nameNode!, 'test/server.go')).toBeNull();
|
|
expect(
|
|
findEnclosingClassInfo(
|
|
nameNode!,
|
|
'test/server.go',
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
goProvider.resolveDetachedOwnerName,
|
|
),
|
|
).toEqual({ className: 'Server' });
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('handles nested classes — returns innermost enclosing class', () => {
|
|
const tree = parseCode(
|
|
TypeScript.typescript,
|
|
`
|
|
class Outer {
|
|
inner = class Inner {
|
|
doWork() {}
|
|
}
|
|
}
|
|
`,
|
|
);
|
|
const methodNode = findNode(tree.rootNode, (n) => n.type === 'method_definition');
|
|
if (methodNode) {
|
|
const nameNode = findNode(
|
|
methodNode,
|
|
(n) => n.type === 'property_identifier' && n.text === 'doWork',
|
|
);
|
|
if (nameNode) {
|
|
const result = findEnclosingClassId(nameNode, filePath);
|
|
// Should find the innermost class (Inner, which is a class node)
|
|
expect(result).not.toBeNull();
|
|
// The result should reference the inner class, not the outer
|
|
}
|
|
}
|
|
});
|
|
|
|
it('returns null for a node without parent', () => {
|
|
const root = parseCode(TypeScript.typescript, 'const value = 1;').rootNode;
|
|
expect(root.parent).toBeNull();
|
|
const result = findEnclosingClassId(root, filePath);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('skips containers without a name node', () => {
|
|
const tree = parseCode(TypeScript.typescript, 'const Anonymous = class { method() {} };');
|
|
const method = findNode(tree.rootNode, (node) => node.type === 'method_definition');
|
|
expect(method).not.toBeNull();
|
|
const result = findEnclosingClassId(method!, filePath);
|
|
// The class has no name, so should return null
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
});
|