From b54506bf9ffa5d70a34a91ca77a0c89c036a6768 Mon Sep 17 00:00:00 2001 From: Tyler Breisacher Date: Thu, 20 Aug 2026 20:44:25 -0700 Subject: [PATCH 1/3] Fix label for source file --- tools/rust_analyzer/bin/flycheck.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/rust_analyzer/bin/flycheck.rs b/tools/rust_analyzer/bin/flycheck.rs index b7fc3c93ee..5edcc7b3d7 100644 --- a/tools/rust_analyzer/bin/flycheck.rs +++ b/tools/rust_analyzer/bin/flycheck.rs @@ -471,10 +471,12 @@ fn query_label_for( let package = find_owning_package(workspace, file_rel).with_context(|| { format!("no BUILD.bazel found above {saved_file} — is this file part of a Bazel target?") })?; - let file_basename = file_rel - .file_name() - .with_context(|| format!("saved file {saved_file} has no file name"))?; - let pattern = format!("//{package}:{file_basename}"); + + // Generate a label that preserves the package-relative file path. + let file_in_package = file_rel.strip_prefix(&package).with_context(|| { + format!("saved file {saved_file} is not under Bazel package //{package}") + })?; + let pattern = format!("//{package}:{file_in_package}"); let query = format!("attr(srcs, {pattern:?}, //{package}:*)"); let output = Command::new(bazel.as_str()) .current_dir(workspace) From aac12ed59fa52698e87918357f124367542c49d2 Mon Sep 17 00:00:00 2001 From: Tyler Breisacher Date: Mon, 24 Aug 2026 21:02:42 -0700 Subject: [PATCH 2/3] tests --- tools/rust_analyzer/bin/flycheck.rs | 21 +++++++++++++++++++++ tools/rust_analyzer/bin/setup.rs | 15 +-------------- tools/rust_analyzer/lib.rs | 18 +++++++++++++++++- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/tools/rust_analyzer/bin/flycheck.rs b/tools/rust_analyzer/bin/flycheck.rs index 5edcc7b3d7..8201dd76cb 100644 --- a/tools/rust_analyzer/bin/flycheck.rs +++ b/tools/rust_analyzer/bin/flycheck.rs @@ -525,8 +525,29 @@ fn scopeguard(path: Utf8PathBuf) -> impl Drop { #[cfg(test)] mod tests { use super::*; + use gen_rust_project_lib::make_workspace; use serde_json::json; + #[test] + fn find_owning_package_test() { + let pkg_path = "example/library"; + let build_path = format!("{pkg_path}/BUILD.bazel"); + let rust_src_path = format!("{pkg_path}/src/main.rs"); + let workspace = make_workspace( + "find_owning_package_test", + &[ + ("MODULE.bazel", ""), + (&build_path, "package()"), + (&rust_src_path, "fn main() {}"), + ], + ); + + assert_eq!( + find_owning_package(&workspace, &Utf8PathBuf::from(&rust_src_path)).unwrap(), + pkg_path + ); + } + #[test] fn relative_file_names_become_absolute() { let workspace = Utf8Path::new("/abs/ws"); diff --git a/tools/rust_analyzer/bin/setup.rs b/tools/rust_analyzer/bin/setup.rs index 70bad8a34a..09154426b9 100644 --- a/tools/rust_analyzer/bin/setup.rs +++ b/tools/rust_analyzer/bin/setup.rs @@ -1273,6 +1273,7 @@ fn generate_settings_json(ctx: &SetupCtx, launcher_dir: &Utf8Path) -> String { #[cfg(test)] mod tests { use super::*; + use gen_rust_project_lib::make_workspace; fn dummy_toolchain() -> ToolchainBinaries { ToolchainBinaries { @@ -1831,20 +1832,6 @@ mod tests { // `.code-workspace` support // ----------------------------------------------------------------- - /// Build a workspace dir in $TMPDIR, populated with the listed - /// files. Returns the dir path; caller is responsible for cleanup - /// (use `remove_dir_all` in a `_guard`-style drop, or accept the - /// leak — TMPDIR gets cleaned eventually). - fn make_workspace(tag: &str, files: &[(&str, &str)]) -> Utf8PathBuf { - let tmp = std::env::temp_dir().join(format!("setup_{tag}_{}", std::process::id())); - let _ = std::fs::remove_dir_all(&tmp); - std::fs::create_dir_all(&tmp).unwrap(); - for (name, content) in files { - std::fs::write(tmp.join(name), content).unwrap(); - } - Utf8PathBuf::try_from(tmp).unwrap() - } - #[test] fn merge_under_settings_key_preserves_top_level_keys() { let (ctx, _launcher_dir) = dummy_ctx(); diff --git a/tools/rust_analyzer/lib.rs b/tools/rust_analyzer/lib.rs index 6733caedf3..1e95045328 100644 --- a/tools/rust_analyzer/lib.rs +++ b/tools/rust_analyzer/lib.rs @@ -4,7 +4,7 @@ mod cache; mod rust_project; pub mod user_config; -use std::{collections::BTreeMap, fs, process::Command}; +use std::{collections::BTreeMap, convert::TryFrom, fs, process::Command}; use anyhow::{bail, Context}; use camino::{Utf8Path, Utf8PathBuf}; @@ -553,6 +553,22 @@ pub struct ToolchainInfo { pub version: String, } +/// Build a workspace dir in $TMPDIR, populated with the listed +/// files. Returns the dir path; caller is responsible for cleanup +/// (use `remove_dir_all` in a `_guard`-style drop, or accept the +/// leak — TMPDIR gets cleaned eventually). +pub fn make_workspace(tag: &str, files: &[(&str, &str)]) -> Utf8PathBuf { + let tmp = std::env::temp_dir().join(format!("setup_{tag}_{}", std::process::id())); + let _ = std::fs::remove_dir_all(&tmp); + std::fs::create_dir_all(&tmp).unwrap(); + for (name, content) in files { + let abs_path = tmp.join(name); + std::fs::create_dir_all(abs_path.parent().unwrap()).unwrap(); + std::fs::write(abs_path, content).unwrap(); + } + Utf8PathBuf::try_from(tmp).unwrap() +} + #[cfg(test)] mod tests { use super::*; From 5f2c153e5d8050e3485c0a3e71404097ad629286 Mon Sep 17 00:00:00 2001 From: Tyler Breisacher Date: Mon, 24 Aug 2026 21:28:26 -0700 Subject: [PATCH 3/3] Add test for query_string_for --- tools/rust_analyzer/bin/flycheck.rs | 45 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/tools/rust_analyzer/bin/flycheck.rs b/tools/rust_analyzer/bin/flycheck.rs index 8201dd76cb..06808c3289 100644 --- a/tools/rust_analyzer/bin/flycheck.rs +++ b/tools/rust_analyzer/bin/flycheck.rs @@ -457,14 +457,8 @@ fn resolve_label_for( query_label_for(bazel, workspace, saved_file) } -/// `bazel query 'attr(srcs, "", //:*)'` scoped to the -/// nearest `BUILD.bazel`. Returns the first match — if a file belongs -/// to several targets, any is correct. -fn query_label_for( - bazel: &Utf8Path, - workspace: &Utf8Path, - saved_file: &Utf8Path, -) -> Result { +/// Produces the query string used by `query_label_for` +fn query_string_for(workspace: &Utf8Path, saved_file: &Utf8Path) -> Result { let file_rel = saved_file .strip_prefix(workspace) .with_context(|| format!("saved file {saved_file} is not under workspace {workspace}"))?; @@ -477,7 +471,18 @@ fn query_label_for( format!("saved file {saved_file} is not under Bazel package //{package}") })?; let pattern = format!("//{package}:{file_in_package}"); - let query = format!("attr(srcs, {pattern:?}, //{package}:*)"); + Ok(format!("attr(srcs, {pattern:?}, //{package}:*)")) +} + +/// `bazel query 'attr(srcs, "", //:*)'` scoped to the +/// nearest `BUILD.bazel`. Returns the first match — if a file belongs +/// to several targets, any is correct. +fn query_label_for( + bazel: &Utf8Path, + workspace: &Utf8Path, + saved_file: &Utf8Path, +) -> Result { + let query = query_string_for(workspace, saved_file)?; let output = Command::new(bazel.as_str()) .current_dir(workspace) .arg("query") @@ -494,7 +499,7 @@ fn query_label_for( .lines() .find(|l| !l.is_empty()) .map(str::to_owned) - .with_context(|| format!("bazel query returned no targets for {pattern}")) + .with_context(|| format!("bazel query {query:?} returned no targets")) } /// Walk up looking for `BUILD.bazel` or `BUILD`. Returns the @@ -548,6 +553,26 @@ mod tests { ); } + #[test] + fn query_test() { + let pkg_path = "example/library"; + let build_path = format!("{pkg_path}/BUILD.bazel"); + let rust_src_path = format!("{pkg_path}/src/main.rs"); + let workspace = make_workspace( + "query_test", + &[ + ("MODULE.bazel", ""), + (&build_path, "package()"), + (&rust_src_path, "fn main() {}"), + ], + ); + + assert_eq!( + query_string_for(&workspace, &workspace.join(&rust_src_path)).unwrap(), + r#"attr(srcs, "//example/library:src/main.rs", //example/library:*)"# + ); + } + #[test] fn relative_file_names_become_absolute() { let workspace = Utf8Path::new("/abs/ws");