GitNexus/gitnexus/test/integration/resolvers/rust-coverage.test.ts
Abhinav Pandey 0ede6ae501
fix(rust): respect Cargo target boundaries in name fallback (#3294)
* fix(rust): respect Cargo target boundaries in name fallback

* fix(rust): read Cargo sources through validated file descriptors

* fix(rust): require matching imports for crate-root guesses

* fix(rust): require Cargo root identity for cross-target imports

* fix(rust): enforce Cargo identity across fallback imports

* fix(rust): keep Cargo membership on typical derive/macros (#3294)

Abort only genuine unknown expansion, ignore Cargo artifact layouts instead of every `target` path segment, and require a covering import for unanswered crate-root name fallback so #3253 still holds on ordinary Rust sources.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(rust): gate Cargo target membership in CI (#3253)

Add a parse-dispatch-rounds-style bench so derive/macro abort, target-path globs, and superlinear membership walks fail in CI instead of staying graph-invisible.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(rust): verify Cargo macro and re-export evidence

* style(bench): format Cargo membership benchmark

* fix(rust): require imports for cross-file root guesses

---------

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 11:36:07 +01:00

103 lines
4.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Regression tests for Rust scope-resolution coverage gaps (issue #1934).
*/
import { describe, it, expect } from 'vitest';
import { emitRustScopeCaptures } from '../../../src/core/ingestion/languages/rust/index.js';
import type { CaptureMatch } from 'gitnexus-shared';
// ---------------------------------------------------------------------------
// F66/F68 — let binding patterns (identifier-only, works with let mut x)
// ---------------------------------------------------------------------------
describe('F66/F68 — let binding pattern shapes', () => {
it('bare identifier let binding emits @declaration.variable', () => {
const src = `fn f() { let x = 1; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const vars = matches.filter((m) => m['@declaration.variable']);
expect(vars.length).toBe(1);
expect(vars[0]['@declaration.name'].text).toBe('x');
});
it('let mut x emits @declaration.variable', () => {
const src = `fn f() { let mut x = 1; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const vars = matches.filter((m) => m['@declaration.variable']);
expect(vars.length).toBe(1);
expect(vars[0]['@declaration.name'].text).toBe('x');
});
});
// ---------------------------------------------------------------------------
// F71 — union declarations
// ---------------------------------------------------------------------------
describe('F71 — union declaration', () => {
it('union item emits @scope.class and @declaration.struct', () => {
const src = `union MyUnion { x: i32, y: f64 }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const scopes = matches.filter((m) => m['@scope.class']);
expect(scopes.length).toBe(1);
const decls = matches.filter((m) => m['@declaration.struct']);
expect(decls.length).toBe(1);
expect(decls[0]['@declaration.name'].text).toBe('MyUnion');
});
});
// ---------------------------------------------------------------------------
// F72 — macro invocations (capture layer)
//
// These pin the tree-sitter CAPTURE shape only. End-to-end macro RESOLUTION
// (the @reference.macro → MacroRegistry → USES-edge-to-a-Macro-node path, and
// the guarantee that a macro never binds to a same-named function) is asserted
// at the pipeline level in `rust.test.ts` › "Rust macro resolution (issue #1934 F72)".
// ---------------------------------------------------------------------------
describe('F72 — macro invocations (capture layer)', () => {
it('macro_invocation with bare identifier emits @reference.macro', () => {
const src = `fn f() { println!("hi"); }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const macroRefs = matches.filter((m) => m['@reference.macro']);
const macroNames = macroRefs.map((m) => m['@reference.name']?.text);
expect(macroNames).toContain('println');
});
it('vec! macro emits @reference.macro', () => {
const src = `fn f() { let v = vec![1, 2, 3]; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const macroRefs = matches.filter((m) => m['@reference.macro']);
const macroNames = macroRefs.map((m) => m['@reference.name']?.text);
expect(macroNames).toContain('vec');
});
it('scoped macro invocation captures the TAIL identifier, not the full path', () => {
const src = `fn f() { log::info!("hi"); }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const macroRefs = matches.filter((m) => m['@reference.macro']);
const macroNames = macroRefs.map((m) => m['@reference.name']?.text);
// Must be the tail `info`, not the whole path `log::info` — mirrors the
// scoped free-call pattern. Guards the P3 fix.
expect(macroNames).toContain('info');
expect(macroNames).not.toContain('log::info');
});
it('macro_rules! definition emits a @declaration.macro capture', () => {
const src = `macro_rules! greet { () => {}; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const macroDecls = matches.filter((m) => m['@declaration.macro']);
expect(macroDecls.length).toBe(1);
expect(macroDecls[0]['@declaration.name'].text).toBe('greet');
});
});
describe('restricted visibility use is not a re-export', () => {
it.each([
['pub use foo::helper;', 'reexport'],
['pub(crate) use foo::helper;', 'named'],
['pub(super) use foo::helper;', 'named'],
['use foo::helper;', 'named'],
])('%s', (src, kind) => {
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const imps = matches.filter((m) => m['@import.kind']);
expect(imps.map((m) => m['@import.kind']?.text)).toEqual([kind]);
});
});