From 1c26a759ea0543d5eaf1c4639fde757b16ed722f Mon Sep 17 00:00:00 2001 From: Shunpoco Date: Sat, 13 Dec 2025 19:41:14 +0000 Subject: [PATCH] modify tidy to run spellcheck if it is installed tidy now runs spellcheck without adding --extra-checks=spellcheck, only if typos-cli is installed under ./build, and the version is expected. If --extra-checks=spellcheck is given, the new spellcheck step is skipped and extra-checks is responsible for (re)install typos-cli and running it. --- src/tools/tidy/src/extra_checks/mod.rs | 103 +++++++++++++++---------- src/tools/tidy/src/lib.rs | 27 +++++++ src/tools/tidy/src/main.rs | 6 ++ src/tools/tidy/src/spellcheck.rs | 31 ++++++++ 4 files changed, 128 insertions(+), 39 deletions(-) create mode 100644 src/tools/tidy/src/spellcheck.rs diff --git a/src/tools/tidy/src/extra_checks/mod.rs b/src/tools/tidy/src/extra_checks/mod.rs index a45af7fcf1580..37c4d4f490707 100644 --- a/src/tools/tidy/src/extra_checks/mod.rs +++ b/src/tools/tidy/src/extra_checks/mod.rs @@ -42,6 +42,10 @@ const RUFF_CONFIG_PATH: &[&str] = &["src", "tools", "tidy", "config", "ruff.toml const RUFF_CACHE_PATH: &[&str] = &["cache", "ruff_cache"]; const PIP_REQ_PATH: &[&str] = &["src", "tools", "tidy", "config", "requirements.txt"]; +pub const SPELLCHECK_BIN_NAME: &str = "typos"; +pub const SPELLCHECK_VERSION: &str = "1.38.1"; +const SPELLCHECK_PACKAGE_NAME: &str = "typos-cli"; +const SPELLCHECK_CONFIG: &str = "typos.toml"; const SPELLCHECK_DIRS: &[&str] = &["compiler", "library", "src/bootstrap", "src/librustdoc"]; pub fn check( @@ -91,30 +95,7 @@ fn check_impl( let bless = tidy_ctx.is_bless_enabled(); // Split comma-separated args up - let mut lint_args = match extra_checks { - Some(s) => s - .strip_prefix("--extra-checks=") - .unwrap() - .split(',') - .map(|s| { - if s == "spellcheck:fix" { - eprintln!("warning: `spellcheck:fix` is no longer valid, use `--extra-checks=spellcheck --bless`"); - } - (ExtraCheckArg::from_str(s), s) - }) - .filter_map(|(res, src)| match res { - Ok(arg) => { - Some(arg) - } - Err(err) => { - // only warn because before bad extra checks would be silently ignored. - eprintln!("warning: bad extra check argument {src:?}: {err:?}"); - None - } - }) - .collect(), - None => vec![], - }; + let mut lint_args = parse_extra_checks(extra_checks); if lint_args.iter().any(|ck| ck.auto) { crate::files_modified_batch_filter(ci_info, &mut lint_args, |ck, path| { ck.is_non_auto_or_matches(path) @@ -305,18 +286,21 @@ fn check_impl( } if spellcheck { - let config_path = root_path.join("typos.toml"); - let mut args = vec!["-c", config_path.as_os_str().to_str().unwrap()]; - - args.extend_from_slice(SPELLCHECK_DIRS); - + let args = build_spellcheck_args(root_path, bless); if bless { eprintln!("spellchecking files and fixing typos"); - args.push("--write-changes"); } else { eprintln!("spellchecking files"); } - let res = spellcheck_runner(root_path, &outdir, &cargo, &args); + + let bin_path = crate::ensure_version_or_cargo_install( + outdir, + cargo, + SPELLCHECK_PACKAGE_NAME, + SPELLCHECK_BIN_NAME, + SPELLCHECK_VERSION, + )?; + let res = spellcheck_runner(root_path, &bin_path, args); if res.is_err() { rerun_with_bless("spellcheck", "fix typos"); } @@ -349,6 +333,38 @@ fn check_impl( Ok(()) } +fn parse_extra_checks(extra_checks: Option<&str>) -> Vec { + match extra_checks { + Some(s) => s + .strip_prefix("--extra-checks=") + .unwrap() + .split(',') + .map(|s| { + if s == "spellcheck:fix" { + eprintln!("warning: `spellcheck:fix` is no longer valid, use `--extra-checks=spellcheck --bless`"); + } + (ExtraCheckArg::from_str(s), s) + }) + .filter_map(|(res, src)| match res { + Ok(arg) => { + Some(arg) + } + Err(err) => { + // only warn because before bad extra checks would be silently ignored. + eprintln!("warning: bad extra check argument {src:?}: {err:?}"); + None + } + }) + .collect(), + None => vec![], + } +} + +pub fn has_spellcheck(extra_checks: Option<&str>) -> bool { + let lint_args = parse_extra_checks(extra_checks); + lint_args.iter().any(|arg| arg.matches(ExtraCheckLang::Spellcheck, ExtraCheckKind::None)) +} + fn run_ruff( root_path: &Path, outdir: &Path, @@ -613,15 +629,24 @@ fn shellcheck_runner(args: &[&OsStr]) -> Result<(), Error> { if status.success() { Ok(()) } else { Err(Error::FailedCheck("shellcheck")) } } -/// Ensure that spellchecker is installed then run it at the given path -fn spellcheck_runner( +pub fn build_spellcheck_args(root_path: &Path, bless: bool) -> Vec { + let config_path = root_path.join(SPELLCHECK_CONFIG).as_os_str().to_str().unwrap().to_string(); + + let mut args = vec!["-c".to_string(), config_path]; + args.extend(SPELLCHECK_DIRS.iter().map(|s| s.to_string())); + + if bless { + args.push("--write-changes".to_string()); + } + + args +} + +pub fn spellcheck_runner( src_root: &Path, - outdir: &Path, - cargo: &Path, - args: &[&str], + bin_path: &PathBuf, + args: Vec, ) -> Result<(), Error> { - let bin_path = - crate::ensure_version_or_cargo_install(outdir, cargo, "typos-cli", "typos", "1.38.1")?; match Command::new(bin_path).current_dir(src_root).args(args).status() { Ok(status) => { if status.success() { @@ -676,7 +701,7 @@ fn find_with_extension( } #[derive(Debug)] -enum Error { +pub enum Error { Io(io::Error), /// a is required to run b. c is extra info MissingReq(&'static str, &'static str, Option), diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs index 756f9790e04ad..c522829b7301c 100644 --- a/src/tools/tidy/src/lib.rs +++ b/src/tools/tidy/src/lib.rs @@ -158,6 +158,32 @@ pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool { !v.is_empty() } +/// Check if the given executable is installed, and the version is expected. +pub fn ensure_version(build_dir: &Path, bin_name: &str, version: &str) -> io::Result { + let tool_root_dir = build_dir.join("misc-tools"); + let tool_bin_dir = tool_root_dir.join("bin"); + let bin_path = tool_bin_dir.join(bin_name); + + match Command::new(&bin_path).arg("--version").output() { + Ok(output) => { + let Some(v) = str::from_utf8(&output.stdout).unwrap().trim().split_whitespace().last() + else { + return Err(io::Error::other("version check failed")); + }; + + if v != version { + eprintln!( + "warning: the tool `{bin_name}` is detected, but version {v} doesn't match with the expected version {version}" + ); + + return Err(io::Error::other("version is not expected")); + } + Ok(bin_path) + } + Err(e) => Err(e), + } +} + /// If the given executable is installed with the given version, use that, /// otherwise install via cargo. pub fn ensure_version_or_cargo_install( @@ -253,6 +279,7 @@ pub mod rustdoc_css_themes; pub mod rustdoc_gui_tests; pub mod rustdoc_json; pub mod rustdoc_templates; +pub mod spellcheck; pub mod style; pub mod target_policy; pub mod target_specific_tests; diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs index 94c24f11ed12f..d898a300ba45a 100644 --- a/src/tools/tidy/src/main.rs +++ b/src/tools/tidy/src/main.rs @@ -12,6 +12,7 @@ use std::thread::{self, ScopedJoinHandle, scope}; use std::{env, process}; use tidy::diagnostics::{COLOR_ERROR, COLOR_SUCCESS, TidyCtx, TidyFlags, output_message}; +use tidy::extra_checks::has_spellcheck; use tidy::*; fn main() { @@ -133,6 +134,11 @@ fn main() { check!(bins, &root_path); } + // If extra-checks option has spellcheck, do nothing here because it is redundant. + if !has_spellcheck(extra_checks) { + check!(spellcheck, &root_path, &output_directory); + } + check!(style, &src_path); check!(style, &tests_path); check!(style, &compiler_path); diff --git a/src/tools/tidy/src/spellcheck.rs b/src/tools/tidy/src/spellcheck.rs new file mode 100644 index 0000000000000..527665bd9bdac --- /dev/null +++ b/src/tools/tidy/src/spellcheck.rs @@ -0,0 +1,31 @@ +use std::path::Path; + +use crate::diagnostics::TidyCtx; +use crate::extra_checks::{ + SPELLCHECK_BIN_NAME, SPELLCHECK_VERSION, build_spellcheck_args, spellcheck_runner, +}; + +/// Check executes spellchecker only if the tool is installed and its version is expected. +pub fn check(root_path: &Path, outdir: &Path, tidy_ctx: TidyCtx) { + let mut check = tidy_ctx.start_check("spellcheck"); + + let args = build_spellcheck_args(root_path, false); + + match crate::ensure_version(outdir, SPELLCHECK_BIN_NAME, SPELLCHECK_VERSION) { + Ok(bin_path) => { + eprintln!("Spellchecker is detected. Run it automatially"); + if let Err(e) = spellcheck_runner(root_path, &bin_path, args) { + check.error(e); + } + } + Err(e) => { + // If the tool is not found, do nothing. + if e.kind() != std::io::ErrorKind::NotFound { + eprintln!( + "error: spellchecker has a problem. --extra-check=specllcheck may solve the problem." + ); + check.error(e); + } + } + } +}