mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
715c62b0d9
The extractors keep one receiver level, so chain links arrive as bare names. Read at the call site: - bare (no `.` / `::` before it): never a method — Rust reaches one only through `self.` / `Type::`, Go through a receiver (axum's routing `get(handler)` went to a cookie jar's `get`, tokio's `drop(x)` to `Drop` impls, #1861's pinned `reset()` → `Target::reset` is now the free fn); - chained (after `.`, receiver read back through arguments, macros `name!`, `::` paths and Go composite literals): never a free function, and a standard-library method name (Option/Result/iterator/collection/string methods; Go's String/Error/Header/locks/reflect/time) needs a receiver named after its owner — project-specific names keep their match (clap's `flag("n").short('n')` → Arg::short, cobra's `c.Root().Name()`, gin's `c.Set(…)`). The same std gate applies to the method-call matcher's unique-name guess. A/B (edges removed / added): clap -1496/+93, tokio -1064/+235, axum -695/+42, ripgrep -477/+32, cobra -167, serde -119/+1, bat -40/+4, gin -32/+26. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
/**
|
||
* Rust and Go extractors keep one receiver level, so a chain's later links
|
||
* (`sym.filename().map(…)`, `c.Flags().String(…)`) arrive as bare names. Read
|
||
* at the call: a bare call never reaches a method (axum's routing `get(h)`
|
||
* went to a cookie jar's `get`, tokio's `drop(x)` to a `Drop` impl); a chained
|
||
* call with a standard-library name (`unwrap`, `map`, Go's `String`) needs a
|
||
* receiver named after the owner, while a project-specific one keeps its
|
||
* match (clap's `flag("n").short('n')` → `Arg::short`).
|
||
*/
|
||
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
||
import * as fs from 'fs';
|
||
import * as os from 'os';
|
||
import * as path from 'path';
|
||
import { CodeGraph } from '../src';
|
||
|
||
let root = '';
|
||
let cg: CodeGraph;
|
||
|
||
beforeAll(async () => {
|
||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-rust-go-shape-'));
|
||
const files: Record<string, string> = {
|
||
'Cargo.toml': '[package]\nname = "app"\nversion = "0.1.0"\n',
|
||
'src/guard.rs': `pub struct MutexGuard;
|
||
impl MutexGuard {
|
||
pub fn map(self) -> Self { self }
|
||
}
|
||
pub struct CookieJar;
|
||
impl CookieJar {
|
||
pub fn get(&self) -> Option<u8> { None }
|
||
}
|
||
pub struct Arg;
|
||
impl Arg {
|
||
pub fn short(self, c: char) -> Self { self }
|
||
}
|
||
pub fn flag(name: &str) -> Arg { Arg }
|
||
`,
|
||
'src/main.rs': `mod guard;
|
||
use guard::flag;
|
||
fn get(handler: u8) -> u8 { handler }
|
||
fn run(sym: Option<&str>) {
|
||
let _ = sym.map(|s| s.len());
|
||
let _ = get(1);
|
||
let _ = flag("n").short('n');
|
||
}
|
||
fn main() { run(None); }
|
||
`,
|
||
'go.mod': 'module example.com/app\n\ngo 1.21\n',
|
||
'flags.go': `package app
|
||
|
||
type customString struct{}
|
||
|
||
func (c customString) String() string { return "" }
|
||
|
||
type FlagSet struct{}
|
||
|
||
func Flags() *FlagSet { return nil }
|
||
`,
|
||
'cmd.go': `package app
|
||
|
||
func run() {
|
||
Flags().String("name", "", "")
|
||
}
|
||
`,
|
||
};
|
||
for (const [rel, content] of Object.entries(files)) {
|
||
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
||
fs.writeFileSync(path.join(root, rel), content);
|
||
}
|
||
cg = await CodeGraph.init(root, { index: true });
|
||
});
|
||
|
||
afterAll(() => {
|
||
cg?.close();
|
||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||
});
|
||
|
||
const callsIn = (file: string, fn: string): string[] => {
|
||
const node = cg.getNodesInFile(file).find((n) => n.name === fn)!;
|
||
return cg.getOutgoingEdges(node.id).filter((e) => e.kind === 'calls').map((e) => cg.getNode(e.target)!.qualifiedName);
|
||
};
|
||
|
||
describe('Rust and Go call shapes', () => {
|
||
it('Rust: bare never a method, std chain names need their receiver, project chain names keep theirs', () => {
|
||
const targets = callsIn('src/main.rs', 'run');
|
||
expect(targets.some((t) => t.endsWith('MutexGuard::map'))).toBe(false);
|
||
expect(targets.some((t) => t.endsWith('CookieJar::get'))).toBe(false);
|
||
expect(targets.some((t) => t.endsWith('Arg::short'))).toBe(true);
|
||
});
|
||
|
||
it('Go: a std-named chained call is not a project type’s method', () => {
|
||
expect(callsIn('cmd.go', 'run').some((t) => t.endsWith('customString::String'))).toBe(false);
|
||
});
|
||
});
|