diff options
| author | flip1995 <philipp.krones@embecosm.com> | 2022-05-05 15:12:52 +0100 |
|---|---|---|
| committer | flip1995 <philipp.krones@embecosm.com> | 2022-05-05 15:12:52 +0100 |
| commit | 7cd86aa1be18d608d828239c11e887a762efc92a (patch) | |
| tree | f2f098e889406b343beba0dbbf58e492c4e5dff6 /clippy_dev/src | |
| parent | 82f469f81b6daafb448e36c0e811cf2d40836edb (diff) | |
| download | rust-7cd86aa1be18d608d828239c11e887a762efc92a.tar.gz rust-7cd86aa1be18d608d828239c11e887a762efc92a.zip | |
Merge commit '7c21f91b15b7604f818565646b686d90f99d1baf' into clippyup
Diffstat (limited to 'clippy_dev/src')
| -rw-r--r-- | clippy_dev/src/lib.rs | 1 | ||||
| -rw-r--r-- | clippy_dev/src/main.rs | 84 | ||||
| -rw-r--r-- | clippy_dev/src/new_lint.rs | 6 | ||||
| -rw-r--r-- | clippy_dev/src/setup/mod.rs | 4 | ||||
| -rw-r--r-- | clippy_dev/src/update_lints.rs | 405 |
5 files changed, 455 insertions, 45 deletions
diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 9c6d754b400..81e807cf10c 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(let_chains)] #![feature(let_else)] #![feature(once_cell)] #![feature(rustc_private)] diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index b1fe35a0243..ebf8f38d490 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -19,9 +19,9 @@ fn main() { if matches.is_present("print-only") { update_lints::print_lints(); } else if matches.is_present("check") { - update_lints::run(update_lints::UpdateMode::Check); + update_lints::update(update_lints::UpdateMode::Check); } else { - update_lints::run(update_lints::UpdateMode::Change); + update_lints::update(update_lints::UpdateMode::Change); } }, ("new_lint", Some(matches)) => { @@ -31,18 +31,36 @@ fn main() { matches.value_of("category"), matches.is_present("msrv"), ) { - Ok(_) => update_lints::run(update_lints::UpdateMode::Change), + Ok(_) => update_lints::update(update_lints::UpdateMode::Change), Err(e) => eprintln!("Unable to create lint: {}", e), } }, ("setup", Some(sub_command)) => match sub_command.subcommand() { - ("intellij", Some(matches)) => setup::intellij::setup_rustc_src( - matches - .value_of("rustc-repo-path") - .expect("this field is mandatory and therefore always valid"), - ), - ("git-hook", Some(matches)) => setup::git_hook::install_hook(matches.is_present("force-override")), - ("vscode-tasks", Some(matches)) => setup::vscode::install_tasks(matches.is_present("force-override")), + ("intellij", Some(matches)) => { + if matches.is_present("remove") { + setup::intellij::remove_rustc_src(); + } else { + setup::intellij::setup_rustc_src( + matches + .value_of("rustc-repo-path") + .expect("this field is mandatory and therefore always valid"), + ); + } + }, + ("git-hook", Some(matches)) => { + if matches.is_present("remove") { + setup::git_hook::remove_hook(); + } else { + setup::git_hook::install_hook(matches.is_present("force-override")); + } + }, + ("vscode-tasks", Some(matches)) => { + if matches.is_present("remove") { + setup::vscode::remove_tasks(); + } else { + setup::vscode::install_tasks(matches.is_present("force-override")); + } + }, _ => {}, }, ("remove", Some(sub_command)) => match sub_command.subcommand() { @@ -60,6 +78,12 @@ fn main() { let path = matches.value_of("path").unwrap(); lint::run(path); }, + ("rename_lint", Some(matches)) => { + let old_name = matches.value_of("old_name").unwrap(); + let new_name = matches.value_of("new_name").unwrap_or(old_name); + let uplift = matches.is_present("uplift"); + update_lints::rename(old_name, new_name, uplift); + }, _ => {}, } } @@ -168,12 +192,19 @@ fn get_clap_config<'a>() -> ArgMatches<'a> { SubCommand::with_name("intellij") .about("Alter dependencies so Intellij Rust can find rustc internals") .arg( + Arg::with_name("remove") + .long("remove") + .help("Remove the dependencies added with 'cargo dev setup intellij'") + .required(false), + ) + .arg( Arg::with_name("rustc-repo-path") .long("repo-path") .short("r") .help("The path to a rustc repo that will be used for setting the dependencies") .takes_value(true) .value_name("path") + .conflicts_with("remove") .required(true), ), ) @@ -181,6 +212,12 @@ fn get_clap_config<'a>() -> ArgMatches<'a> { SubCommand::with_name("git-hook") .about("Add a pre-commit git hook that formats your code to make it look pretty") .arg( + Arg::with_name("remove") + .long("remove") + .help("Remove the pre-commit hook added with 'cargo dev setup git-hook'") + .required(false), + ) + .arg( Arg::with_name("force-override") .long("force-override") .short("f") @@ -192,6 +229,12 @@ fn get_clap_config<'a>() -> ArgMatches<'a> { SubCommand::with_name("vscode-tasks") .about("Add several tasks to vscode for formatting, validation and testing") .arg( + Arg::with_name("remove") + .long("remove") + .help("Remove the tasks added with 'cargo dev setup vscode-tasks'") + .required(false), + ) + .arg( Arg::with_name("force-override") .long("force-override") .short("f") @@ -242,5 +285,26 @@ fn get_clap_config<'a>() -> ArgMatches<'a> { .help("The path to a file or package directory to lint"), ), ) + .subcommand( + SubCommand::with_name("rename_lint") + .about("Renames the given lint") + .arg( + Arg::with_name("old_name") + .index(1) + .required(true) + .help("The name of the lint to rename"), + ) + .arg( + Arg::with_name("new_name") + .index(2) + .required_unless("uplift") + .help("The new name of the lint"), + ) + .arg( + Arg::with_name("uplift") + .long("uplift") + .help("This lint will be uplifted into rustc"), + ), + ) .get_matches() } diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 7a3fd131761..10f67d301f8 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,5 +1,6 @@ use crate::clippy_project_root; use indoc::indoc; +use std::fmt::Write as _; use std::fs::{self, OpenOptions}; use std::io::prelude::*; use std::io::{self, ErrorKind}; @@ -232,7 +233,8 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { ) }); - result.push_str(&format!( + let _ = write!( + result, indoc! {r#" declare_clippy_lint! {{ /// ### What it does @@ -256,7 +258,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { version = version, name_upper = name_upper, category = category, - )); + ); result.push_str(&if enable_msrv { format!( diff --git a/clippy_dev/src/setup/mod.rs b/clippy_dev/src/setup/mod.rs index a1e4dd103b8..f691ae4fa45 100644 --- a/clippy_dev/src/setup/mod.rs +++ b/clippy_dev/src/setup/mod.rs @@ -7,7 +7,7 @@ use std::path::Path; const CLIPPY_DEV_DIR: &str = "clippy_dev"; /// This function verifies that the tool is being executed in the clippy directory. -/// This is useful to ensure that setups only modify Clippys resources. The verification +/// This is useful to ensure that setups only modify Clippy's resources. The verification /// is done by checking that `clippy_dev` is a sub directory of the current directory. /// /// It will print an error message and return `false` if the directory could not be @@ -17,7 +17,7 @@ fn verify_inside_clippy_dir() -> bool { if path.exists() && path.is_dir() { true } else { - eprintln!("error: unable to verify that the working directory is clippys directory"); + eprintln!("error: unable to verify that the working directory is clippy's directory"); false } } diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 59db51fbfac..1a6a4336da2 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,11 +1,13 @@ -use core::fmt::Write; +use aho_corasick::AhoCorasickBuilder; +use core::fmt::Write as _; use itertools::Itertools; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; use std::fs; -use std::path::Path; -use walkdir::WalkDir; +use std::io::{self, Read as _, Seek as _, Write as _}; +use std::path::{Path, PathBuf}; +use walkdir::{DirEntry, WalkDir}; use crate::clippy_project_root; @@ -30,12 +32,19 @@ pub enum UpdateMode { /// # Panics /// /// Panics if a file path could not read from or then written to -#[allow(clippy::too_many_lines)] -pub fn run(update_mode: UpdateMode) { - let (lints, deprecated_lints) = gather_all(); +pub fn update(update_mode: UpdateMode) { + let (lints, deprecated_lints, renamed_lints) = gather_all(); + generate_lint_files(update_mode, &lints, &deprecated_lints, &renamed_lints); +} - let internal_lints = Lint::internal_lints(&lints); - let usable_lints = Lint::usable_lints(&lints); +fn generate_lint_files( + update_mode: UpdateMode, + lints: &[Lint], + deprecated_lints: &[DeprecatedLint], + renamed_lints: &[RenamedLint], +) { + let internal_lints = Lint::internal_lints(lints); + let usable_lints = Lint::usable_lints(lints); let mut sorted_usable_lints = usable_lints.clone(); sorted_usable_lints.sort_by_key(|lint| lint.name.clone()); @@ -87,7 +96,7 @@ pub fn run(update_mode: UpdateMode) { process_file( "clippy_lints/src/lib.deprecated.rs", update_mode, - &gen_deprecated(&deprecated_lints), + &gen_deprecated(deprecated_lints), ); let all_group_lints = usable_lints.iter().filter(|l| { @@ -107,10 +116,16 @@ pub fn run(update_mode: UpdateMode) { &content, ); } + + let content = gen_deprecated_lints_test(deprecated_lints); + process_file("tests/ui/deprecated.rs", update_mode, &content); + + let content = gen_renamed_lints_test(renamed_lints); + process_file("tests/ui/rename.rs", update_mode, &content); } pub fn print_lints() { - let (lint_list, _) = gather_all(); + let (lint_list, _, _) = gather_all(); let usable_lints = Lint::usable_lints(&lint_list); let usable_lint_count = usable_lints.len(); let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter()); @@ -128,6 +143,209 @@ pub fn print_lints() { println!("there are {} lints", usable_lint_count); } +/// Runs the `rename_lint` command. +/// +/// This does the following: +/// * Adds an entry to `renamed_lints.rs`. +/// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`). +/// * Renames the lint struct to the new name. +/// * Renames the module containing the lint struct to the new name if it shares a name with the +/// lint. +/// +/// # Panics +/// Panics for the following conditions: +/// * If a file path could not read from or then written to +/// * If either lint name has a prefix +/// * If `old_name` doesn't name an existing lint. +/// * If `old_name` names a deprecated or renamed lint. +#[allow(clippy::too_many_lines)] +pub fn rename(old_name: &str, new_name: &str, uplift: bool) { + if let Some((prefix, _)) = old_name.split_once("::") { + panic!("`{}` should not contain the `{}` prefix", old_name, prefix); + } + if let Some((prefix, _)) = new_name.split_once("::") { + panic!("`{}` should not contain the `{}` prefix", new_name, prefix); + } + + let (mut lints, deprecated_lints, mut renamed_lints) = gather_all(); + let mut old_lint_index = None; + let mut found_new_name = false; + for (i, lint) in lints.iter().enumerate() { + if lint.name == old_name { + old_lint_index = Some(i); + } else if lint.name == new_name { + found_new_name = true; + } + } + let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{}`", old_name)); + + let lint = RenamedLint { + old_name: format!("clippy::{}", old_name), + new_name: if uplift { + new_name.into() + } else { + format!("clippy::{}", new_name) + }, + }; + + // Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in + // case. + assert!( + !renamed_lints.iter().any(|l| lint.old_name == l.old_name), + "`{}` has already been renamed", + old_name + ); + assert!( + !deprecated_lints.iter().any(|l| lint.old_name == l.name), + "`{}` has already been deprecated", + old_name + ); + + // Update all lint level attributes. (`clippy::lint_name`) + for file in WalkDir::new(clippy_project_root()) + .into_iter() + .map(Result::unwrap) + .filter(|f| { + let name = f.path().file_name(); + let ext = f.path().extension(); + (ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed"))) + && name != Some(OsStr::new("rename.rs")) + && name != Some(OsStr::new("renamed_lints.rs")) + }) + { + rewrite_file(file.path(), |s| { + replace_ident_like(s, &[(&lint.old_name, &lint.new_name)]) + }); + } + + renamed_lints.push(lint); + renamed_lints.sort_by(|lhs, rhs| { + lhs.new_name + .starts_with("clippy::") + .cmp(&rhs.new_name.starts_with("clippy::")) + .reverse() + .then_with(|| lhs.old_name.cmp(&rhs.old_name)) + }); + + write_file( + Path::new("clippy_lints/src/renamed_lints.rs"), + &gen_renamed_lints_list(&renamed_lints), + ); + + if uplift { + write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints)); + println!( + "`{}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually.", + old_name + ); + } else if found_new_name { + write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints)); + println!( + "`{}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually.", + new_name + ); + } else { + // Rename the lint struct and source files sharing a name with the lint. + let lint = &mut lints[old_lint_index]; + let old_name_upper = old_name.to_uppercase(); + let new_name_upper = new_name.to_uppercase(); + lint.name = new_name.into(); + + // Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist. + if try_rename_file( + Path::new(&format!("tests/ui/{}.rs", old_name)), + Path::new(&format!("tests/ui/{}.rs", new_name)), + ) { + try_rename_file( + Path::new(&format!("tests/ui/{}.stderr", old_name)), + Path::new(&format!("tests/ui/{}.stderr", new_name)), + ); + try_rename_file( + Path::new(&format!("tests/ui/{}.fixed", old_name)), + Path::new(&format!("tests/ui/{}.fixed", new_name)), + ); + } + + // Try to rename the file containing the lint if the file name matches the lint's name. + let replacements; + let replacements = if lint.module == old_name + && try_rename_file( + Path::new(&format!("clippy_lints/src/{}.rs", old_name)), + Path::new(&format!("clippy_lints/src/{}.rs", new_name)), + ) { + // Edit the module name in the lint list. Note there could be multiple lints. + for lint in lints.iter_mut().filter(|l| l.module == old_name) { + lint.module = new_name.into(); + } + replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)]; + replacements.as_slice() + } else if !lint.module.contains("::") + // Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs` + && try_rename_file( + Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, old_name)), + Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, new_name)), + ) + { + // Edit the module name in the lint list. Note there could be multiple lints, or none. + let renamed_mod = format!("{}::{}", lint.module, old_name); + for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) { + lint.module = format!("{}::{}", lint.module, new_name); + } + replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)]; + replacements.as_slice() + } else { + replacements = [(&*old_name_upper, &*new_name_upper), ("", "")]; + &replacements[0..1] + }; + + // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being + // renamed. + for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("renamed_lints.rs")) { + rewrite_file(file.path(), |s| replace_ident_like(s, replacements)); + } + + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("{} has been successfully renamed", old_name); + } + + println!("note: `cargo uitest` still needs to be run to update the test results"); +} + +/// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there +/// were no replacements. +fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> { + fn is_ident_char(c: u8) -> bool { + matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_') + } + + let searcher = AhoCorasickBuilder::new() + .dfa(true) + .match_kind(aho_corasick::MatchKind::LeftmostLongest) + .build_with_size::<u16, _, _>(replacements.iter().map(|&(x, _)| x.as_bytes())) + .unwrap(); + + let mut result = String::with_capacity(contents.len() + 1024); + let mut pos = 0; + let mut edited = false; + for m in searcher.find_iter(contents) { + let (old, new) = replacements[m.pattern()]; + result.push_str(&contents[pos..m.start()]); + result.push_str( + if !is_ident_char(contents.as_bytes().get(m.start().wrapping_sub(1)).copied().unwrap_or(0)) + && !is_ident_char(contents.as_bytes().get(m.end()).copied().unwrap_or(0)) + { + edited = true; + new + } else { + old + }, + ); + pos = m.end(); + } + result.push_str(&contents[pos..]); + edited.then(|| result) +} + fn round_to_fifty(count: usize) -> usize { count / 50 * 50 } @@ -210,6 +428,19 @@ impl DeprecatedLint { } } +struct RenamedLint { + old_name: String, + new_name: String, +} +impl RenamedLint { + fn new(old_name: &str, new_name: &str) -> Self { + Self { + old_name: remove_line_splices(old_name), + new_name: remove_line_splices(new_name), + } + } +} + /// Generates the code for registering a group fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lint>) -> String { let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect(); @@ -217,12 +448,13 @@ fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lin let mut output = GENERATED_FILE_COMMENT.to_string(); - output.push_str(&format!( - "store.register_group(true, \"clippy::{0}\", Some(\"clippy_{0}\"), vec![\n", + let _ = writeln!( + output, + "store.register_group(true, \"clippy::{0}\", Some(\"clippy_{0}\"), vec![", group_name - )); + ); for (module, name) in details { - output.push_str(&format!(" LintId::of({}::{}),\n", module, name)); + let _ = writeln!(output, " LintId::of({}::{}),", module, name); } output.push_str("])\n"); @@ -235,7 +467,8 @@ fn gen_deprecated(lints: &[DeprecatedLint]) -> String { let mut output = GENERATED_FILE_COMMENT.to_string(); output.push_str("{\n"); for lint in lints { - output.push_str(&format!( + let _ = write!( + output, concat!( " store.register_removed(\n", " \"clippy::{}\",\n", @@ -243,7 +476,7 @@ fn gen_deprecated(lints: &[DeprecatedLint]) -> String { " );\n" ), lint.name, lint.reason, - )); + ); } output.push_str("}\n"); @@ -269,25 +502,62 @@ fn gen_register_lint_list<'a>( if !is_public { output.push_str(" #[cfg(feature = \"internal\")]\n"); } - output.push_str(&format!(" {}::{},\n", module_name, lint_name)); + let _ = writeln!(output, " {}::{},", module_name, lint_name); } output.push_str("])\n"); output } +fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String { + let mut res: String = GENERATED_FILE_COMMENT.into(); + for lint in lints { + writeln!(res, "#![warn(clippy::{})]", lint.name).unwrap(); + } + res.push_str("\nfn main() {}\n"); + res +} + +fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String { + let mut seen_lints = HashSet::new(); + let mut res: String = GENERATED_FILE_COMMENT.into(); + res.push_str("// run-rustfix\n\n"); + for lint in lints { + if seen_lints.insert(&lint.new_name) { + writeln!(res, "#![allow({})]", lint.new_name).unwrap(); + } + } + seen_lints.clear(); + for lint in lints { + if seen_lints.insert(&lint.old_name) { + writeln!(res, "#![warn({})]", lint.old_name).unwrap(); + } + } + res.push_str("\nfn main() {}\n"); + res +} + +fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String { + const HEADER: &str = "\ + // This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\ + #[rustfmt::skip]\n\ + pub static RENAMED_LINTS: &[(&str, &str)] = &[\n"; + + let mut res = String::from(HEADER); + for lint in lints { + writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap(); + } + res.push_str("];\n"); + res +} + /// Gathers all lints defined in `clippy_lints/src` -fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>) { +fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>) { let mut lints = Vec::with_capacity(1000); let mut deprecated_lints = Vec::with_capacity(50); - let root_path = clippy_project_root().join("clippy_lints/src"); + let mut renamed_lints = Vec::with_capacity(50); - for (rel_path, file) in WalkDir::new(&root_path) - .into_iter() - .map(Result::unwrap) - .filter(|f| f.path().extension() == Some(OsStr::new("rs"))) - .map(|f| (f.path().strip_prefix(&root_path).unwrap().to_path_buf(), f)) - { + for (rel_path, file) in clippy_lints_src_files() { let path = file.path(); let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e)); @@ -305,13 +575,21 @@ fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>) { module.strip_suffix(".rs").unwrap_or(&module) }; - if module == "deprecated_lints" { - parse_deprecated_contents(&contents, &mut deprecated_lints); - } else { - parse_contents(&contents, module, &mut lints); + match module { + "deprecated_lints" => parse_deprecated_contents(&contents, &mut deprecated_lints), + "renamed_lints" => parse_renamed_contents(&contents, &mut renamed_lints), + _ => parse_contents(&contents, module, &mut lints), } } - (lints, deprecated_lints) + (lints, deprecated_lints, renamed_lints) +} + +fn clippy_lints_src_files() -> impl Iterator<Item = (PathBuf, DirEntry)> { + let root_path = clippy_project_root().join("clippy_lints/src"); + let iter = WalkDir::new(&root_path).into_iter(); + iter.map(Result::unwrap) + .filter(|f| f.path().extension() == Some(OsStr::new("rs"))) + .map(move |f| (f.path().strip_prefix(&root_path).unwrap().to_path_buf(), f)) } macro_rules! match_tokens { @@ -394,6 +672,25 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) { } } +fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) { + for line in contents.lines() { + let mut offset = 0usize; + let mut iter = tokenize(line).map(|t| { + let range = offset..offset + t.len; + offset = range.end; + (t.kind, &line[range]) + }); + let (old_name, new_name) = match_tokens!( + iter, + // ("old_name", + Whitespace OpenParen Literal{kind: LiteralKind::Str{..},..}(old_name) Comma + // "new_name"), + Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma + ); + lints.push(RenamedLint::new(old_name, new_name)); + } +} + /// Removes the line splices and surrounding quotes from a string literal fn remove_line_splices(s: &str) -> String { let s = s @@ -462,6 +759,52 @@ fn replace_region_in_text<'a>( Ok(res) } +fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { + match fs::OpenOptions::new().create_new(true).write(true).open(new_name) { + Ok(file) => drop(file), + Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false, + Err(e) => panic_file(e, new_name, "create"), + }; + match fs::rename(old_name, new_name) { + Ok(()) => true, + Err(e) => { + drop(fs::remove_file(new_name)); + if e.kind() == io::ErrorKind::NotFound { + false + } else { + panic_file(e, old_name, "rename"); + } + }, + } +} + +#[allow(clippy::needless_pass_by_value)] +fn panic_file(error: io::Error, name: &Path, action: &str) -> ! { + panic!("failed to {} file `{}`: {}", action, name.display(), error) +} + +fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option<String>) { + let mut file = fs::OpenOptions::new() + .write(true) + .read(true) + .open(path) + .unwrap_or_else(|e| panic_file(e, path, "open")); + let mut buf = String::new(); + file.read_to_string(&mut buf) + .unwrap_or_else(|e| panic_file(e, path, "read")); + if let Some(new_contents) = f(&buf) { + file.rewind().unwrap_or_else(|e| panic_file(e, path, "write")); + file.write_all(new_contents.as_bytes()) + .unwrap_or_else(|e| panic_file(e, path, "write")); + file.set_len(new_contents.len() as u64) + .unwrap_or_else(|e| panic_file(e, path, "write")); + } +} + +fn write_file(path: &Path, contents: &str) { + fs::write(path, contents).unwrap_or_else(|e| panic_file(e, path, "write")); +} + #[cfg(test)] mod tests { use super::*; |
