GitNexus/gitnexus/test/integration/resolvers/rust-import-scope.test.ts
Abhinav Pandey 2220f4d851
fix(resolution): label fallback guesses and preserve export visibility (#3190)
* fix(resolution): distinguish name guesses and preserve export visibility

* test(go): keep method enrichment fixture in one package

* fix(resolution): address split review edge cases and evidence reporting

* fix(exports): recognize imported and expression-local receivers

* fix(resolution): align export and target evidence with language scope

* fix(ingestion): preserve lexical import provenance through resolution

* test(ci): rebalance Windows shards from measured slow suites

* Address PR review feedback (#3190)

- Label constructor unique-name guesses as global-name-fallback and run language vetoes
- Tighten Go qualified, Rust crate::, and Ruby class-reopen fallback guards
- Ignore for-loop shadowed CommonJS receivers and exclude guesses from the resolved-call census
- Refresh FinalizeOutput and hook docs for lexical binding scopes

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

* Tighten review-feedback leftovers on fallback visibility.

Qualified Go calls still respect export and test-package rules, nested Rust src/ stays a module segment, and top-level conditional this.x is treated as CommonJS.

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

* chore(autofix): apply prettier + eslint fixes via /autofix command

* Address PR review feedback (#3190)

- Distinguish Swift package prefixes when comparing target modules
- Document lexical import binding and handledSites refusal marking
- Drop the stale ci-scope-parity workflow claim and prototype-safe export verdicts

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

* Address PR review feedback (#3190)

Supply caller source on Ruby visibility cases so they exercise the named allow branches instead of the missing-text bypass.

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

* fix(resolution): label unique constructor types as name guesses

A workspace-unique class hit in findClassBindingInScope was treated as
an in-scope bind, so Go/JS constructor-form sites skipped the guess
label and the Go unexported veto.

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

* fix(resolution): keep qualified constructors precise after unique-name split

Bare constructor unique-name hits stay guesses so Go can veto an
unexported type. A written qualifier is now carried as rawQualifiedName
so `new pkg.Foo()` and `models.Box[T]{}` can still recover the unique
class without that veto.

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

* test(bench): rebaseline Go/Java scope-capture fingerprints for constructor qualifiers

Generic Go composite literals and qualified Java `new pkg.Foo()` now
carry @reference.qualified-name on existing constructor matches.

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

* fix(resolution): treat import-reached unique constructors as precise

C++ #include and Rust re-exports do not mint a lexical class binding.
A unique type in an imported file (or imported directory) is therefore
a real bind, not a name guess, so those CALLS edges stay import-resolved.

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

* fix(resolution): require named or resolved imports for constructor precision

Bare Go Box[T]{} is not package-qualified, and a sibling-file import of a different name is not constructor visibility.

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

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-10 11:38:19 +01:00

73 lines
2.4 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { getRelationships, runPipelineFromRepo, writeFixtureRepo } from './helpers.js';
async function callsFor(
source: string,
targetSource = 'pub fn uniqueScopeHelper() {}\n',
targetName = 'uniqueScopeHelper',
) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gn-rust-import-scope-'));
try {
writeFixtureRepo(dir, {
'Cargo.toml': '[package]\nname = "scope-test"\nversion = "0.1.0"\nedition = "2021"\n',
'src/lib.rs': source,
'src/target.rs': targetSource,
});
const result = await runPipelineFromRepo(dir, () => {});
return getRelationships(result, 'CALLS').filter((edge) => edge.target === targetName);
} finally {
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
}
}
describe('Rust import scope through full ingestion', () => {
it('preserves method calls on the result of a function-local imported factory', async () => {
const calls = await callsFor(
`
mod target;
pub fn allowed() {
use crate::target::make_user;
let user = make_user();
user.save();
}
`,
'pub struct User {}\nimpl User { pub fn save(&self) {} }\npub fn make_user() -> User { User {} }\n',
'save',
);
expect(calls.filter((edge) => edge.source === 'allowed')).toHaveLength(1);
});
it('an import in one inline module cannot bind or authorize calls in its sibling', async () => {
const calls = await callsFor(`
mod target;
mod importing {
use crate::target::uniqueScopeHelper;
pub fn allowed() { uniqueScopeHelper(); }
}
mod sibling {
pub fn denied() { uniqueScopeHelper(); }
}
`);
expect(calls.filter((edge) => edge.source === 'allowed')).toHaveLength(1);
expect(calls.filter((edge) => edge.source === 'allowed')[0]!.rel.reason).toBe(
'import-resolved',
);
expect(calls.filter((edge) => edge.source === 'denied')).toEqual([]);
});
it('function-local alias imports remain usable inside that function, not its sibling', async () => {
const calls = await callsFor(`
mod target;
pub fn allowed() {
use crate::target::uniqueScopeHelper as localHelper;
localHelper();
}
pub fn denied() { localHelper(); }
`);
expect(calls.filter((edge) => edge.source === 'allowed')).toHaveLength(1);
expect(calls.filter((edge) => edge.source === 'denied')).toEqual([]);
});
});