diff options
| author | Philipp Krones <hello@philkrones.com> | 2022-06-30 10:50:09 +0200 |
|---|---|---|
| committer | Philipp Krones <hello@philkrones.com> | 2022-06-30 10:50:09 +0200 |
| commit | 09f5df5087c1d045db3bbf1b886702ec343a2f61 (patch) | |
| tree | 5f7eecd245841b64223f85e99b269c9dd3b55f18 /clippy_dev/src | |
| parent | ee37029afa62e93ab5ec8b1f2a23f8dbfd2a453f (diff) | |
| download | rust-09f5df5087c1d045db3bbf1b886702ec343a2f61.tar.gz rust-09f5df5087c1d045db3bbf1b886702ec343a2f61.zip | |
Merge commit '0cb0f7636851f9fcc57085cf80197a2ef6db098f' into clippyup
Diffstat (limited to 'clippy_dev/src')
| -rw-r--r-- | clippy_dev/src/main.rs | 18 | ||||
| -rw-r--r-- | clippy_dev/src/new_lint.rs | 2 | ||||
| -rw-r--r-- | clippy_dev/src/update_lints.rs | 395 |
3 files changed, 373 insertions, 42 deletions
diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 2c27a0bcaf9..243a901503f 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -5,6 +5,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue}; use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints}; use indoc::indoc; + fn main() { let matches = get_clap_config(); @@ -85,6 +86,11 @@ fn main() { let uplift = matches.contains_id("uplift"); update_lints::rename(old_name, new_name, uplift); }, + Some(("deprecate", matches)) => { + let name = matches.get_one::<String>("name").unwrap(); + let reason = matches.get_one("reason"); + update_lints::deprecate(name, reason); + }, _ => {}, } } @@ -266,6 +272,18 @@ fn get_clap_config() -> ArgMatches { .long("uplift") .help("This lint will be uplifted into rustc"), ]), + Command::new("deprecate").about("Deprecates the given lint").args([ + Arg::new("name") + .index(1) + .required(true) + .help("The name of the lint to deprecate"), + Arg::new("reason") + .long("reason") + .short('r') + .required(false) + .takes_value(true) + .help("The reason for deprecation"), + ]), ]) .get_matches() } diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 748d73c0801..7d7e760ef44 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -138,7 +138,7 @@ fn to_camel_case(name: &str) -> String { .collect() } -fn get_stabilization_version() -> String { +pub(crate) fn get_stabilization_version() -> String { fn parse_manifest(contents: &str) -> Option<String> { let version = contents .lines() diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 1bbd9a45b61..2e0659f42d7 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,16 +1,17 @@ +use crate::clippy_project_root; use aho_corasick::AhoCorasickBuilder; -use core::fmt::Write as _; +use indoc::writedoc; use itertools::Itertools; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; -use std::fs; -use std::io::{self, Read as _, Seek as _, Write as _}; +use std::fmt::Write; +use std::fs::{self, OpenOptions}; +use std::io::{self, Read, Seek, SeekFrom, Write as _}; +use std::ops::Range; use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; -use crate::clippy_project_root; - const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ // Use that command to update this file and do not edit by hand.\n\ // Manual edits will be overwritten.\n\n"; @@ -326,6 +327,200 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { println!("note: `cargo uitest` still needs to be run to update the test results"); } +const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note"; +/// Runs the `deprecate` command +/// +/// This does the following: +/// * Adds an entry to `deprecated_lints.rs`. +/// * Removes the lint declaration (and the entire file if applicable) +/// +/// # Panics +/// +/// If a file path could not read from or written to +pub fn deprecate(name: &str, reason: Option<&String>) { + fn finish( + (lints, mut deprecated_lints, renamed_lints): (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>), + name: &str, + reason: &str, + ) { + deprecated_lints.push(DeprecatedLint { + name: name.to_string(), + reason: reason.to_string(), + declaration_range: Range::default(), + }); + + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("info: `{}` has successfully been deprecated", name); + + if reason == DEFAULT_DEPRECATION_REASON { + println!("note: the deprecation reason must be updated in `clippy_lints/src/deprecated_lints.rs`"); + } + println!("note: you must run `cargo uitest` to update the test results"); + } + + let reason = reason.map_or(DEFAULT_DEPRECATION_REASON, String::as_str); + let name_lower = name.to_lowercase(); + let name_upper = name.to_uppercase(); + + let (mut lints, deprecated_lints, renamed_lints) = gather_all(); + let Some(lint) = lints.iter().find(|l| l.name == name_lower) else { eprintln!("error: failed to find lint `{}`", name); return; }; + + let mod_path = { + let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module)); + if mod_path.is_dir() { + mod_path = mod_path.join("mod"); + } + + mod_path.set_extension("rs"); + mod_path + }; + + let deprecated_lints_path = &*clippy_project_root().join("clippy_lints/src/deprecated_lints.rs"); + + if remove_lint_declaration(&name_lower, &mod_path, &mut lints).unwrap_or(false) { + declare_deprecated(&name_upper, deprecated_lints_path, reason).unwrap(); + finish((lints, deprecated_lints, renamed_lints), name, reason); + return; + } + + eprintln!("error: lint not found"); +} + +fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> { + fn remove_lint(name: &str, lints: &mut Vec<Lint>) { + lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); + } + + fn remove_test_assets(name: &str) { + let test_file_stem = format!("tests/ui/{}", name); + let path = Path::new(&test_file_stem); + + // Some lints have their own directories, delete them + if path.is_dir() { + fs::remove_dir_all(path).ok(); + return; + } + + // Remove all related test files + fs::remove_file(path.with_extension("rs")).ok(); + fs::remove_file(path.with_extension("stderr")).ok(); + fs::remove_file(path.with_extension("fixed")).ok(); + } + + fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) { + let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| { + content + .find("declare_lint_pass!") + .unwrap_or_else(|| panic!("failed to find `impl_lint_pass`")) + }); + let mut impl_lint_pass_end = content[impl_lint_pass_start..] + .find(']') + .expect("failed to find `impl_lint_pass` terminator"); + + impl_lint_pass_end += impl_lint_pass_start; + if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(&lint_name_upper) { + let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len()); + for c in content[lint_name_end..impl_lint_pass_end].chars() { + // Remove trailing whitespace + if c == ',' || c.is_whitespace() { + lint_name_end += 1; + } else { + break; + } + } + + content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, ""); + } + } + + if path.exists() { + if let Some(lint) = lints.iter().find(|l| l.name == name) { + if lint.module == name { + // The lint name is the same as the file, we can just delete the entire file + fs::remove_file(path)?; + } else { + // We can't delete the entire file, just remove the declaration + + if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) { + // Remove clippy_lints/src/some_mod/some_lint.rs + let mut lint_mod_path = path.to_path_buf(); + lint_mod_path.set_file_name(name); + lint_mod_path.set_extension("rs"); + + fs::remove_file(lint_mod_path).ok(); + } + + let mut content = + fs::read_to_string(&path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); + + eprintln!( + "warn: you will have to manually remove any code related to `{}` from `{}`", + name, + path.display() + ); + + assert!( + content[lint.declaration_range.clone()].contains(&name.to_uppercase()), + "error: `{}` does not contain lint `{}`'s declaration", + path.display(), + lint.name + ); + + // Remove lint declaration (declare_clippy_lint!) + content.replace_range(lint.declaration_range.clone(), ""); + + // Remove the module declaration (mod xyz;) + let mod_decl = format!("\nmod {};", name); + content = content.replacen(&mod_decl, "", 1); + + remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content); + fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy())); + } + + remove_test_assets(name); + remove_lint(name, lints); + return Ok(true); + } + } + + Ok(false) +} + +fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> { + let mut file = OpenOptions::new().write(true).open(path)?; + + file.seek(SeekFrom::End(0))?; + + let version = crate::new_lint::get_stabilization_version(); + let deprecation_reason = if reason == DEFAULT_DEPRECATION_REASON { + "TODO" + } else { + reason + }; + + writedoc!( + file, + " + + declare_deprecated_lint! {{ + /// ### What it does + /// Nothing. This lint has been deprecated. + /// + /// ### Deprecation reason + /// {} + #[clippy::version = \"{}\"] + pub {}, + \"{}\" + }} + + ", + deprecation_reason, + version, + name, + reason, + ) +} + /// 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> { @@ -393,16 +588,18 @@ struct Lint { group: String, desc: String, module: String, + declaration_range: Range<usize>, } impl Lint { #[must_use] - fn new(name: &str, group: &str, desc: &str, module: &str) -> Self { + fn new(name: &str, group: &str, desc: &str, module: &str, declaration_range: Range<usize>) -> Self { Self { name: name.to_lowercase(), group: group.into(), desc: remove_line_splices(desc), module: module.into(), + declaration_range, } } @@ -433,12 +630,14 @@ impl Lint { struct DeprecatedLint { name: String, reason: String, + declaration_range: Range<usize>, } impl DeprecatedLint { - fn new(name: &str, reason: &str) -> Self { + fn new(name: &str, reason: &str, declaration_range: Range<usize>) -> Self { Self { name: name.to_lowercase(), reason: remove_line_splices(reason), + declaration_range, } } } @@ -610,7 +809,11 @@ fn clippy_lints_src_files() -> impl Iterator<Item = (PathBuf, DirEntry)> { macro_rules! match_tokens { ($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => { { - $($(let $capture =)? if let Some((TokenKind::$token $({$($fields)*})?, _x)) = $iter.next() { + $($(let $capture =)? if let Some(LintDeclSearchResult { + token_kind: TokenKind::$token $({$($fields)*})?, + content: _x, + .. + }) = $iter.next() { _x } else { continue; @@ -621,40 +824,72 @@ macro_rules! match_tokens { } } +struct LintDeclSearchResult<'a> { + token_kind: TokenKind, + content: &'a str, + range: Range<usize>, +} + /// Parse a source file looking for `declare_clippy_lint` macro invocations. fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) { let mut offset = 0usize; let mut iter = tokenize(contents).map(|t| { let range = offset..offset + t.len; offset = range.end; - (t.kind, &contents[range]) + + LintDeclSearchResult { + token_kind: t.kind, + content: &contents[range.clone()], + range, + } }); - while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_clippy_lint") { + while let Some(LintDeclSearchResult { range, .. }) = iter.find( + |LintDeclSearchResult { + token_kind, content, .. + }| token_kind == &TokenKind::Ident && *content == "declare_clippy_lint", + ) { + let start = range.start; + let mut iter = iter .by_ref() - .filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); + .filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); // matches `!{` match_tokens!(iter, Bang OpenBrace); match iter.next() { // #[clippy::version = "version"] pub - Some((TokenKind::Pound, _)) => { + Some(LintDeclSearchResult { + token_kind: TokenKind::Pound, + .. + }) => { match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident); }, // pub - Some((TokenKind::Ident, _)) => (), + Some(LintDeclSearchResult { + token_kind: TokenKind::Ident, + .. + }) => (), _ => continue, } + let (name, group, desc) = match_tokens!( iter, // LINT_NAME Ident(name) Comma // group, Ident(group) Comma - // "description" } - Literal{..}(desc) CloseBrace + // "description" + Literal{..}(desc) ); - lints.push(Lint::new(name, group, desc, module)); + + if let Some(LintDeclSearchResult { + token_kind: TokenKind::CloseBrace, + range, + .. + }) = iter.next() + { + lints.push(Lint::new(name, group, desc, module, start..range.end)); + } } } @@ -664,12 +899,24 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) { let mut iter = tokenize(contents).map(|t| { let range = offset..offset + t.len; offset = range.end; - (t.kind, &contents[range]) + + LintDeclSearchResult { + token_kind: t.kind, + content: &contents[range.clone()], + range, + } }); - while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_deprecated_lint") { - let mut iter = iter - .by_ref() - .filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); + + while let Some(LintDeclSearchResult { range, .. }) = iter.find( + |LintDeclSearchResult { + token_kind, content, .. + }| token_kind == &TokenKind::Ident && *content == "declare_deprecated_lint", + ) { + let start = range.start; + + let mut iter = iter.by_ref().filter(|LintDeclSearchResult { ref token_kind, .. }| { + !matches!(token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }) + }); let (name, reason) = match_tokens!( iter, // !{ @@ -680,10 +927,16 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) { Ident Ident(name) Comma // "description" Literal{kind: LiteralKind::Str{..},..}(reason) - // } - CloseBrace ); - lints.push(DeprecatedLint::new(name, reason)); + + if let Some(LintDeclSearchResult { + token_kind: TokenKind::CloseBrace, + range, + .. + }) = iter.next() + { + lints.push(DeprecatedLint::new(name, reason, start..range.end)); + } } } @@ -693,8 +946,14 @@ fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) { let mut iter = tokenize(line).map(|t| { let range = offset..offset + t.len; offset = range.end; - (t.kind, &line[range]) + + LintDeclSearchResult { + token_kind: t.kind, + content: &line[range.clone()], + range, + } }); + let (old_name, new_name) = match_tokens!( iter, // ("old_name", @@ -844,10 +1103,25 @@ mod tests { "#; let mut result = Vec::new(); parse_contents(CONTENTS, "module_name", &mut result); + for r in &mut result { + r.declaration_range = Range::default(); + } let expected = vec![ - Lint::new("ptr_arg", "style", "\"really long text\"", "module_name"), - Lint::new("doc_markdown", "pedantic", "\"single line\"", "module_name"), + Lint::new( + "ptr_arg", + "style", + "\"really long text\"", + "module_name", + Range::default(), + ), + Lint::new( + "doc_markdown", + "pedantic", + "\"single line\"", + "module_name", + Range::default(), + ), ]; assert_eq!(expected, result); } @@ -865,10 +1139,14 @@ mod tests { let mut result = Vec::new(); parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result); + for r in &mut result { + r.declaration_range = Range::default(); + } let expected = vec![DeprecatedLint::new( "should_assert_eq", "\"`assert!()` will be more flexible with RFC 2011\"", + Range::default(), )]; assert_eq!(expected, result); } @@ -876,15 +1154,34 @@ mod tests { #[test] fn test_usable_lints() { let lints = vec![ - Lint::new("should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name"), - Lint::new("should_assert_eq2", "internal", "\"abc\"", "module_name"), - Lint::new("should_assert_eq2", "internal_style", "\"abc\"", "module_name"), + Lint::new( + "should_assert_eq2", + "Not Deprecated", + "\"abc\"", + "module_name", + Range::default(), + ), + Lint::new( + "should_assert_eq2", + "internal", + "\"abc\"", + "module_name", + Range::default(), + ), + Lint::new( + "should_assert_eq2", + "internal_style", + "\"abc\"", + "module_name", + Range::default(), + ), ]; let expected = vec![Lint::new( "should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name", + Range::default(), )]; assert_eq!(expected, Lint::usable_lints(&lints)); } @@ -892,21 +1189,33 @@ mod tests { #[test] fn test_by_lint_group() { let lints = vec![ - Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), - Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name"), - Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"), + Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), + Lint::new( + "should_assert_eq2", + "group2", + "\"abc\"", + "module_name", + Range::default(), + ), + Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()), ]; let mut expected: HashMap<String, Vec<Lint>> = HashMap::new(); expected.insert( "group1".to_string(), vec![ - Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), - Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"), + Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), + Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()), ], ); expected.insert( "group2".to_string(), - vec![Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name")], + vec![Lint::new( + "should_assert_eq2", + "group2", + "\"abc\"", + "module_name", + Range::default(), + )], ); assert_eq!(expected, Lint::by_lint_group(lints.into_iter())); } @@ -914,8 +1223,12 @@ mod tests { #[test] fn test_gen_deprecated() { let lints = vec![ - DeprecatedLint::new("should_assert_eq", "\"has been superseded by should_assert_eq2\""), - DeprecatedLint::new("another_deprecated", "\"will be removed\""), + DeprecatedLint::new( + "should_assert_eq", + "\"has been superseded by should_assert_eq2\"", + Range::default(), + ), + DeprecatedLint::new("another_deprecated", "\"will be removed\"", Range::default()), ]; let expected = GENERATED_FILE_COMMENT.to_string() @@ -940,9 +1253,9 @@ mod tests { #[test] fn test_gen_lint_group_list() { let lints = vec![ - Lint::new("abc", "group1", "\"abc\"", "module_name"), - Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), - Lint::new("internal", "internal_style", "\"abc\"", "module_name"), + Lint::new("abc", "group1", "\"abc\"", "module_name", Range::default()), + Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), + Lint::new("internal", "internal_style", "\"abc\"", "module_name", Range::default()), ]; let expected = GENERATED_FILE_COMMENT.to_string() + &[ |
