mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-07 02:58:02 +00:00
Add Rust scope-resolution integration tests (RFC #909 Ring 3)
Tests cover the core deliverables for the Rust scope-resolution pipeline:
- impl blocks and trait implementations
- Module resolution (crate::, super::, self::)
- Struct fields and type bindings
- Self/&self/&mut self receiver binding
- Generic functions (V1 ignores generic args)
- Grouped imports (use foo::{A, B})
- Renamed imports (use foo::Bar as Baz)
- Arity checking (no overloading)
- Struct literal constructor inference
- Return type inference
- Scoped/qualified calls (Foo::new())
- Enum declarations
- Multiple impl blocks
- Free function calls
- Typed let bindings
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
This commit is contained in:
parent
e93f332d58
commit
75f01b941a
1 changed files with 865 additions and 0 deletions
865
gitnexus/test/integration/resolvers/rust-scope.test.ts
Normal file
865
gitnexus/test/integration/resolvers/rust-scope.test.ts
Normal file
|
|
@ -0,0 +1,865 @@
|
|||
/**
|
||||
* Rust scope-resolution integration tests (RFC #909 Ring 3).
|
||||
*
|
||||
* These tests run with REGISTRY_PRIMARY_RUST=true to exercise the
|
||||
* scope-based resolution path. They validate the core deliverables:
|
||||
* impl blocks, use statements, receiver binding, module resolution.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import path from 'path';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import {
|
||||
getRelationships,
|
||||
getNodesByLabel,
|
||||
getNodesByLabelFull,
|
||||
edgeSet,
|
||||
runPipelineFromRepo,
|
||||
type PipelineResult,
|
||||
} from './helpers.js';
|
||||
|
||||
function writeFixtureRepo(root: string, files: Record<string, string>): void {
|
||||
for (const [rel, content] of Object.entries(files)) {
|
||||
const abs = path.join(root, rel);
|
||||
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
||||
fs.writeFileSync(abs, content, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
let savedEnv: string | undefined;
|
||||
|
||||
beforeAll(() => {
|
||||
savedEnv = process.env['REGISTRY_PRIMARY_RUST'];
|
||||
process.env['REGISTRY_PRIMARY_RUST'] = 'true';
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (savedEnv === undefined) delete process.env['REGISTRY_PRIMARY_RUST'];
|
||||
else process.env['REGISTRY_PRIMARY_RUST'] = savedEnv;
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1. Impl blocks: methods classified as Method, owned by struct
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: impl block method classification', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-impl-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod user;
|
||||
use crate::user::User;
|
||||
|
||||
fn main() {
|
||||
let u = User { name: String::new() };
|
||||
u.save();
|
||||
}
|
||||
`,
|
||||
'src/user.rs': `
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn create(name: String) -> User {
|
||||
User { name }
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('detects User struct', () => {
|
||||
expect(getNodesByLabel(result, 'Struct')).toContain('User');
|
||||
});
|
||||
|
||||
it('classifies save as Method (not Function)', () => {
|
||||
const methods = getNodesByLabel(result, 'Method');
|
||||
expect(methods).toContain('save');
|
||||
});
|
||||
|
||||
it('emits HAS_METHOD edge from User to save', () => {
|
||||
const edges = getRelationships(result, 'HAS_METHOD');
|
||||
const userSave = edges.find((e) => e.source === 'User' && e.target === 'save');
|
||||
expect(userSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves main → u.save() as CALLS edge to save in user.rs', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const mainSave = calls.find(
|
||||
(c) => c.target === 'save' && c.source === 'main' && c.targetFilePath?.includes('user.rs'),
|
||||
);
|
||||
expect(mainSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('emits IMPORTS edge from main.rs to user.rs', () => {
|
||||
const imports = getRelationships(result, 'IMPORTS');
|
||||
const imp = imports.find((e) => e.targetFilePath?.includes('user.rs'));
|
||||
expect(imp).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2. Trait implementations: IMPLEMENTS edges + method resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: trait implementation', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-trait-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod shapes;
|
||||
use crate::shapes::{Circle, Drawable};
|
||||
|
||||
fn main() {
|
||||
let c = Circle { radius: 5.0 };
|
||||
c.draw();
|
||||
}
|
||||
`,
|
||||
'src/shapes.rs': `
|
||||
pub trait Drawable {
|
||||
fn draw(&self);
|
||||
}
|
||||
|
||||
pub struct Circle {
|
||||
pub radius: f64,
|
||||
}
|
||||
|
||||
impl Drawable for Circle {
|
||||
fn draw(&self) {
|
||||
println!("Drawing circle");
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('detects Drawable trait and Circle struct', () => {
|
||||
expect(getNodesByLabel(result, 'Trait')).toContain('Drawable');
|
||||
expect(getNodesByLabel(result, 'Struct')).toContain('Circle');
|
||||
});
|
||||
|
||||
it('emits IMPLEMENTS edge from Circle to Drawable', () => {
|
||||
const impls = getRelationships(result, 'IMPLEMENTS');
|
||||
const circleDrawable = impls.find(
|
||||
(e) => e.source === 'Circle' && e.target === 'Drawable',
|
||||
);
|
||||
expect(circleDrawable).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves main → c.draw() to shapes.rs', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const drawCall = calls.find(
|
||||
(c) => c.target === 'draw' && c.source === 'main',
|
||||
);
|
||||
expect(drawCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3. Grouped imports: use crate::models::{User, Config}
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: grouped imports', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-grouped-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod models;
|
||||
use crate::models::{User, Config};
|
||||
|
||||
fn process() {
|
||||
let u = User { name: String::new() };
|
||||
u.save();
|
||||
let c = Config { debug: true };
|
||||
c.validate();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
`,
|
||||
'src/models.rs': `
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn validate(&self) {}
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('resolves grouped import to models.rs', () => {
|
||||
const imports = getRelationships(result, 'IMPORTS');
|
||||
const modelsImport = imports.find((e) => e.targetFilePath?.includes('models.rs'));
|
||||
expect(modelsImport).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves u.save() to User#save via grouped import binding', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(
|
||||
(c) => c.target === 'save' && c.source === 'process',
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves c.validate() to Config#validate via grouped import binding', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const validateCall = calls.find(
|
||||
(c) => c.target === 'validate' && c.source === 'process',
|
||||
);
|
||||
expect(validateCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 4. Renamed imports: use Foo as Bar
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: renamed imports (use as)', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-alias-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod models;
|
||||
use crate::models::User as U;
|
||||
|
||||
fn process() {
|
||||
let u = U { name: String::new() };
|
||||
u.save();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
`,
|
||||
'src/models.rs': `
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('emits IMPORTS edge to models.rs', () => {
|
||||
const imports = getRelationships(result, 'IMPORTS');
|
||||
const imp = imports.find((e) => e.targetFilePath?.includes('models.rs'));
|
||||
expect(imp).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 5. Module resolution: super:: and self::
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: module resolution (crate/super/self)', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-modules-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod models;
|
||||
mod services;
|
||||
|
||||
fn main() {}
|
||||
`,
|
||||
'src/models.rs': `
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
`,
|
||||
'src/services.rs': `
|
||||
use crate::models::User;
|
||||
|
||||
pub fn process() {
|
||||
let u = User { name: String::new() };
|
||||
u.save();
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('resolves crate::models::User import from services.rs to models.rs', () => {
|
||||
const imports = getRelationships(result, 'IMPORTS');
|
||||
const imp = imports.find(
|
||||
(e) =>
|
||||
e.targetFilePath?.includes('models.rs') &&
|
||||
e.sourceFilePath?.includes('services.rs'),
|
||||
);
|
||||
expect(imp).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves process → u.save() to models.rs', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(
|
||||
(c) =>
|
||||
c.target === 'save' &&
|
||||
c.source === 'process' &&
|
||||
c.targetFilePath?.includes('models.rs'),
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 6. Receiver binding: &self, &mut self
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: receiver binding disambiguation', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-receiver-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod user;
|
||||
mod repo;
|
||||
use crate::user::User;
|
||||
use crate::repo::Repo;
|
||||
|
||||
fn process() {
|
||||
let u = User { name: String::new() };
|
||||
u.save();
|
||||
let r = Repo { path: String::new() };
|
||||
r.save();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
`,
|
||||
'src/user.rs': `
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
`,
|
||||
'src/repo.rs': `
|
||||
pub struct Repo {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
impl Repo {
|
||||
pub fn save(&mut self) {}
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('resolves u.save() to User#save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const userSave = calls.find(
|
||||
(c) =>
|
||||
c.target === 'save' &&
|
||||
c.source === 'process' &&
|
||||
c.targetFilePath?.includes('user.rs'),
|
||||
);
|
||||
expect(userSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves r.save() to Repo#save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const repoSave = calls.find(
|
||||
(c) =>
|
||||
c.target === 'save' &&
|
||||
c.source === 'process' &&
|
||||
c.targetFilePath?.includes('repo.rs'),
|
||||
);
|
||||
expect(repoSave).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 7. Arity filtering: no Rust overloading
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: arity filtering', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-arity-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod onearg;
|
||||
mod twoarg;
|
||||
|
||||
fn main() {
|
||||
onearg::write_audit(String::from("test"));
|
||||
}
|
||||
`,
|
||||
'src/onearg.rs': `
|
||||
pub fn write_audit(msg: String) {}
|
||||
`,
|
||||
'src/twoarg.rs': `
|
||||
pub fn write_audit(msg: String, level: i32) {}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('resolves main → write_audit to onearg.rs (1-arg match)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const call = calls.find(
|
||||
(c) => c.target === 'write_audit' && c.targetFilePath?.includes('onearg.rs'),
|
||||
);
|
||||
expect(call).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 8. Struct constructor inference: let x = Foo { ... }
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: struct literal constructor inference', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-ctor-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod user;
|
||||
use crate::user::User;
|
||||
|
||||
fn process() {
|
||||
let u = User { name: String::new() };
|
||||
u.save();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
`,
|
||||
'src/user.rs': `
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('resolves u.save() to User#save via struct literal type inference', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(
|
||||
(c) =>
|
||||
c.target === 'save' &&
|
||||
c.source === 'process' &&
|
||||
c.targetFilePath?.includes('user.rs'),
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 9. Return type inference: fn get_user() -> User
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: return type inference', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-rettype-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod models;
|
||||
use crate::models::User;
|
||||
|
||||
fn get_user() -> User {
|
||||
User { name: String::new() }
|
||||
}
|
||||
|
||||
fn process() {
|
||||
let u = get_user();
|
||||
u.save();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
`,
|
||||
'src/models.rs': `
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('resolves process → u.save() via return type of get_user()', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(
|
||||
(c) =>
|
||||
c.target === 'save' &&
|
||||
c.source === 'process' &&
|
||||
c.targetFilePath?.includes('models.rs'),
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves process → get_user() free call', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const getUserCall = calls.find(
|
||||
(c) => c.target === 'get_user' && c.source === 'process',
|
||||
);
|
||||
expect(getUserCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 10. Scoped call: Type::method()
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: scoped/qualified calls (Foo::new())', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-qualified-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod user;
|
||||
use crate::user::User;
|
||||
|
||||
fn process() {
|
||||
let u = User::new(String::from("test"));
|
||||
u.save();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
`,
|
||||
'src/user.rs': `
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(name: String) -> User {
|
||||
User { name }
|
||||
}
|
||||
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('resolves process → User::new() free call', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const newCall = calls.find(
|
||||
(c) => c.target === 'new' && c.source === 'process',
|
||||
);
|
||||
expect(newCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 11. Struct field declarations captured as Property
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: struct field declarations', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-fields-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
struct Point {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let p = Point { x: 1, y: 2 };
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('detects Point struct', () => {
|
||||
expect(getNodesByLabel(result, 'Struct')).toContain('Point');
|
||||
});
|
||||
|
||||
it('captures x and y as Property nodes', () => {
|
||||
const props = getNodesByLabel(result, 'Property');
|
||||
expect(props).toContain('x');
|
||||
expect(props).toContain('y');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 12. Enum declarations
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: enum declarations', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-enum-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
enum Color {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('detects Color enum', () => {
|
||||
expect(getNodesByLabel(result, 'Enum')).toContain('Color');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 13. Multiple impl blocks for same struct
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: multiple impl blocks', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-multi-impl-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod user;
|
||||
use crate::user::User;
|
||||
|
||||
fn process() {
|
||||
let u = User { name: String::new() };
|
||||
u.save();
|
||||
u.display();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
`,
|
||||
'src/user.rs': `
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn display(&self) {}
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('resolves u.save() from first impl block', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(
|
||||
(c) => c.target === 'save' && c.source === 'process',
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves u.display() from second impl block', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const displayCall = calls.find(
|
||||
(c) => c.target === 'display' && c.source === 'process',
|
||||
);
|
||||
expect(displayCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 14. Free function calls (non-member)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: free function calls', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-freecall-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod utils;
|
||||
use crate::utils::helper;
|
||||
|
||||
fn main() {
|
||||
helper();
|
||||
}
|
||||
`,
|
||||
'src/utils.rs': `
|
||||
pub fn helper() {
|
||||
println!("helping");
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('resolves main → helper() free call', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const helperCall = calls.find(
|
||||
(c) => c.target === 'helper' && c.source === 'main',
|
||||
);
|
||||
expect(helperCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 15. Variable type binding via let: type annotation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust scope: typed let binding', () => {
|
||||
let result: PipelineResult;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rust-scope-lettype-'));
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'src/main.rs': `
|
||||
mod user;
|
||||
use crate::user::User;
|
||||
|
||||
fn process() {
|
||||
let u: User = User { name: String::new() };
|
||||
u.save();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
`,
|
||||
'src/user.rs': `
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
`,
|
||||
});
|
||||
result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('resolves u.save() via typed let binding', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(
|
||||
(c) => c.target === 'save' && c.source === 'process',
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue