Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions __tests__/evaluation/results/precision-gin-dcaa429-45e6d66e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"timestamp": "2026-10-29T21:30:48.620Z",
"corpus": "gin",
"repo": "https://github.com/gin-gonic/gin.git",
"commit": "dcaa429",
"codegraphSha": "45e6d66e",
"summary": {
"absentCases": 1,
"absentHeld": 1,
"presentCases": 2,
"presentHeld": 2,
"unjudgeable": 0
},
"resolvedBy": {
"(none)": 3593,
"exact-match": 2568,
"instance-method": 1814,
"framework": 228,
"file-path": 104,
"import": 96,
"function-ref": 14
},
"edges": 8417,
"results": [
{
"caseId": "gin-declared-factory-receiver",
"expect": "present",
"pass": true,
"found": [
{
"from": "function:285dcfdab6ec00ce5d1c87eddc233f0d",
"to": "method:08a6ca18de8a7f08f33790ca286e0a20",
"resolvedBy": "instance-method"
}
],
"missingEndpoints": []
},
{
"caseId": "gin-composite-receiver-json-control",
"expect": "present",
"pass": true,
"found": [
{
"from": "function:e6a071cb9b3ce2eeba6fb82f941cef3d",
"to": "method:3ab506d0531aac0489e0c51fcc2128aa",
"resolvedBy": "instance-method"
}
],
"missingEndpoints": []
},
{
"caseId": "gin-composite-receiver-not-bson",
"expect": "absent",
"pass": true,
"found": [],
"missingEndpoints": []
}
]
}
24 changes: 24 additions & 0 deletions __tests__/evaluation/results/precision-jq-9d241e2-9888e4a7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"timestamp": "2026-10-29T21:12:33.608Z",
"corpus": "jq",
"repo": "https://github.com/jqlang/jq.git",
"commit": "9d241e2",
"codegraphSha": "9888e4a7",
"summary": {
"absentCases": 0,
"absentHeld": 0,
"presentCases": 0,
"presentHeld": 0,
"unjudgeable": 0
},
"resolvedBy": {
"exact-match": 5308,
"(none)": 2272,
"import": 115,
"function-ref": 87,
"file-path": 2,
"instance-method": 1
},
"edges": 7785,
"results": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"timestamp": "2026-10-29T21:12:32.224Z",
"corpus": "svelte",
"repo": "https://github.com/sveltejs/svelte.git",
"commit": "5895c63",
"codegraphSha": "9888e4a7",
"summary": {
"absentCases": 1,
"absentHeld": 1,
"presentCases": 0,
"presentHeld": 0,
"unjudgeable": 0
},
"resolvedBy": {
"(none)": 30920,
"import": 22673,
"exact-match": 9038,
"qualified-name": 1874,
"instance-method": 814,
"framework": 220,
"function-ref": 195,
"file-path": 58,
"fuzzy": 13
},
"edges": 65805,
"results": [
{
"caseId": "svelte-bare-import-bundle",
"expect": "absent",
"pass": true,
"found": [],
"missingEndpoints": []
}
]
}
95 changes: 95 additions & 0 deletions __tests__/evaluation/results/precision-vite-8492422-9888e4a7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"timestamp": "2026-10-29T21:12:20.882Z",
"corpus": "vite",
"repo": "https://github.com/vitejs/vite.git",
"commit": "8492422",
"codegraphSha": "9888e4a7",
"summary": {
"absentCases": 6,
"absentHeld": 6,
"presentCases": 2,
"presentHeld": 2,
"unjudgeable": 0
},
"resolvedBy": {
"(none)": 13392,
"import": 11144,
"exact-match": 3386,
"qualified-name": 1009,
"instance-method": 547,
"file-path": 197,
"function-ref": 163,
"framework": 1
},
"edges": 29839,
"results": [
{
"caseId": "vite-factory-receiver-control",
"expect": "present",
"pass": true,
"found": [
{
"from": "function:d321dd128d90578bc69867835ec73ef8",
"to": "method:db73e5f5d9c7af61d0f22c5b9971ced3",
"resolvedBy": "instance-method"
}
],
"missingEndpoints": []
},
{
"caseId": "vite-external-receiver-not-project-method",
"expect": "absent",
"pass": true,
"found": [],
"missingEndpoints": []
},
{
"caseId": "vite-bare-import-self-edge",
"expect": "absent",
"pass": true,
"found": [],
"missingEndpoints": []
},
{
"caseId": "vite-bare-import-getEnv",
"expect": "absent",
"pass": true,
"found": [],
"missingEndpoints": []
},
{
"caseId": "vite-fuzzy-nested-resolveConfig-getEnv",
"expect": "absent",
"pass": true,
"found": [],
"missingEndpoints": []
},
{
"caseId": "vite-fuzzy-nested-build-scan",
"expect": "absent",
"pass": true,
"found": [],
"missingEndpoints": []
},
{
"caseId": "vite-sealed-module-defineConfig",
"expect": "absent",
"pass": true,
"found": [],
"missingEndpoints": []
},
{
"caseId": "vite-control-relative-import",
"expect": "present",
"pass": true,
"found": [
{
"from": "file:packages/vite/src/node/config.ts",
"to": "function:0c2f36c32c5742aee412978220fd5b23",
"resolvedBy": "import"
}
],
"missingEndpoints": []
}
]
}
17 changes: 7 additions & 10 deletions __tests__/explore-long-query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ const DEF = ['class', 'interface', 'struct', 'union', 'trait', 'protocol', 'enum
const CALL = ['function', 'method', 'component'];
const SEED = ['function', 'method', 'component', 'variable', 'constant'];

/** The lookup as it was before the index: a LIKE scan, ties left to the plan. */
/** The lookup as a LIKE scan: the rows the in-memory index must reproduce. */
function likeScan(db: SqliteDatabase, substring: string, kinds: string[], excludePrefix: boolean, limit: number): string[] {
let sql = 'SELECT nodes.*, 1.0 as score FROM nodes WHERE name LIKE ?';
const params: (string | number)[] = [`%${substring}%`];
if (excludePrefix) { sql += ' AND name NOT LIKE ?'; params.push(`${substring}%`); }
sql += ` AND kind IN (${kinds.map(() => '?').join(',')}) ORDER BY length(name) ASC LIMIT ?`;
sql += ` AND kind IN (${kinds.map(() => '?').join(',')}) ORDER BY length(name) ASC, kind ASC, nodes.rowid ASC LIMIT ?`;
return (db.prepare(sql).all(...params, ...kinds, limit) as Array<{ id: string }>).map((row) => row.id);
}

Expand Down Expand Up @@ -108,7 +108,7 @@ describe('name-substring lookups give the rows a LIKE scan gives (#2620)', () =>
return cg;
}

function expectSameRows(cg: CodeGraph, plan: 'rowid' | 'index'): void {
function expectSameRows(cg: CodeGraph): void {
const q = queriesOf(cg);
const db = rawDb(cg);
let compared = 0;
Expand All @@ -125,13 +125,10 @@ describe('name-substring lookups give the rows a LIKE scan gives (#2620)', () =>
}
}
expect(compared).toBeGreaterThan(900);
// The answers came from the index, under the plan SQLite chose.
const memo = (q as unknown as { readMemos: Map<string, { value: { plans: Map<string, string | null> } | null }> })
// The answers came from the index.
const memo = (q as unknown as { readMemos: Map<string, { value: unknown }> })
.readMemos.get('nameSubstringIndex');
expect(memo?.value).toBeTruthy();
const plans = [...memo!.value!.plans.values()];
expect(plans).toContain(plan);
expect(plans).not.toContain(null);
}

it('matches when SQLite scans the table (a small index)', async () => {
Expand All @@ -140,7 +137,7 @@ describe('name-substring lookups give the rows a LIKE scan gives (#2620)', () =>
`EXPLAIN QUERY PLAN SELECT * FROM nodes WHERE name LIKE '%x%' AND kind IN (${DEF.map(() => '?').join(',')}) ORDER BY length(name) LIMIT 200`,
).all(...DEF) as Array<{ detail: string }>;
expect(plan.map((r) => r.detail)).toContain('SCAN nodes');
expectSameRows(cg, 'rowid');
expectSameRows(cg);
});

it('matches when SQLite reads through the kind index (a large index)', async () => {
Expand All @@ -155,7 +152,7 @@ describe('name-substring lookups give the rows a LIKE scan gives (#2620)', () =>
`EXPLAIN QUERY PLAN SELECT * FROM nodes WHERE name LIKE '%x%' AND kind IN (${DEF.map(() => '?').join(',')}) ORDER BY length(name) LIMIT 200`,
).all(...DEF) as Array<{ detail: string }>;
expect(plan.map((r) => r.detail).some((d) => d.startsWith('SEARCH nodes USING INDEX idx_nodes_kind'))).toBe(true);
expectSameRows(cg, 'index');
expectSameRows(cg);
});

it('asks SQLite inside a transaction, and for a lookup without a kind filter', async () => {
Expand Down
12 changes: 10 additions & 2 deletions codegraph-kernel/src/resolve/declared_member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl KernelResolver {
let mut hits = line.match_indices(&call).map(|(i, _)| &line[..i]).peekable();
hits.peek().is_some() && hits.all(|before| before.ends_with("this."))
}
fn declaration_head(&mut self, n: &KNode, count: i64) -> String {
pub(super) fn declaration_head(&mut self, n: &KNode, count: i64) -> String {
let Some(lines) = self.read_file(&n.file_path) else { return String::new() };
let from = ((n.start_line - 1).max(0) as usize).min(lines.len());
let to = ((n.start_line + count).max(0) as usize).min(lines.len());
Expand Down Expand Up @@ -56,7 +56,15 @@ impl KernelResolver {
if let Some(m)=re.captures(line) {
let before=&line[..m.get(0).unwrap().start()];
let in_params=*depth==1 && before.matches('(').count()>before.matches(')').count();
if !in_params && !matches!(&m[1], "return"|"new"|"throw"|"var"|"val"|"get"|"set"|"init"|"class"|"interface"|"object"|"static"|"final") {
// A language keyword where a type would be is not a member's
// type — upstream's MEMBER_TYPE_NON_TYPES, verbatim.
if !in_params && !matches!(&m[1],
"return"|"new"|"case"|"throw"|"else"|"in"|"out"|"ref"|"params"|"await"|"yield"|"is"|"as"|"using"
|"var"|"val"|"goto"|"nameof"|"typeof"|"sizeof"|"default"|"when"|"where"|"get"|"set"|"init"
|"class"|"interface"|"enum"|"struct"|"record"|"object"|"namespace"|"package"|"import"|"extends"
|"implements"|"fun"|"static"|"final"|"abstract"|"sealed"|"override"|"virtual"|"delegate"|"event"
// Java's `sealed interface Shape permits Circle, Square` and `void run() throws Failure {`.
|"permits"|"throws") {
found=if m[1].contains('.') {Some(m[1].to_string())} else {self.normalize_inferred_type_name(&m[1])?};break;
}
}
Expand Down
43 changes: 35 additions & 8 deletions codegraph-kernel/src/resolve/go_asserted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ impl KernelResolver {
/// of `name` in scope at `r` binds the value of a type assertion.
pub(super) fn go_asserted_local_type(&mut self, name: &str, r: &ResolveRefIn) -> Option<String> {
if r.language != "go" || name.is_empty() || !name.bytes().all(|b| b == b'_' || b.is_ascii_alphanumeric()) { return None; }
let key = (r.file_path.clone(), name.to_string(), r.line, r.column);
if let Some(hit) = self.go_asserted_type_memo.get(&key) { return hit.clone(); }
let out = self.go_asserted_local_type_uncached(name, r);
self.go_asserted_type_memo.insert(key, out.clone());
out
}

fn go_asserted_local_type_uncached(&mut self, name: &str, r: &ResolveRefIn) -> Option<String> {
let file = self.read_file(&r.file_path)?;
let text = file.text();
// Only a file that asserts a type can bind from one.
Expand Down Expand Up @@ -344,8 +352,13 @@ pub(super) fn local_declaration_in<'a>(scope: TsNode<'a>, name: &str, site: usiz
}

#[cfg_attr(feature = "addon", napi(object))]
#[derive(Clone)]
pub struct GoLocalDeclaration { pub line: i64, pub column: i64, pub declared_type: Option<String> }

/// A memoized `local_decl_at` result: the target identifier's byte range
/// plus its declaration, for callers that re-derive the node.
pub(super) type GoDeclHit = Option<(usize, usize, GoLocalDeclaration)>;

/// Source-only lexical lookup. Synthesis must not attach to the live WAL.
#[cfg_attr(feature = "addon", napi)]
pub struct GoSourceScope {
Expand All @@ -367,28 +380,42 @@ impl GoSourceScope {
}
}

fn local_at(tree: &tree_sitter::Tree, source: &str, name: &str, line: i64, column: i64) -> Option<GoLocalDeclaration> {
let site = super::iteration::descendant_for_position(tree.root_node(), source,
((line - 1).max(0) as usize, column.max(0) as usize));
/// The ancestor walk shared by `local_at`, the resolver's decl memo and
/// `go_named_local_written`: the target identifier node plus its declaration.
pub(super) fn local_decl_at<'a>(source: &str, name: &str, site: TsNode<'a>) -> Option<(TsNode<'a>, GoLocalDeclaration)> {
let mut scope = Some(site);
while let Some(node) = scope {
if let Some((target, ty)) = local_declaration_in(node, name, site.start_byte(), source) {
return Some(GoLocalDeclaration { line: target.start_position().row as i64 + 1,
column: target.start_position().column as i64, declared_type: ty });
return Some((target, GoLocalDeclaration { line: target.start_position().row as i64 + 1,
column: target.start_position().column as i64, declared_type: ty }));
}
if matches!(node.kind(), "function_declaration" | "method_declaration") { break; }
scope = node.parent();
}
None
}

fn local_at(tree: &tree_sitter::Tree, source: &str, name: &str, line: i64, column: i64) -> Option<GoLocalDeclaration> {
let site = super::iteration::descendant_for_position(tree.root_node(), source,
((line - 1).max(0) as usize, column.max(0) as usize));
local_decl_at(source, name, site).map(|(_, decl)| decl)
}

impl KernelResolver {
pub(super) fn go_local_declaration_at(&mut self, name: String, file_path: String, line: i64, column: i64) -> Result<Option<GoLocalDeclaration>> {
let r = ResolveRefIn { row_id: None, from_node_id: String::new(), reference_name: name.clone(), reference_kind: "references".into(),
line, column, candidates: None, file_path: file_path.clone(), language: "go".into(), failure_reason: None, generated: None };
let Some(file) = self.read_file(&file_path) else { return Ok(None) };
let Some(tree) = self.parsed_tree(&file, &r) else { return Ok(None) };
Ok(local_at(&tree, file.text(), &name, line, column))
let out = self.read_file(&file_path).and_then(|file| self.parsed_tree(&file, &r).map(|tree| {
let site = super::iteration::descendant_for_position(tree.root_node(), file.text(),
((line - 1).max(0) as usize, column.max(0) as usize));
let key = (file_path.clone(), name.clone(), site.start_byte());
if let Some(hit) = self.go_local_decl_memo.get(&key) { return hit.clone(); }
let hit = local_decl_at(file.text(), &name, site)
.map(|(t, decl)| (t.start_byte(), t.end_byte(), decl));
self.go_local_decl_memo.insert(key, hit.clone());
hit
})).unwrap_or_default();
Ok(out.map(|(_, _, decl)| decl))
}
}

Expand Down
Loading
Loading