Since Clippy uses the `let_chains` feature, there are many occasions to collapse `if` and `if let` statements.
142 lines
4.5 KiB
Rust
142 lines
4.5 KiB
Rust
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<ExitStatus>) {
|
|
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<String, &'a str> {
|
|
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)
|
|
}
|