about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-06-27 00:32:22 +0000
committerbors <bors@rust-lang.org>2022-06-27 00:32:22 +0000
commit57e7e1d7d8a90f75228aa247d6d9fdbb6b4d70d6 (patch)
treeaeeb3ee71314aee0c5c8e5257b302e6a75eca20d
parent88da5f2bb3c4218005f59e7bbdd5c3865069ac77 (diff)
parentaec465cabdeb9a126446ccf791a9cf5ae045c185 (diff)
downloadrust-57e7e1d7d8a90f75228aa247d6d9fdbb6b4d70d6.tar.gz
rust-57e7e1d7d8a90f75228aa247d6d9fdbb6b4d70d6.zip
Auto merge of #8871 - Serial-ATA:cargo-dev-deprecate, r=giraffate
Add `cargo dev deprecate`

changelog: none

I wrote this awhile ago when `regex` was still a dependency. Is it alright to add it back?
-rw-r--r--clippy_dev/Cargo.toml2
-rw-r--r--clippy_dev/src/main.rs18
-rw-r--r--clippy_dev/src/new_lint.rs2
-rw-r--r--clippy_dev/src/update_lints.rs395
-rw-r--r--clippy_lints/src/deprecated_lints.rs15
-rw-r--r--clippy_lints/src/lib.register_internal.rs1
-rw-r--r--clippy_lints/src/lib.register_lints.rs2
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/utils/internal_lints.rs106
-rw-r--r--clippy_lints/src/utils/internal_lints/metadata_collector.rs2
-rw-r--r--tests/compile-test.rs3
-rw-r--r--tests/ui-internal/default_deprecation_reason.rs30
-rw-r--r--tests/ui-internal/default_deprecation_reason.stderr22
13 files changed, 530 insertions, 70 deletions
diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml
index b0d470a2124..2ac3b4fe2ed 100644
--- a/clippy_dev/Cargo.toml
+++ b/clippy_dev/Cargo.toml
@@ -5,7 +5,7 @@ edition = "2021"
 
 [dependencies]
 aho-corasick = "0.7"
