use std::path::{Path, PathBuf}; use std::process::{self, ExitStatus}; use std::{fs, io}; #[cfg(not(windows))] static CARGO_CLIPPY_EXE: &str = "cargo-clippy"; #[cfg(windows)] static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe"; /// Returns the path to the `cargo-clippy` binary /// /// # Panics /// /// Panics if the path of current executable could not be retrieved. #[must_use] pub fn cargo_clippy_path() -> PathBuf { let mut path = std::env::current_exe().expect("failed to get current executable name"); path.set_file_name(CARGO_CLIPPY_EXE); path } /// Returns the path to the Clippy project directory /// /// # Panics /// /// Panics if the current directory could not be retrieved, there was an error reading any of the /// Cargo.toml files or ancestor directory is the clippy root directory #[must_use] pub fn clippy_project_root() -> PathBuf { let current_dir = std::env::current_dir().unwrap(); for path in current_dir.ancestors() { let result = fs::read_to_string(path.join("Cargo.toml")); if let Err(err) = &result && err.kind() == io::ErrorKind::NotFound { continue; } let content = result.unwrap(); if content.contains("[package]\nname = \"clippy\"") { return path.to_path_buf(); } } panic!("error: Can't determine root of project. Please run inside a Clippy working dir."); } /// # Panics /// Panics if given command result was failed. pub fn exit_if_err(status: io::Result) { match status.expect("failed to run command").code() { Some(0) => {}, Some(n) => process::exit(n), None => { eprintln!("Killed by signal"); process::exit(1); }, } } pub(crate) fn clippy_version() -> (u32, u32) { fn parse_manifest(contents: &str) -> Option<(u32, u32)> { let version = contents .lines() .filter_map(|l| l.split_once('=')) .find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))?; let Some(("0", version)) = version.get(1..version.len() - 1)?.split_once('.') else { return None; }; let (minor, patch) = version.split_once('.')?; Some((minor.parse().ok()?, patch.parse().ok()?)) } let contents = fs::read_to_string("Cargo.toml").expect("Unable to read `Cargo.toml`"); parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`") } #[derive(Clone, Copy, PartialEq, Eq)] pub enum UpdateMode { Check, Change, } pub(crate) fn exit_with_failure() { println!( "Not all lints defined properly. \ Please run `cargo dev update_lints` to make sure all lints are defined properly." ); process::exit(1); } /// Replaces a region in a file delimited by two lines matching regexes. /// /// `path` is the relative path to the file on which you want to perform the replacement. /// /// See `replace_region_in_text` for documentation of the other options. /// /// # Panics /// /// Panics if the path could not read or then written pub(crate) fn replace_region_in_file( update_mode: UpdateMode, path: &Path, start: &str, end: &str, write_replacement: impl FnMut(&mut String), ) { let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {e}", path.display())); let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) { Ok(x) => x, Err(delim) => panic!("Couldn't find `{delim}` in file `{}`", path.display()), }; match update_mode { UpdateMode::Check if contents != new_contents => exit_with_failure(), UpdateMode::Check => (), UpdateMode::Change => { if let Err(e) = fs::write(path, new_contents.as_bytes()) { panic!("Cannot write to `{}`: {e}", path.display()); } }, } } /// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters /// were found, or the missing delimiter if not. pub(crate) fn replace_region_in_text<'a>( text: &str, start: &'a str, end: &'a str, mut write_replacement: impl FnMut(&mut String), ) -> Result { let (text_start, rest) = text.split_once(start).ok_or(start)?; let (_, text_end) = rest.split_once(end).ok_or(end)?; let mut res = String::with_capacity(text.len() + 4096); res.push_str(text_start); res.push_str(start); write_replacement(&mut res); res.push_str(end); res.push_str(text_end); Ok(res) }