]> git.lizzy.rs Git - rust.git/blobdiff - src/tools/clippy/clippy_dev/src/update_lints.rs
Rollup merge of #98585 - cuviper:covariant-thinbox, r=thomcc
[rust.git] / src / tools / clippy / clippy_dev / src / update_lints.rs
index 1bbd9a45b619e448daea496eb77f63fa6693756f..2e0659f42d7b6b85ea248f4e45a91513c70ed2ca 100644 (file)
@@ -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 @@ fn by_lint_group(lints: impl Iterator<Item = Self>) -> HashMap<String, Vec<Self>
 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 @@ fn test_parse_contents() {
         "#;
         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 @@ fn test_parse_deprecated_contents() {
 
         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 @@ fn test_parse_deprecated_contents() {
     #[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 @@ fn test_usable_lints() {
     #[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 @@ fn test_by_lint_group() {
     #[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 @@ fn test_gen_deprecated() {
     #[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()
             + &[