| use crate::parse::cursor::Cursor; |
| use crate::parse::{DeprecatedLint, Lint, ParseCx, RenamedLint}; |
| 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 lints = cx.find_lint_decls(); |
| let (deprecated, renamed) = cx.read_deprecated_lints(); |
| generate_lint_files(update_mode, &lints, &deprecated, &renamed); |
| } |
| |
| #[expect(clippy::too_many_lines)] |
| pub fn generate_lint_files( |
| update_mode: UpdateMode, |
| lints: &[Lint<'_>], |
| deprecated: &[DeprecatedLint<'_>], |
| renamed: &[RenamedLint<'_>], |
| ) { |
| let mut updater = FileUpdater::default(); |
| 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, "{}", round_to_fifty(lints.len())).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, "{}", round_to_fifty(lints.len())).unwrap(); |
| }), |
| ); |
| 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 |
| .iter() |
| .map(|l| l.name) |
| .chain(deprecated.iter().filter_map(|l| l.name.strip_prefix("clippy::"))) |
| .chain(renamed.iter().filter_map(|l| l.old_name.strip_prefix("clippy::"))) |
| .sorted() |
| { |
| writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").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 lint in deprecated { |
| write!( |
| dst, |
| " #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n", |
| lint.version, lint.name, lint.reason, |
| ) |
| .unwrap(); |
| } |
| dst.push_str( |
| "]}\n\n\ |
| #[rustfmt::skip]\n\ |
| declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ |
| ", |
| ); |
| for lint in renamed { |
| write!( |
| dst, |
| " #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n", |
| lint.version, lint.old_name, lint.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({})] //~ ERROR: lint `{}`", lint.name, lint.name).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(); |
| } |
| } |
| seen_lints.clear(); |
| for lint in renamed { |
| if seen_lints.insert(lint.old_name) { |
| writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap(); |
| } |
| } |
| dst.push_str("\nfn main() {}\n"); |
| UpdateStatus::from_changed(src != dst) |
| }, |
| ); |
| for (crate_name, lints) in lints.iter().into_group_map_by(|&l| { |
| let Some(path::Component::Normal(name)) = l.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 `{}`", l.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| { |
| for lint_mod in lints |
| .iter() |
| .filter(|l| !l.module.is_empty()) |
| .map(|l| l.module.split_once("::").map_or(l.module, |x| x.0)) |
| .sorted() |
| .dedup() |
| { |
| writeln!(dst, "mod {lint_mod};").unwrap(); |
| } |
| }, |
| ), |
| ); |
| 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"); |
| for (module_path, lint_name) in lints.iter().map(|l| (&l.module, l.name.to_uppercase())).sorted() { |
| if module_path.is_empty() { |
| writeln!(dst, " crate::{lint_name}_INFO,").unwrap(); |
| } else { |
| writeln!(dst, " crate::{module_path}::{lint_name}_INFO,").unwrap(); |
| } |
| } |
| dst.push_str("];\n"); |
| UpdateStatus::from_changed(src != dst) |
| }, |
| ); |
| } |
| } |
| |
| fn round_to_fifty(count: usize) -> usize { |
| count / 50 * 50 |
| } |