| use crate::parse::cursor::Cursor; |
| use crate::parse::{Lint, LintData, ParseCx}; |
| use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; |
| use itertools::Itertools; |
| use std::collections::HashSet; |
| use std::fmt::Write; |
| use std::path::{self, Path}; |
| |
| 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"; |
| |
| const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; |
| |
| /// Runs the `update_lints` command. |
| /// |
| /// This updates various generated values from the lint source code. |
| /// |
| /// `update_mode` indicates if the files should be updated or if updates should be checked for. |
| /// |
| /// # Panics |
| /// |
| /// Panics if a file path could not read from or then written to |
| pub fn update(cx: ParseCx<'_>, update_mode: UpdateMode) { |
| let data = cx.parse_lint_decls(); |
| generate_lint_files(update_mode, &data); |
| } |
| |
| #[expect(clippy::too_many_lines)] |
| pub fn generate_lint_files(update_mode: UpdateMode, data: &LintData<'_>) { |
| let mut updater = FileUpdater::default(); |
| |
| let mut lints: Vec<_> = data.lints.iter().map(|(&x, y)| (x, y)).collect(); |
| lints.sort_by_key(|&(x, _)| x); |
| updater.update_file_checked( |
| "cargo dev update_lints", |
| update_mode, |
| "CHANGELOG.md", |
| &mut update_text_region_fn( |
| "<!-- begin autogenerated links to lint list -->\n", |
| "<!-- end autogenerated links to lint list -->", |
| |dst| { |
| for &(lint, _) in &lints { |
| writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); |
| } |
| }, |
| ), |
| ); |
| |
| let mut active = Vec::with_capacity(lints.len()); |
| let mut deprecated = Vec::with_capacity(lints.len() / 8); |
| let mut renamed = Vec::with_capacity(lints.len() / 8); |
| for &(name, lint) in &lints { |
| match lint { |
| Lint::Active(lint) => active.push((name, lint)), |
| Lint::Deprecated(lint) => deprecated.push((name, lint)), |
| Lint::Renamed(lint) => renamed.push((name, lint)), |
| } |
| } |
| active.sort_by_key(|&(_, lint)| lint.module); |
| |
| // Round to avoid updating the readme every time a lint is added/deprecated. |
| let lint_count = active.len() / 50 * 50; |
| updater.update_file_checked( |
| "cargo dev update_lints", |
| update_mode, |
| "README.md", |
| &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { |
| write!(dst, "{lint_count}").unwrap(); |
| }), |
| ); |
| updater.update_file_checked( |
| "cargo dev update_lints", |
| update_mode, |
| "book/src/README.md", |
| &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { |
| write!(dst, "{lint_count}").unwrap(); |
| }), |
| ); |
| |
| updater.update_file_checked( |
| "cargo dev update_lints", |
| update_mode, |
| "clippy_lints/src/deprecated_lints.rs", |
| &mut |_, src, dst| { |
| let mut cursor = Cursor::new(src); |
| assert!( |
| cursor.find_ident("declare_with_version").is_some() |
| && cursor.find_ident("declare_with_version").is_some(), |
| "error reading deprecated lints" |
| ); |
| dst.push_str(&src[..cursor.pos() as usize]); |
| dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); |
| for &(name, data) in &deprecated { |
| write!( |
| dst, |
| " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", |
| data.version, data.reason, |
| ) |
| .unwrap(); |
| } |
| dst.push_str( |
| "]}\n\n\ |
| #[rustfmt::skip]\n\ |
| declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ |
| ", |
| ); |
| for &(name, data) in &renamed { |
| write!( |
| dst, |
| " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", |
| data.version, data.new_name, |
| ) |
| .unwrap(); |
| } |
| dst.push_str("]}\n"); |
| UpdateStatus::from_changed(src != dst) |
| }, |
| ); |
| updater.update_file_checked( |
| "cargo dev update_lints", |
| update_mode, |
| "tests/ui/deprecated.rs", |
| &mut |_, src, dst| { |
| dst.push_str(GENERATED_FILE_COMMENT); |
| for &(lint, _) in &deprecated { |
| writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); |
| } |
| dst.push_str("\nfn main() {}\n"); |
| UpdateStatus::from_changed(src != dst) |
| }, |
| ); |
| updater.update_file_checked( |
| "cargo dev update_lints", |
| update_mode, |
| "tests/ui/rename.rs", |
| &mut move |_, src, dst| { |
| let mut seen_lints = HashSet::new(); |
| dst.push_str(GENERATED_FILE_COMMENT); |
| dst.push_str("#![allow(clippy::duplicated_attributes)]\n"); |
| for &(_, lint) in &renamed { |
| if seen_lints.insert(lint.new_name) { |
| writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); |
| } |
| } |
| for &(lint, _) in &renamed { |
| writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); |
| } |
| dst.push_str("\nfn main() {}\n"); |
| UpdateStatus::from_changed(src != dst) |
| }, |
| ); |
| for (crate_name, lints) in active.iter().copied().into_group_map_by(|&(_, lint)| { |
| let Some(path::Component::Normal(name)) = lint.path.components().next() else { |
| // All paths should start with `{crate_name}/src` when parsed from `find_lint_decls` |
| panic!( |
| "internal error: can't read crate name from path `{}`", |
| lint.path.display() |
| ); |
| }; |
| name |
| }) { |
| updater.update_file_checked( |
| "cargo dev update_lints", |
| update_mode, |
| Path::new(crate_name).join("src/lib.rs"), |
| &mut update_text_region_fn( |
| "// begin lints modules, do not remove this comment, it's used in `update_lints`\n", |
| "// end lints modules, do not remove this comment, it's used in `update_lints`", |
| |dst| { |
| let mut prev = ""; |
| for &(_, lint) in &lints { |
| if lint.module != prev { |
| writeln!(dst, "mod {};", lint.module).unwrap(); |
| prev = lint.module; |
| } |
| } |
| }, |
| ), |
| ); |
| updater.update_file_checked( |
| "cargo dev update_lints", |
| update_mode, |
| Path::new(crate_name).join("src/declared_lints.rs"), |
| &mut |_, src, dst| { |
| dst.push_str(GENERATED_FILE_COMMENT); |
| dst.push_str("pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[\n"); |
| let mut buf = String::new(); |
| for &(name, lint) in &lints { |
| buf.clear(); |
| buf.push_str(name); |
| buf.make_ascii_uppercase(); |
| if lint.module.is_empty() { |
| writeln!(dst, " crate::{buf}_INFO,").unwrap(); |
| } else { |
| writeln!(dst, " crate::{}::{buf}_INFO,", lint.module).unwrap(); |
| } |
| } |
| dst.push_str("];\n"); |
| UpdateStatus::from_changed(src != dst) |
| }, |
| ); |
| } |
| } |