about summary refs log tree commit diff
path: root/clippy_lints/src/utils/internal_lints
diff options
context:
space:
mode:
authorJason Newcomb <jsnewcomb@pm.me>2024-08-24 17:34:45 -0400
committerJason Newcomb <jsnewcomb@pm.me>2024-08-24 18:33:44 -0400
commit173d5a6af0f2352b3efa0b3a2faad87207706936 (patch)
tree6edfd6284a78d5390d15f03d34bc7e1f7c82cd52 /clippy_lints/src/utils/internal_lints
parent43e1145c80d1c2f4e159885d382659ace3a1bc5d (diff)
Merge commit '0f8eabd6231366bfc1bb1464601297c2d48f8f68' into clippyup
Diffstat (limited to 'clippy_lints/src/utils/internal_lints')
-rw-r--r--clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs10
-rw-r--r--clippy_lints/src/utils/internal_lints/metadata_collector.rs1078
2 files changed, 7 insertions, 1081 deletions
diff --git a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs b/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs
index df342e48d63..20526113d69 100644
--- a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs
+++ b/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs
@@ -9,7 +9,6 @@ use rustc_hir::intravisit::Visitor;
 use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::hir::nested_filter;
-use rustc_semver::RustcVersion;
 use rustc_session::impl_lint_pass;
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::Symbol;
@@ -92,7 +91,12 @@ 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,
+]);
 
 impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
@@ -220,7 +224,7 @@ fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'
             return;
         }
 
