Fix crate-name import resolution for bin+lib crates
Register the Cargo package name (normalized - -> _) as an alias for
src/lib.rs in the resolver, so 'use <crate>::{...}' in src/main.rs
resolves to src/lib.rs. Adds rustdemo/Cargo.toml fixture and test
coverage; updates blast-radius test for the new transitive edge.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
1 parent
0bb09da454
commit
6875998ab6
4 files changed
+64
-5
No files matched your search
+12
-1
@@ -294,7 +294,7 @@ fn clean(target: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
pub fn build(files: Vec<(String, i64)>) -> Self {
|
||||
pub fn build(files: Vec<(String, i64)>, crate_name: Option<&str>) -> Self {
|
||||
let mut by_path = HashMap::new();
|
||||
let mut by_name = HashMap::new();
|
||||
let mut by_stem = HashMap::new();
|
||||
@@ -308,6 +308,17 @@ impl Resolver {
|
||||
.unwrap_or(name);
|
||||
by_stem.entry(stem).or_insert(id);
|
||||
}
|
||||
// `use <crate>::…` names the crate's own lib root, which always lives
|
||||
// at src/lib.rs — there is no `<crate>.rs` file. Register the package
|
||||
// name as an alias so bin→lib edges resolve. Workspaces (no [package]
|
||||
// name) are skipped.
|
||||
if let Some(name) = crate_name.filter(|n| !n.is_empty())
|
||||
&& let Some(id) = by_path.get("src/lib.rs").copied()
|
||||
{
|
||||
by_path.insert(name.to_string(), id);
|
||||
by_name.entry(name.to_string()).or_insert(id);
|
||||
by_stem.entry(name.to_string()).or_insert(id);
|
||||
}
|
||||
Resolver {
|
||||
by_path,
|
||||
by_name,
|
||||
|
||||
+28
-2
@@ -18,6 +18,30 @@ pub struct IndexStats {
|
||||
pub imports: i64,
|
||||
}
|
||||
|
||||
/// Read the `[package] name` from `Cargo.toml`, normalized to the crate
|
||||
/// identifier (`-` → `_`) used in `use <crate>::…`.
|
||||
fn read_crate_name(project: &Path) -> Option<String> {
|
||||
let toml = std::fs::read_to_string(project.join("Cargo.toml")).ok()?;
|
||||
let mut in_package = false;
|
||||
for line in toml.lines() {
|
||||
let l = line.trim();
|
||||
if l.starts_with('[') {
|
||||
in_package = l == "[package]";
|
||||
continue;
|
||||
}
|
||||
if in_package
|
||||
&& let Some(v) = l.strip_prefix("name").map(str::trim)
|
||||
&& let Some(v) = v.strip_prefix('=').map(str::trim)
|
||||
{
|
||||
let name = v.trim_matches('"');
|
||||
if !name.is_empty() {
|
||||
return Some(name.replace('-', "_"));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// (Re)index a project. `recreate` drops any existing index first.
|
||||
pub fn run(project: &Path, db_path: &Path, recreate: bool) -> Result<IndexStats> {
|
||||
if recreate && db_path.exists() {
|
||||
@@ -83,8 +107,10 @@ pub fn run(project: &Path, db_path: &Path, recreate: bool) -> Result<IndexStats>
|
||||
}
|
||||
}
|
||||
|
||||
let resolver =
|
||||
imports::Resolver::build(file_ids.iter().map(|(k, v)| (k.clone(), *v)).collect());
|
||||
let resolver = imports::Resolver::build(
|
||||
file_ids.iter().map(|(k, v)| (k.clone(), *v)).collect(),
|
||||
read_crate_name(project).as_deref(),
|
||||
);
|
||||
|
||||
for f in &changed {
|
||||
let fid = file_ids[&f.rel];
|
||||
|
||||
+20
-2
@@ -90,6 +90,22 @@ fn rust_index_search_and_dead() {
|
||||
"source contains function body"
|
||||
);
|
||||
|
||||
// `use rustdemo::add` in main.rs resolves via the package-name alias.
|
||||
let importers = run_json(&[
|
||||
"--project",
|
||||
fx.to_str().unwrap(),
|
||||
"find",
|
||||
"importers",
|
||||
"src/lib.rs",
|
||||
]);
|
||||
let names: Vec<&str> = importers
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|v| v.as_str().unwrap())
|
||||
.collect();
|
||||
assert!(names.contains(&"src/main.rs"), "main.rs imports lib.rs");
|
||||
|
||||
// dead.rs is flagged; entry points lib.rs/main.rs are not.
|
||||
let dead = run_json(&["--project", fx.to_str().unwrap(), "find", "dead"]);
|
||||
let paths: Vec<&str> = dead["files"]
|
||||
@@ -118,7 +134,7 @@ fn inspect_blast_radius_finds_affected_files() {
|
||||
"blast-radius",
|
||||
"src/util.rs",
|
||||
]);
|
||||
assert_eq!(blast["count"], 1);
|
||||
assert_eq!(blast["count"], 2);
|
||||
assert_eq!(blast["file"], "src/util.rs");
|
||||
let affected: Vec<&str> = blast["affected"]
|
||||
.as_array()
|
||||
@@ -126,7 +142,9 @@ fn inspect_blast_radius_finds_affected_files() {
|
||||
.iter()
|
||||
.map(|f| f["path"].as_str().unwrap())
|
||||
.collect();
|
||||
assert_eq!(affected, ["src/lib.rs"], "lib.rs imports util.rs");
|
||||
// lib.rs imports util.rs directly; main.rs reaches it via the crate name.
|
||||
assert!(affected.contains(&"src/lib.rs"), "lib.rs imports util.rs");
|
||||
assert!(affected.contains(&"src/main.rs"), "main.rs → lib.rs → util.rs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "rustdemo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
Reference in new issue
Block a user