-clap = "3.1"
+clap = "3.2"
 indoc = "1.0"
 itertools = "0.10.1"
 opener = "0.5"
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..115f5f0064f 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()
             + &[
diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs
index 5d5ea0f49c8..9aa5af3190f 100644
--- a/clippy_lints/src/deprecated_lints.rs
+++ b/clippy_lints/src/deprecated_lints.rs
@@ -1,16 +1,21 @@
-// NOTE: if you add a deprecated lint in this file, please add a corresponding test in
-// tests/ui/deprecated.rs
+// NOTE: Entries should be created with `cargo dev deprecate`
 
 /// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This
 /// enables the simple extraction of the metadata without changing the current deprecation
 /// declaration.
-pub struct ClippyDeprecatedLint;
+pub struct ClippyDeprecatedLint {
+    #[allow(dead_code)]
+    pub desc: &'static str,
+}
 
+#[macro_export]
 macro_rules! declare_deprecated_lint {
-    { $(#[$attr:meta])* pub $name: ident, $_reason: expr} => {
+    { $(#[$attr:meta])* pub $name: ident, $reason: literal} => {
         $(#[$attr])*
         #[allow(dead_code)]
-        pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {};
+        pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {
+            desc: $reason
+        };
     }
 }
 
diff --git a/clippy_lints/src/lib.register_internal.rs b/clippy_lints/src/lib.register_internal.rs
index 4778f4fdfa7..be63646a12f 100644
--- a/clippy_lints/src/lib.register_internal.rs
+++ b/clippy_lints/src/lib.register_internal.rs
@@ -6,6 +6,7 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
     LintId::of(utils::internal_lints::CLIPPY_LINTS_INTERNAL),
     LintId::of(utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
     LintId::of(utils::internal_lints::COMPILER_LINT_FUNCTIONS),
+    LintId::of(utils::internal_lints::DEFAULT_DEPRECATION_REASON),
     LintId::of(utils::internal_lints::DEFAULT_LINT),
     LintId::of(utils::internal_lints::IF_CHAIN_STYLE),
     LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL),
diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs
index f706ba0620f..cd308a80f46 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -10,6 +10,8 @@ store.register_lints(&[
     #[cfg(feature = "internal")]
     utils::internal_lints::COMPILER_LINT_FUNCTIONS,
     #[cfg(feature = "internal")]
+    utils::internal_lints::DEFAULT_DEPRECATION_REASON,
+    #[cfg(feature = "internal")]
     utils::internal_lints::DEFAULT_LINT,
     #[cfg(feature = "internal")]
     utils::internal_lints::IF_CHAIN_STYLE,
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 172c08a8168..078df64a1e1 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -159,7 +159,7 @@ macro_rules! declare_clippy_lint {
 }
 
 #[cfg(feature = "internal")]
-mod deprecated_lints;
+pub mod deprecated_lints;
 #[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
 mod utils;
 
diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs
index 36a85b0869f..a94f0357977 100644
--- a/clippy_lints/src/utils/internal_lints.rs
+++ b/clippy_lints/src/utils/internal_lints.rs
@@ -1,3 +1,4 @@
+use crate::utils::internal_lints::metadata_collector::is_deprecated_lint;
 use clippy_utils::consts::{constant_simple, Constant};
 use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
 use clippy_utils::macros::root_macro_call_first_node;
@@ -338,6 +339,46 @@ declare_clippy_lint! {
     "checking if all necessary steps were taken when adding a MSRV to a lint"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for cases of an auto-generated deprecated lint without an updated reason,
+    /// i.e. `"default deprecation note"`.
+    ///
+    /// ### Why is this bad?
+    /// Indicates that the documentation is incomplete.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// declare_deprecated_lint! {
+    ///     /// ### What it does
+    ///     /// Nothing. This lint has been deprecated.
+    ///     ///
+    ///     /// ### Deprecation reason
+    ///     /// TODO
+    ///     #[clippy::version = "1.63.0"]
+    ///     pub COOL_LINT,
+    ///     "default deprecation note"
+    /// }
+    /// ```
+    ///
+    /// Use instead:
+    /// ```rust,ignore
+    /// declare_deprecated_lint! {
+    ///     /// ### What it does
+    ///     /// Nothing. This lint has been deprecated.
+    ///     ///
+    ///     /// ### Deprecation reason
+    ///     /// This lint has been replaced by `cooler_lint`
+    ///     #[clippy::version = "1.63.0"]
+    ///     pub COOL_LINT,
+    ///     "this lint has been replaced by `cooler_lint`"
+    /// }
+    /// ```
+    pub DEFAULT_DEPRECATION_REASON,
+    internal,
+    "found 'default deprecation note' in a deprecated lint declaration"
+}
+
 declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
 
 impl EarlyLintPass for ClippyLintsInternal {
@@ -375,42 +416,67 @@ pub struct LintWithoutLintPass {
     registered_lints: FxHashSet<Symbol>,
 }
 
-impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE]);
+impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE, DEFAULT_DEPRECATION_REASON]);
 
 impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
-        if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id()) {
+        if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id())
+            || is_lint_allowed(cx, DEFAULT_DEPRECATION_REASON, item.hir_id())
+        {
             return;
         }
 
         if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind {
-            if is_lint_ref_type(cx, ty) {
+            let is_lint_ref_ty = is_lint_ref_type(cx, ty);
+            if is_deprecated_lint(cx, ty) || is_lint_ref_ty {
                 check_invalid_clippy_version_attribute(cx, item);
 
                 let expr = &cx.tcx.hir().body(body_id).value;
-                if_chain! {
-                    if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind;
-                    if let ExprKind::Struct(_, fields, _) = inner_exp.kind;
-                    let field = fields
-                        .iter()
-                        .find(|f| f.ident.as_str() == "desc")
-                        .expect("lints must have a description field");
-                    if let ExprKind::Lit(Spanned {
-                        node: LitKind::Str(ref sym, _),
-                        ..
-                    }) = field.expr.kind;
-                    if sym.as_str() == "default lint description";
-
-                    then {
+                let fields;
+                if is_lint_ref_ty {
+                    if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind
+                        && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind {
+                            fields = struct_fields;
+                    } else {
+                        return;
+                    }
+                } else if let ExprKind::Struct(_, struct_fields, _) = expr.kind {
+                    fields = struct_fields;
+                } else {
+                    return;
+                }
+
+                let field = fields
+                    .iter()
+                    .find(|f| f.ident.as_str() == "desc")
+                    .expect("lints must have a description field");
+
+                if let ExprKind::Lit(Spanned {
+                    node: LitKind::Str(ref sym, _),
+                    ..
+                }) = field.expr.kind
+                {
+                    let sym_str = sym.as_str();
+                    if is_lint_ref_ty {
+                        if sym_str == "default lint description" {
+                            span_lint(
+                                cx,
+                                DEFAULT_LINT,
+                                item.span,
+                                &format!("the lint `{}` has the default lint description", item.ident.name),
+                            );
+                        }
+
+                        self.declared_lints.insert(item.ident.name, item.span);
+                    } else if sym_str == "default deprecation note" {
                         span_lint(
                             cx,
-                            DEFAULT_LINT,
+                            DEFAULT_DEPRECATION_REASON,
                             item.span,
-                            &format!("the lint `{}` has the default lint description", item.ident.name),
+                            &format!("the lint `{}` has the default deprecation reason", item.ident.name),
                         );
                     }
                 }
-                self.declared_lints.insert(item.ident.name, item.span);
             }
         } else if let Some(macro_call) = root_macro_call_first_node(cx, item) {
             if !matches!(
diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs
index f3d36938041..c97a1f1e258 100644
--- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs
+++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs
@@ -847,7 +847,7 @@ fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> {
         .find_map(|(group_name, group_level)| (*group_name == lint_group).then(|| *group_level))
 }
 
-fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
+pub(super) fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
     if let hir::TyKind::Path(ref path) = ty.kind {
         if let hir::def::Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, ty.hir_id) {
             return match_def_path(cx, def_id, &DEPRECATED_LINT_TYPE);
diff --git a/tests/compile-test.rs b/tests/compile-test.rs
index 7d219835723..a303d90d953 100644
--- a/tests/compile-test.rs
+++ b/tests/compile-test.rs
@@ -24,6 +24,7 @@ const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal");
 /// All crates used in UI tests are listed here
 static TEST_DEPENDENCIES: &[&str] = &[
     "clap",
+    "clippy_lints",
     "clippy_utils",
     "derive_new",
     "futures",
@@ -44,6 +45,8 @@ static TEST_DEPENDENCIES: &[&str] = &[
 #[allow(unused_extern_crates)]
 extern crate clap;
 #[allow(unused_extern_crates)]
+extern crate clippy_lints;
+#[allow(unused_extern_crates)]
 extern crate clippy_utils;
 #[allow(unused_extern_crates)]
 extern crate derive_new;
diff --git a/tests/ui-internal/default_deprecation_reason.rs b/tests/ui-internal/default_deprecation_reason.rs
new file mode 100644
index 00000000000..c8961d5e1f0
--- /dev/null
+++ b/tests/ui-internal/default_deprecation_reason.rs
@@ -0,0 +1,30 @@
+#![deny(clippy::internal)]
+#![feature(rustc_private)]
+
+#[macro_use]
+extern crate clippy_lints;
+use clippy_lints::deprecated_lints::ClippyDeprecatedLint;
+
+declare_deprecated_lint! {
+    /// ### What it does
+    /// Nothing. This lint has been deprecated.
+    ///
+    /// ### Deprecation reason
+    /// TODO
+    #[clippy::version = "1.63.0"]
+    pub COOL_LINT_DEFAULT,
+    "default deprecation note"
+}
+
+declare_deprecated_lint! {
+    /// ### What it does
+    /// Nothing. This lint has been deprecated.
+    ///
+    /// ### Deprecation reason
+    /// This lint has been replaced by `cooler_lint`
+    #[clippy::version = "1.63.0"]
+    pub COOL_LINT,
+    "this lint has been replaced by `cooler_lint`"
+}
+
+fn main() {}
diff --git a/tests/ui-internal/default_deprecation_reason.stderr b/tests/ui-internal/default_deprecation_reason.stderr
new file mode 100644
index 00000000000..ca26b649f98
--- /dev/null
+++ b/tests/ui-internal/default_deprecation_reason.stderr
@@ -0,0 +1,22 @@
+error: the lint `COOL_LINT_DEFAULT` has the default deprecation reason
+  --> $DIR/default_deprecation_reason.rs:8:1
+   |
+LL | / declare_deprecated_lint! {
+LL | |     /// ### What it does
+LL | |     /// Nothing. This lint has been deprecated.
+LL | |     ///
+...  |
+LL | |     "default deprecation note"
+LL | | }
+   | |_^
+   |
+note: the lint level is defined here
+  --> $DIR/default_deprecation_reason.rs:1:9
+   |
+LL | #![deny(clippy::internal)]
+   |         ^^^^^^^^^^^^^^^^
+   = note: `#[deny(clippy::default_deprecation_reason)]` implied by `#[deny(clippy::internal)]`
+   = note: this error originates in the macro `declare_deprecated_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to previous error
+