-        if RustcVersion::parse(value.as_str()).is_err() {
+        if rustc_attr::parse_version(value).is_none() {
             span_lint_and_help(
                 cx,
                 INVALID_CLIPPY_VERSION_ATTRIBUTE,
diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs
deleted file mode 100644
index 57f45aa3e48..00000000000
--- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs
+++ /dev/null
@@ -1,1078 +0,0 @@
-//! This lint is used to collect metadata about clippy lints. This metadata is exported as a json
-//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html)
-//!
-//! This module and therefore the entire lint is guarded by a feature flag called `internal`
-//!
-//! The module transforms all lint names to ascii lowercase to ensure that we don't have mismatches
-//! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such
-//! a simple mistake)
-
-use crate::utils::internal_lints::lint_without_lint_pass::{extract_clippy_version_value, is_lint_ref_type};
-use clippy_config::{get_configuration_metadata, ClippyConfiguration};
-
-use clippy_utils::diagnostics::span_lint;
-use clippy_utils::ty::{match_type, walk_ptrs_ty_depth};
-use clippy_utils::{last_path_segment, match_function_call, match_path, paths};
-use itertools::Itertools;
-use rustc_ast as ast;
-use rustc_data_structures::fx::FxHashMap;
-use rustc_hir::def::DefKind;
-use rustc_hir::intravisit::Visitor;
-use rustc_hir::{self as hir, intravisit, Closure, ExprKind, Item, ItemKind, Mutability, QPath};
-use rustc_lint::{unerased_lint_store, CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
-use rustc_middle::hir::nested_filter;
-use rustc_session::impl_lint_pass;
-use rustc_span::symbol::Ident;
-use rustc_span::{sym, Loc, Span, Symbol};
-use serde::ser::SerializeStruct;
-use serde::{Serialize, Serializer};
-use std::collections::{BTreeSet, BinaryHeap};
-use std::fmt::Write as _;
-use std::fs::{self, File};
-use std::io::prelude::*;
-use std::path::{Path, PathBuf};
-use std::process::Command;
-use std::{env, fmt};
-
-/// This is the json output file of the lint collector.
-const JSON_OUTPUT_FILE: &str = "../util/gh-pages/lints.json";
-/// This is the markdown output file of the lint collector.
-const MARKDOWN_OUTPUT_FILE: &str = "../book/src/lint_configuration.md";
-/// These groups will be ignored by the lint group matcher. This is useful for collections like
-/// `clippy::all`
-const IGNORED_LINT_GROUPS: [&str; 1] = ["clippy::all"];
-/// Lints within this group will be excluded from the collection. These groups
-/// have to be defined without the `clippy::` prefix.
-const EXCLUDED_LINT_GROUPS: [&str; 1] = ["internal"];
-/// Collected deprecated lint will be assigned to this group in the JSON output
-const DEPRECATED_LINT_GROUP_STR: &str = "deprecated";
-/// This is the lint level for deprecated lints that will be displayed in the lint list
-const DEPRECATED_LINT_LEVEL: &str = "none";
-/// This array holds Clippy's lint groups with their corresponding default lint level. The
-/// lint level for deprecated lints is set in `DEPRECATED_LINT_LEVEL`.
-const DEFAULT_LINT_LEVELS: &[(&str, &str)] = &[
-    ("correctness", "deny"),
-    ("suspicious", "warn"),
-    ("restriction", "allow"),
-    ("style", "warn"),
-    ("pedantic", "allow"),
-    ("complexity", "warn"),
-    ("perf", "warn"),
-    ("cargo", "allow"),
-    ("nursery", "allow"),
-];
-/// This prefix is in front of the lint groups in the lint store. The prefix will be trimmed
-/// to only keep the actual lint group in the output.
-const CLIPPY_LINT_GROUP_PREFIX: &str = "clippy::";
-const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [
-    &["clippy_utils", "diagnostics", "span_lint"],
-    &["clippy_utils", "diagnostics", "span_lint_and_help"],
-    &["clippy_utils", "diagnostics", "span_lint_and_note"],
-    &["clippy_utils", "diagnostics", "span_lint_hir"],
-    &["clippy_utils", "diagnostics", "span_lint_and_sugg"],
-    &["clippy_utils", "diagnostics", "span_lint_and_then"],
-    &["clippy_utils", "diagnostics", "span_lint_hir_and_then"],
-];
-const SUGGESTION_DIAG_METHODS: [(&str, bool); 9] = [
-    ("span_suggestion", false),
-    ("span_suggestion_short", false),
-    ("span_suggestion_verbose", false),
-    ("span_suggestion_hidden", false),
-    ("tool_only_span_suggestion", false),
-    ("multipart_suggestion", true),
-    ("multipart_suggestions", true),
-    ("tool_only_multipart_suggestion", true),
-    ("span_suggestions", true),
-];
-
-/// The index of the applicability name of `paths::APPLICABILITY_VALUES`
-const APPLICABILITY_NAME_INDEX: usize = 2;
-/// This applicability will be set for unresolved applicability values.
-const APPLICABILITY_UNRESOLVED_STR: &str = "Unresolved";
-/// The version that will be displayed if none has been defined
-const VERSION_DEFAULT_STR: &str = "Unknown";
-
-const CHANGELOG_PATH: &str = "../CHANGELOG.md";
-
-declare_clippy_lint! {
-    /// ### What it does
-    /// Collects metadata about clippy lints for the website.
-    ///
-    /// This lint will be used to report problems of syntax parsing. You should hopefully never
-    /// see this but never say never I guess ^^
-    ///
-    /// ### Why is this bad?
-    /// This is not a bad thing but definitely a hacky way to do it. See
-    /// issue [#4310](https://github.com/rust-lang/rust-clippy/issues/4310) for a discussion
-    /// about the implementation.
-    ///
-    /// ### Known problems
-    /// Hopefully none. It would be pretty uncool to have a problem here :)
-    ///
-    /// ### Example output
-    /// ```json,ignore
-    /// {
-    ///     "id": "metadata_collector",
-    ///     "id_span": {
-    ///         "path": "clippy_lints/src/utils/internal_lints/metadata_collector.rs",
-    ///         "line": 1
-    ///     },
-    ///     "group": "clippy::internal",
-    ///     "docs": " ### What it does\nCollects metadata about clippy lints for the website. [...] "
-    /// }
-    /// ```
-    #[clippy::version = "1.56.0"]
-    pub METADATA_COLLECTOR,
-    internal,
-    "A busy bee collection metadata about lints"
-}
-
-impl_lint_pass!(MetadataCollector => [METADATA_COLLECTOR]);
-
-#[allow(clippy::module_name_repetitions)]
-#[derive(Debug, Clone)]
-pub struct MetadataCollector {
-    /// All collected lints
-    ///
-    /// We use a Heap here to have the lints added in alphabetic order in the export
-    lints: BinaryHeap<LintMetadata>,
-    applicability_info: FxHashMap<String, ApplicabilityInfo>,
-    config: Vec<ClippyConfiguration>,
-    clippy_project_root: PathBuf,
-}
-
-impl MetadataCollector {
-    pub fn new() -> Self {
-        Self {
-            lints: BinaryHeap::<LintMetadata>::default(),
-            applicability_info: FxHashMap::<String, ApplicabilityInfo>::default(),
-            config: get_configuration_metadata(),
-            clippy_project_root: env::current_dir()
-                .expect("failed to get current dir")
-                .ancestors()
-                .nth(1)
-                .expect("failed to get project root")
-                .to_path_buf(),
-        }
-    }
-
-    fn get_lint_configs(&self, lint_name: &str) -> Option<String> {
-        self.config
-            .iter()
-            .filter(|config| config.lints.iter().any(|&lint| lint == lint_name))
-            .map(ToString::to_string)
-            .reduce(|acc, x| acc + "\n\n" + &x)
-            .map(|configurations| {
-                format!(
-                    r#"
-### Configuration
-This lint has the following configuration variables:
-
-{configurations}
-"#
-                )
-            })
-    }
-
-    fn configs_to_markdown(&self, map_fn: fn(&ClippyConfiguration) -> String) -> String {
-        self.config
-            .iter()
-            .filter(|config| config.deprecation_reason.is_none())
-            .filter(|config| !config.lints.is_empty())
-            .map(map_fn)
-            .join("\n")
-    }
-
-    fn get_markdown_docs(&self) -> String {
-        format!(
-            r#"# Lint Configuration Options
-
-The following list shows each configuration option, along with a description, its default value, an example
-and lints affected.
-
----
-
-{}"#,
-            self.configs_to_markdown(ClippyConfiguration::to_markdown_paragraph),
-        )
-    }
-}
-
-impl Drop for MetadataCollector {
-    /// You might ask: How hacky is this?
-    /// My answer:     YES
-    fn drop(&mut self) {
-        // The metadata collector gets dropped twice, this makes sure that we only write
-        // when the list is full
-        if self.lints.is_empty() {
-            return;
-        }
-
-        let mut applicability_info = std::mem::take(&mut self.applicability_info);
-
-        // Add deprecated lints
-        self.lints.extend(
-            crate::deprecated_lints::DEPRECATED
-                .iter()
-                .zip(crate::deprecated_lints::DEPRECATED_VERSION)
-                .filter_map(|((lint, reason), version)| LintMetadata::new_deprecated(lint, reason, version)),
-        );
-        // Mapping the final data
-        let mut lints = std::mem::take(&mut self.lints).into_sorted_vec();
-        for x in &mut lints {
-            x.applicability = Some(applicability_info.remove(&x.id).unwrap_or_default());
-            replace_produces(&x.id, &mut x.docs, &self.clippy_project_root);
-        }
-
-        collect_renames(&mut lints);
-
-        // Outputting json
-        fs::write(JSON_OUTPUT_FILE, serde_json::to_string_pretty(&lints).unwrap()).unwrap();
-
-        // Outputting markdown
-        let mut file = File::create(MARKDOWN_OUTPUT_FILE).unwrap();
-        writeln!(
-            file,
-            "<!--
-This file is generated by `cargo collect-metadata`.
-Please use that command to update the file and do not edit it by hand.
--->
-
-{}",
-            self.get_markdown_docs(),
-        )
-        .unwrap();
-
-        // Write configuration links to CHANGELOG.md
-        let changelog = fs::read_to_string(CHANGELOG_PATH).unwrap();
-        let mut changelog_file = File::create(CHANGELOG_PATH).unwrap();
-        let position = changelog
-            .find("<!-- begin autogenerated links to configuration documentation -->")
-            .unwrap();
-        writeln!(
-            changelog_file,
-            "{}<!-- begin autogenerated links to configuration documentation -->\n{}\n<!-- end autogenerated links to configuration documentation -->",
-            &changelog[..position],
-            self.configs_to_markdown(ClippyConfiguration::to_markdown_link)
-        )
-        .unwrap();
-    }
-}
-
-#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
-struct LintMetadata {
-    id: String,
-    id_span: Option<SerializableSpan>,
-    group: String,
-    level: String,
-    docs: String,
-    version: String,
-    /// This field is only used in the output and will only be
-    /// mapped shortly before the actual output.
-    applicability: Option<ApplicabilityInfo>,
-    /// All the past names of lints which have been renamed.
-    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
-    former_ids: BTreeSet<String>,
-}
-
-impl LintMetadata {
-    fn new(
-        id: String,
-        id_span: SerializableSpan,
-        group: String,
-        level: &'static str,
-        version: String,
-        docs: String,
-    ) -> Self {
-        Self {
-            id,
-            id_span: Some(id_span),
-            group,
-            level: level.to_string(),
-            version,
-            docs,
-            applicability: None,
-            former_ids: BTreeSet::new(),
-        }
-    }
-
-    fn new_deprecated(name: &str, reason: &str, version: &str) -> Option<Self> {
-        // The reason starts with a lowercase letter and end without a period.
-        // This needs to be fixed for the website.
-        let mut reason = reason.to_owned();
-        if let Some(reason) = reason.get_mut(0..1) {
-            reason.make_ascii_uppercase();
-        }
-        name.strip_prefix("clippy::").map(|name| Self {
-            id: name.into(),
-            id_span: None,
-            group: DEPRECATED_LINT_GROUP_STR.into(),
-            level: DEPRECATED_LINT_LEVEL.into(),
-            version: version.into(),
-            docs: format!(
-                "### What it does\n\n\
-                Nothing. This lint has been deprecated\n\n\
-                ### Deprecation reason\n\n{reason}.\n",
-            ),
-            applicability: None,
-            former_ids: BTreeSet::new(),
-        })
-    }
-}
-
-fn replace_produces(lint_name: &str, docs: &mut String, clippy_project_root: &Path) {
-    let mut doc_lines = docs.lines().map(ToString::to_string).collect::<Vec<_>>();
-    let mut lines = doc_lines.iter_mut();
-
-    'outer: loop {
-        // Find the start of the example
-
-        // ```rust
-        loop {
-            match lines.next() {
-                Some(line) if line.trim_start().starts_with("```rust") => {
-                    if line.contains("ignore") || line.contains("no_run") {
-                        // A {{produces}} marker may have been put on a ignored code block by mistake,
-                        // just seek to the end of the code block and continue checking.
-                        if lines.any(|line| line.trim_start().starts_with("```")) {
-                            continue;
-                        }
-
-                        panic!("lint `{lint_name}` has an unterminated code block")
-                    }
-
-                    break;
-                },
-                Some(line) if line.trim_start() == "{{produces}}" => {
-                    panic!("lint `{lint_name}` has marker {{{{produces}}}} with an ignored or missing code block")
-                },
-                Some(line) => {
-                    let line = line.trim();
-                    // These are the two most common markers of the corrections section
-                    if line.eq_ignore_ascii_case("Use instead:") || line.eq_ignore_ascii_case("Could be written as:") {
-                        break 'outer;
-                    }
-                },
-                None => break 'outer,
-            }
-        }
-
-        // Collect the example
-        let mut example = Vec::new();
-        loop {
-            match lines.next() {
-                Some(line) if line.trim_start() == "```" => break,
-                Some(line) => example.push(line),
-                None => panic!("lint `{lint_name}` has an unterminated code block"),
-            }
-        }
-
-        // Find the {{produces}} and attempt to generate the output
-        loop {
-            match lines.next() {
-                Some(line) if line.is_empty() => {},
-                Some(line) if line.trim() == "{{produces}}" => {
-                    let output = get_lint_output(lint_name, &example, clippy_project_root);
-                    line.replace_range(
-                        ..,
-                        &format!(
-                            "<details>\
-                            <summary>Produces</summary>\n\
-                            \n\
-                            ```text\n\
-                            {output}\n\
-                            ```\n\
-                        </details>"
-                        ),
-                    );
-
-                    break;
-                },
-                // No {{produces}}, we can move on to the next example
-                Some(_) => break,
-                None => break 'outer,
-            }
-        }
-    }
-
-    *docs = cleanup_docs(&doc_lines);
-}
-
-fn get_lint_output(lint_name: &str, example: &[&mut String], clippy_project_root: &Path) -> String {
-    let dir = tempfile::tempdir().unwrap_or_else(|e| panic!("failed to create temp dir: {e}"));
-    let file = dir.path().join("lint_example.rs");
-
-    let mut source = String::new();
-    let unhidden = example
-        .iter()
-        .map(|line| line.trim_start().strip_prefix("# ").unwrap_or(line));
-
-    // Get any attributes
-    let mut lines = unhidden.peekable();
-    while let Some(line) = lines.peek() {
-        if line.starts_with("#!") {
-            source.push_str(line);
-            source.push('\n');
-            lines.next();
-        } else {
-            break;
-        }
-    }
-
-    let needs_main = !example.iter().any(|line| line.contains("fn main"));
-    if needs_main {
-        source.push_str("fn main() {\n");
-    }
-
-    for line in lines {
-        source.push_str(line);
-        source.push('\n');
-    }
-
-    if needs_main {
-        source.push_str("}\n");
-    }
-
-    if let Err(e) = fs::write(&file, &source) {
-        panic!("failed to write to `{}`: {e}", file.as_path().to_string_lossy());
-    }
-
-    let prefixed_name = format!("{CLIPPY_LINT_GROUP_PREFIX}{lint_name}");
-
-    let mut cmd = Command::new(env::var("CARGO").unwrap_or("cargo".into()));
-
-    cmd.current_dir(clippy_project_root)
-        .env("CARGO_INCREMENTAL", "0")
-        .env("CLIPPY_ARGS", "")
-        .env("CLIPPY_DISABLE_DOCS_LINKS", "1")
-        // We need to disable this to enable all lints
-        .env("ENABLE_METADATA_COLLECTION", "0")
-        .args(["run", "--bin", "clippy-driver"])
-        .args(["--target-dir", "./clippy_lints/target"])
-        .args(["--", "--error-format=json"])
-        .args(["--edition", "2021"])
-        .arg("-Cdebuginfo=0")
-        .args(["-A", "clippy::all"])
-        .args(["-W", &prefixed_name])
-        .args(["-L", "./target/debug"])
-        .args(["-Z", "no-codegen"]);
-
-    let output = cmd
-        .arg(file.as_path())
-        .output()
-        .unwrap_or_else(|e| panic!("failed to run `{cmd:?}`: {e}"));
-
-    let tmp_file_path = file.to_string_lossy();
-    let stderr = std::str::from_utf8(&output.stderr).unwrap();
-    let msgs = stderr
-        .lines()
-        .filter(|line| line.starts_with('{'))
-        .map(|line| serde_json::from_str(line).unwrap())
-        .collect::<Vec<serde_json::Value>>();
-
-    let mut rendered = String::new();
-    let iter = msgs
-        .iter()
-        .filter(|msg| matches!(&msg["code"]["code"], serde_json::Value::String(s) if s == &prefixed_name));
-
-    for message in iter {
-        let rendered_part = message["rendered"].as_str().expect("rendered field should exist");
-        rendered.push_str(rendered_part);
-    }
-
-    if rendered.is_empty() {
-        let rendered: Vec<&str> = msgs.iter().filter_map(|msg| msg["rendered"].as_str()).collect();
-        let non_json: Vec<&str> = stderr.lines().filter(|line| !line.starts_with('{')).collect();
-        panic!(
-            "did not find lint `{lint_name}` in output of example, got:\n{}\n{}",
-            non_json.join("\n"),
-            rendered.join("\n")
-        );
-    }
-
-    // The reader doesn't need to see `/tmp/.tmpfiy2Qd/lint_example.rs` :)
-    rendered.trim_end().replace(&*tmp_file_path, "lint_example.rs")
-}
-
-#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
-struct SerializableSpan {
-    path: String,
-    line: usize,
-}
-
-impl fmt::Display for SerializableSpan {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}:{}", self.path.rsplit('/').next().unwrap_or_default(), self.line)
-    }
-}
-
-impl SerializableSpan {
-    fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Self {
-        Self::from_span(cx, item.ident.span)
-    }
-
-    fn from_span(cx: &LateContext<'_>, span: Span) -> Self {
-        let loc: Loc = cx.sess().source_map().lookup_char_pos(span.lo());
-
-        Self {
-            path: format!("{}", loc.file.name.prefer_remapped_unconditionaly()),
-            line: loc.line,
-        }
-    }
-}
-
-#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct ApplicabilityInfo {
-    /// Indicates if any of the lint emissions uses multiple spans. This is related to
-    /// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
-    /// currently not be applied automatically.
-    is_multi_part_suggestion: bool,
-    applicability: Option<usize>,
-}
-
-impl Serialize for ApplicabilityInfo {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        let mut s = serializer.serialize_struct("ApplicabilityInfo", 2)?;
-        s.serialize_field("is_multi_part_suggestion", &self.is_multi_part_suggestion)?;
-        if let Some(index) = self.applicability {
-            s.serialize_field(
-                "applicability",
-                &paths::APPLICABILITY_VALUES[index][APPLICABILITY_NAME_INDEX],
-            )?;
-        } else {
-            s.serialize_field("applicability", APPLICABILITY_UNRESOLVED_STR)?;
-        }
-        s.end()
-    }
-}
-
-// ==================================================================
-// Lint pass
-// ==================================================================
-impl<'hir> LateLintPass<'hir> for MetadataCollector {
-    /// Collecting lint declarations like:
-    /// ```rust, ignore
-    /// declare_clippy_lint! {
-    ///     /// ### What it does
-    ///     /// Something IDK.
-    ///     pub SOME_LINT,
-    ///     internal,
-    ///     "Who am I?"
-    /// }
-    /// ```
-    fn check_item(&mut self, cx: &LateContext<'hir>, item: &'hir Item<'_>) {
-        if let ItemKind::Static(ty, Mutability::Not, _) = item.kind {
-            // Normal lint
-            if is_lint_ref_type(cx, ty)
-                // item validation
-                // disallow check
-                && let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase()
-                // metadata extraction
-                && let Some((group, level)) = get_lint_group_and_level_or_lint(cx, &lint_name, item)
-                && let Some(mut raw_docs) = extract_attr_docs_or_lint(cx, item)
-            {
-                if let Some(configuration_section) = self.get_lint_configs(&lint_name) {
-                    raw_docs.push_str(&configuration_section);
-                }
-                let version = get_lint_version(cx, item);
-
-                self.lints.push(LintMetadata::new(
-                    lint_name,
-                    SerializableSpan::from_item(cx, item),
-                    group,
-                    level,
-                    version,
-                    raw_docs,
-                ));
-            }
-        }
-    }
-
-    /// Collecting constant applicability from the actual lint emissions
-    ///
-    /// Example:
-    /// ```rust, ignore
-    /// span_lint_and_sugg(
-    ///     cx,
-    ///     SOME_LINT,
-    ///     item.span,
-    ///     "Le lint message",
-    ///     "Here comes help:",
-    ///     "#![allow(clippy::all)]",
-    ///     Applicability::MachineApplicable, // <-- Extracts this constant value
-    /// );
-    /// ```
-    fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
-        if let Some(args) = match_lint_emission(cx, expr) {
-            let emission_info = extract_emission_info(cx, args);
-            if emission_info.is_empty() {
-                // See:
-                // - src/misc.rs:734:9
-                // - src/methods/mod.rs:3545:13
-                // - src/methods/mod.rs:3496:13
-                // We are basically unable to resolve the lint name itself.
-                return;
-            }
-
-            for (lint_name, applicability, is_multi_part) in emission_info {
-                let app_info = self.applicability_info.entry(lint_name).or_default();
-                app_info.applicability = applicability;
-                app_info.is_multi_part_suggestion = is_multi_part;
-            }
-        }
-    }
-}
-
-// ==================================================================
-// Lint definition extraction
-// ==================================================================
-fn sym_to_string(sym: Symbol) -> String {
-    sym.as_str().to_string()
-}
-
-fn extract_attr_docs_or_lint(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
-    extract_attr_docs(cx, item).or_else(|| {
-        lint_collection_error_item(cx, item, "could not collect the lint documentation");
-        None
-    })
-}
-
-/// This function collects all documentation that has been added to an item using
-/// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks
-///
-/// ```ignore
-/// #[doc = r"Hello world!"]
-/// #[doc = r"=^.^="]
-/// struct SomeItem {}
-/// ```
-///
-/// Would result in `Hello world!\n=^.^=\n`
-fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
-    let attrs = cx.tcx.hir().attrs(item.hir_id());
-    let mut lines = attrs.iter().filter_map(ast::Attribute::doc_str);
-
-    if let Some(line) = lines.next() {
-        let raw_docs = lines.fold(String::from(line.as_str()) + "\n", |s, line| s + line.as_str() + "\n");
-        return Some(raw_docs);
-    }
-
-    None
-}
-
-/// This function may modify the doc comment to ensure that the string can be displayed using a
-/// markdown viewer in Clippy's lint list. The following modifications could be applied:
-/// * Removal of leading space after a new line. (Important to display tables)
-/// * Ensures that code blocks only contain language information
-fn cleanup_docs(docs_collection: &Vec<String>) -> String {
-    let mut in_code_block = false;
-    let mut is_code_block_rust = false;
-
-    let mut docs = String::new();
-    for line in docs_collection {
-        // Rustdoc hides code lines starting with `# ` and this removes them from Clippy's lint list :)
-        if is_code_block_rust && line.trim_start().starts_with("# ") {
-            continue;
-        }
-
-        // The line should be represented in the lint list, even if it's just an empty line
-        docs.push('\n');
-        if let Some(info) = line.trim_start().strip_prefix("```") {
-            in_code_block = !in_code_block;
-            is_code_block_rust = false;
-            if in_code_block {
-                let lang = info
-                    .trim()
-                    .split(',')
-                    // remove rustdoc directives
-                    .find(|&s| !matches!(s, "" | "ignore" | "no_run" | "should_panic"))
-                    // if no language is present, fill in "rust"
-                    .unwrap_or("rust");
-                let len_diff = line.len() - line.trim_start().len();
-                if len_diff != 0 {
-                    // We put back the indentation.
-                    docs.push_str(&line[..len_diff]);
-                }
-                docs.push_str("```");
-                docs.push_str(lang);
-
-                is_code_block_rust = lang == "rust";
-                continue;
-            }
-        }
-        // This removes the leading space that the macro translation introduces
-        if let Some(stripped_doc) = line.strip_prefix(' ') {
-            docs.push_str(stripped_doc);
-        } else if !line.is_empty() {
-            docs.push_str(line);
-        }
-    }
-
-    docs
-}
-
-fn get_lint_version(cx: &LateContext<'_>, item: &Item<'_>) -> String {
-    extract_clippy_version_value(cx, item).map_or_else(
-        || VERSION_DEFAULT_STR.to_string(),
-        |version| version.as_str().to_string(),
-    )
-}
-
-fn get_lint_group_and_level_or_lint(
-    cx: &LateContext<'_>,
-    lint_name: &str,
-    item: &Item<'_>,
-) -> Option<(String, &'static str)> {
-    let result = unerased_lint_store(cx.tcx.sess).check_lint_name(
-        lint_name,
-        Some(sym::clippy),
-        &std::iter::once(Ident::with_dummy_span(sym::clippy)).collect(),
-    );
-    if let CheckLintNameResult::Tool(lint_lst, None) = result {
-        if let Some(group) = get_lint_group(cx, lint_lst[0]) {
-            if EXCLUDED_LINT_GROUPS.contains(&group.as_str()) {
-                return None;
-            }
-
-            if let Some(level) = get_lint_level_from_group(&group) {
-                Some((group, level))
-            } else {
-                lint_collection_error_item(
-                    cx,
-                    item,
-                    &format!("Unable to determine lint level for found group `{group}`"),
-                );
-                None
-            }
-        } else {
-            lint_collection_error_item(cx, item, "Unable to determine lint group");
-            None
-        }
-    } else {
-        lint_collection_error_item(cx, item, "Unable to find lint in lint_store");
-        None
-    }
-}
-
-fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
-    for (group_name, lints, _) in unerased_lint_store(cx.tcx.sess).get_lint_groups() {
-        if IGNORED_LINT_GROUPS.contains(&group_name) {
-            continue;
-        }
-
-        if lints.iter().any(|group_lint| *group_lint == lint_id) {
-            let group = group_name.strip_prefix(CLIPPY_LINT_GROUP_PREFIX).unwrap_or(group_name);
-            return Some((*group).to_string());
-        }
-    }
-
-    None
-}
-
-fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> {
-    DEFAULT_LINT_LEVELS
-        .iter()
-        .find_map(|(group_name, group_level)| (*group_name == lint_group).then_some(*group_level))
-}
-
-fn collect_renames(lints: &mut Vec<LintMetadata>) {
-    for lint in lints {
-        let mut collected = String::new();
-        let mut names = vec![lint.id.clone()];
-
-        loop {
-            if let Some(lint_name) = names.pop() {
-                for (k, v) in crate::deprecated_lints::RENAMED {
-                    if let Some(name) = v.strip_prefix(CLIPPY_LINT_GROUP_PREFIX)
-                        && name == lint_name
-                        && let Some(past_name) = k.strip_prefix(CLIPPY_LINT_GROUP_PREFIX)
-                    {
-                        lint.former_ids.insert(past_name.to_owned());
-                        writeln!(collected, "* `{past_name}`").unwrap();
-                        names.push(past_name.to_string());
-                    }
-                }
-
-                continue;
-            }
-
-            break;
-        }
-
-        if !collected.is_empty() {
-            write!(
-                &mut lint.docs,
-                r#"
-### Past names
-
-{collected}
-"#
-            )
-            .unwrap();
-        }
-    }
-}
-
-// ==================================================================
-// Lint emission
-// ==================================================================
-fn lint_collection_error_item(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
-    span_lint(
-        cx,
-        METADATA_COLLECTOR,
-        item.ident.span,
-        format!("metadata collection error for `{}`: {message}", item.ident.name),
-    );
-}
-
-// ==================================================================
-// Applicability
-// ==================================================================
-/// This function checks if a given expression is equal to a simple lint emission function call.
-/// It will return the function arguments if the emission matched any function.
-fn match_lint_emission<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) -> Option<&'hir [hir::Expr<'hir>]> {
-    LINT_EMISSION_FUNCTIONS
-        .iter()
-        .find_map(|emission_fn| match_function_call(cx, expr, emission_fn))
-}
-
-fn take_higher_applicability(a: Option<usize>, b: Option<usize>) -> Option<usize> {
-    a.map_or(b, |a| a.max(b.unwrap_or_default()).into())
-}
-
-fn extract_emission_info<'hir>(
-    cx: &LateContext<'hir>,
-    args: &'hir [hir::Expr<'hir>],
-) -> Vec<(String, Option<usize>, bool)> {
-    let mut lints = Vec::new();
-    let mut applicability = None;
-    let mut multi_part = false;
-
-    for arg in args {
-        let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(arg));
-
-        if match_type(cx, arg_ty, &paths::LINT) {
-            // If we found the lint arg, extract the lint name
-            let mut resolved_lints = resolve_lints(cx, arg);
-            lints.append(&mut resolved_lints);
-        } else if match_type(cx, arg_ty, &paths::APPLICABILITY) {
-            applicability = resolve_applicability(cx, arg);
-        } else if arg_ty.is_closure() {
-            multi_part |= check_is_multi_part(cx, arg);
-            applicability = applicability.or_else(|| resolve_applicability(cx, arg));
-        }
-    }
-
-    lints
-        .into_iter()
-        .map(|lint_name| (lint_name, applicability, multi_part))
-        .collect()
-}
-
-/// Resolves the possible lints that this expression could reference
-fn resolve_lints<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Vec<String> {
-    let mut resolver = LintResolver::new(cx);
-    resolver.visit_expr(expr);
-    resolver.lints
-}
-
-/// This function tries to resolve the linked applicability to the given expression.
-fn resolve_applicability<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<usize> {
-    let mut resolver = ApplicabilityResolver::new(cx);
-    resolver.visit_expr(expr);
-    resolver.complete()
-}
-
-fn check_is_multi_part<'hir>(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool {
-    if let ExprKind::Closure(&Closure { body, .. }) = closure_expr.kind {
-        let mut scanner = IsMultiSpanScanner::new(cx);
-        intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body));
-        return scanner.is_multi_part();
-    } else if let Some(local) = get_parent_local(cx, closure_expr) {
-        if let Some(local_init) = local.init {
-            return check_is_multi_part(cx, local_init);
-        }
-    }
-
-    false
-}
-
-struct LintResolver<'a, 'hir> {
-    cx: &'a LateContext<'hir>,
-    lints: Vec<String>,
-}
-
-impl<'a, 'hir> LintResolver<'a, 'hir> {
-    fn new(cx: &'a LateContext<'hir>) -> Self {
-        Self {
-            cx,
-            lints: Vec::<String>::default(),
-        }
-    }
-}
-
-impl<'a, 'hir> Visitor<'hir> for LintResolver<'a, 'hir> {
-    type NestedFilter = nested_filter::All;
-
-    fn nested_visit_map(&mut self) -> Self::Map {
-        self.cx.tcx.hir()
-    }
-
-    fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
-        if let ExprKind::Path(qpath) = &expr.kind
-            && let QPath::Resolved(_, path) = qpath
-            && let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr))
-            && match_type(self.cx, expr_ty, &paths::LINT)
-        {
-            if let hir::def::Res::Def(DefKind::Static { .. }, _) = path.res {
-                let lint_name = last_path_segment(qpath).ident.name;
-                self.lints.push(sym_to_string(lint_name).to_ascii_lowercase());
-            } else if let Some(local) = get_parent_local(self.cx, expr) {
-                if let Some(local_init) = local.init {
-                    intravisit::walk_expr(self, local_init);
-                }
-            }
-        }
-
-        intravisit::walk_expr(self, expr);
-    }
-}
-
-/// This visitor finds the highest applicability value in the visited expressions
-struct ApplicabilityResolver<'a, 'hir> {
-    cx: &'a LateContext<'hir>,
-    /// This is the index of highest `Applicability` for `paths::APPLICABILITY_VALUES`
-    applicability_index: Option<usize>,
-}
-
-impl<'a, 'hir> ApplicabilityResolver<'a, 'hir> {
-    fn new(cx: &'a LateContext<'hir>) -> Self {
-        Self {
-            cx,
-            applicability_index: None,
-        }
-    }
-
-    fn add_new_index(&mut self, new_index: usize) {
-        self.applicability_index = take_higher_applicability(self.applicability_index, Some(new_index));
-    }
-
-    fn complete(self) -> Option<usize> {
-        self.applicability_index
-    }
-}
-
-impl<'a, 'hir> Visitor<'hir> for ApplicabilityResolver<'a, 'hir> {
-    type NestedFilter = nested_filter::All;
-
-    fn nested_visit_map(&mut self) -> Self::Map {
-        self.cx.tcx.hir()
-    }
-
-    fn visit_path(&mut self, path: &hir::Path<'hir>, _id: hir::HirId) {
-        for (index, enum_value) in paths::APPLICABILITY_VALUES.iter().enumerate() {
-            if match_path(path, enum_value) {
-                self.add_new_index(index);
-                return;
-            }
-        }
-    }
-
-    fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
-        let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr));
-
-        if match_type(self.cx, expr_ty, &paths::APPLICABILITY)
-            && let Some(local) = get_parent_local(self.cx, expr)
-            && let Some(local_init) = local.init
-        {
-            intravisit::walk_expr(self, local_init);
-        };
-
-        intravisit::walk_expr(self, expr);
-    }
-}
-
-/// This returns the parent local node if the expression is a reference one
-fn get_parent_local<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::LetStmt<'hir>> {
-    if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind {
-        if let hir::def::Res::Local(local_hir) = path.res {
-            return get_parent_local_hir_id(cx, local_hir);
-        }
-    }
-
-    None
-}
-
-fn get_parent_local_hir_id<'hir>(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::LetStmt<'hir>> {
-    match cx.tcx.parent_hir_node(hir_id) {
-        hir::Node::LetStmt(local) => Some(local),
-        hir::Node::Pat(pattern) => get_parent_local_hir_id(cx, pattern.hir_id),
-        _ => None,
-    }
-}
-
-/// This visitor finds the highest applicability value in the visited expressions
-struct IsMultiSpanScanner<'a, 'hir> {
-    cx: &'a LateContext<'hir>,
-    suggestion_count: usize,
-}
-
-impl<'a, 'hir> IsMultiSpanScanner<'a, 'hir> {
-    fn new(cx: &'a LateContext<'hir>) -> Self {
-        Self {
-            cx,
-            suggestion_count: 0,
-        }
-    }
-
-    /// Add a new single expression suggestion to the counter
-    fn add_single_span_suggestion(&mut self) {
-        self.suggestion_count += 1;
-    }
-
-    /// Signals that a suggestion with possible multiple spans was found
-    fn add_multi_part_suggestion(&mut self) {
-        self.suggestion_count += 2;
-    }
-
-    /// Checks if the suggestions include multiple spans
-    fn is_multi_part(&self) -> bool {
-        self.suggestion_count > 1
-    }
-}
-
-impl<'a, 'hir> Visitor<'hir> for IsMultiSpanScanner<'a, 'hir> {
-    type NestedFilter = nested_filter::All;
-
-    fn nested_visit_map(&mut self) -> Self::Map {
-        self.cx.tcx.hir()
-    }
-
-    fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
-        // Early return if the lint is already multi span
-        if self.is_multi_part() {
-            return;
-        }
-
-        if let ExprKind::MethodCall(path, recv, _, _arg_span) = &expr.kind {
-            let (self_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(recv));
-            if match_type(self.cx, self_ty, &paths::DIAG) {
-                let called_method = path.ident.name.as_str().to_string();
-                for (method_name, is_multi_part) in &SUGGESTION_DIAG_METHODS {
-                    if *method_name == called_method {
-                        if *is_multi_part {
-                            self.add_multi_part_suggestion();
-                        } else {
-                            self.add_single_span_suggestion();
-                        }
-                        break;
-                    }
-                }
-            }
-        }
-
-        intravisit::walk_expr(self, expr);
-    }
-}