about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_dev/src/deprecate_lint.rs38
-rw-r--r--clippy_dev/src/rename_lint.rs45
-rw-r--r--clippy_dev/src/update_lints.rs140
-rw-r--r--clippy_dev/src/utils.rs63
-rw-r--r--clippy_lints/src/deprecated_lints.rs2
5 files changed, 172 insertions, 116 deletions
diff --git a/clippy_dev/src/deprecate_lint.rs b/clippy_dev/src/deprecate_lint.rs
index 2c4286b325d..bf0e7771046 100644
--- a/clippy_dev/src/deprecate_lint.rs
+++ b/clippy_dev/src/deprecate_lint.rs
@@ -1,5 +1,7 @@
-use crate::update_lints::{DeprecatedLint, Lint, gather_all, generate_lint_files};
-use crate::utils::{UpdateMode, Version, insert_at_marker, rewrite_file};
+use crate::update_lints::{
+    DeprecatedLint, DeprecatedLints, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints,
+};
+use crate::utils::{UpdateMode, Version};
 use std::ffi::OsStr;
 use std::path::{Path, PathBuf};
 use std::{fs, io};
@@ -21,7 +23,16 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
     };
     let stripped_name = &prefixed_name[8..];
 
-    let (mut lints, mut deprecated_lints, renamed_lints) = gather_all();
+    let mut lints = find_lint_decls();
+    let DeprecatedLints {
+        renamed: renamed_lints,
+        deprecated: mut deprecated_lints,
+        file: mut deprecated_file,
+        contents: mut deprecated_contents,
+        deprecated_end,
+        ..
+    } = read_deprecated_lints();
+
     let Some(lint) = lints.iter().find(|l| l.name == stripped_name) else {
         eprintln!("error: failed to find lint `{name}`");
         return;
@@ -38,16 +49,17 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
     };
 
     if remove_lint_declaration(stripped_name, &mod_path, &mut lints).unwrap_or(false) {
-        rewrite_file("clippy_lints/src/deprecated_lints.rs".as_ref(), |s| {
-            insert_at_marker(
-                s,
-                "// end deprecated lints. used by `cargo dev deprecate_lint`",
-                &format!(
-                    "#[clippy::version = \"{}\"]\n    (\"{prefixed_name}\", \"{reason}\"),\n    ",
-                    clippy_version.rust_display(),
-                ),
-            )
-        });
+        deprecated_contents.insert_str(
+            deprecated_end as usize,
+            &format!(
+                "    #[clippy::version = \"{}\"]\n    (\"{}\", \"{}\"),\n",
+                clippy_version.rust_display(),
+                prefixed_name,
+                reason,
+            ),
+        );
+        deprecated_file.replace_contents(deprecated_contents.as_bytes());
+        drop(deprecated_file);
 
         deprecated_lints.push(DeprecatedLint {
             name: prefixed_name,
diff --git a/clippy_dev/src/rename_lint.rs b/clippy_dev/src/rename_lint.rs
index c78ec98c1aa..9e7e5d97f02 100644
--- a/clippy_dev/src/rename_lint.rs
+++ b/clippy_dev/src/rename_lint.rs
@@ -1,7 +1,8 @@
 use crate::update_lints::{
-    RenamedLint, clippy_lints_src_files, gather_all, gen_renamed_lints_test_fn, generate_lint_files,
+    DeprecatedLints, RenamedLint, find_lint_decls, gen_renamed_lints_test_fn, generate_lint_files,
+    read_deprecated_lints,
 };
-use crate::utils::{FileUpdater, StringReplacer, UpdateMode, Version, insert_at_marker, rewrite_file, try_rename_file};
+use crate::utils::{FileUpdater, StringReplacer, UpdateMode, Version, try_rename_file};
 use std::ffi::OsStr;
 use std::path::Path;
 use walkdir::WalkDir;
@@ -31,7 +32,16 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
     }
 
     let mut updater = FileUpdater::default();
-    let (mut lints, deprecated_lints, mut renamed_lints) = gather_all();
+    let mut lints = find_lint_decls();
+    let DeprecatedLints {
+        renamed: mut renamed_lints,
+        deprecated: deprecated_lints,
+        file: mut deprecated_file,
+        contents: mut deprecated_contents,
+        renamed_end,
+        ..
+    } = read_deprecated_lints();
+
     let mut old_lint_index = None;
     let mut found_new_name = false;
     for (i, lint) in lints.iter().enumerate() {
@@ -76,19 +86,17 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
         updater.update_file(file.path(), &mut replacer.replace_ident_fn());
     }
 
-    rewrite_file(Path::new("clippy_lints/src/deprecated_lints.rs"), |s| {
-        insert_at_marker(
-            s,
-            "// end renamed lints. used by `cargo dev rename_lint`",
-            &format!(
-                "#[clippy::version = \"{}\"]\n    \
-                (\"{}\", \"{}\"),\n    ",
-                clippy_version.rust_display(),
-                lint.old_name,
-                lint.new_name,
-            ),
-        )
-    });
+    deprecated_contents.insert_str(
+        renamed_end as usize,
+        &format!(
+            "    #[clippy::version = \"{}\"]\n    (\"{}\", \"{}\"),\n",
+            clippy_version.rust_display(),
+            lint.old_name,
+            lint.new_name,
+        ),
+    );
+    deprecated_file.replace_contents(deprecated_contents.as_bytes());
+    drop(deprecated_file);
 
     renamed_lints.push(lint);
     renamed_lints.sort_by(|lhs, rhs| {
@@ -166,12 +174,13 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
         // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being
         // renamed.
         let replacer = StringReplacer::new(replacements);
-        for file in clippy_lints_src_files() {
+        for file in WalkDir::new("clippy_lints/src") {
+            let file = file.expect("error reading `clippy_lints/src`");
             if file
                 .path()
                 .as_os_str()
                 .to_str()
-                .is_none_or(|x| x["clippy_lints/src/".len()..] != *"deprecated_lints.rs")
+                .is_some_and(|x| x.ends_with("*.rs") && x["clippy_lints/src/".len()..] != *"deprecated_lints.rs")
             {
                 updater.update_file(file.path(), &mut replacer.replace_ident_fn());
             }
diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs
index ad995f5e4c2..28c988bc19f 100644
--- a/clippy_dev/src/update_lints.rs
+++ b/clippy_dev/src/update_lints.rs
@@ -1,11 +1,11 @@
-use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn};
+use crate::utils::{File, FileAction, FileUpdater, UpdateMode, UpdateStatus, panic_file, update_text_region_fn};
+use core::str;
 use itertools::Itertools;
 use rustc_lexer::{LiteralKind, TokenKind, tokenize};
 use rustc_literal_escaper::{Mode, unescape_unicode};
 use std::collections::{HashMap, HashSet};
-use std::ffi::OsStr;
 use std::fmt::Write;
-use std::fs;
+use std::fs::OpenOptions;
 use std::ops::Range;
 use std::path::Path;
 use walkdir::{DirEntry, WalkDir};
@@ -26,8 +26,11 @@ const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.ht
 ///
 /// Panics if a file path could not read from or then written to
 pub fn update(update_mode: UpdateMode) {
-    let (lints, deprecated_lints, renamed_lints) = gather_all();
-    generate_lint_files(update_mode, &lints, &deprecated_lints, &renamed_lints);
+    let lints = find_lint_decls();
+    let DeprecatedLints {
+        renamed, deprecated, ..
+    } = read_deprecated_lints();
+    generate_lint_files(update_mode, &lints, &deprecated, &renamed);
 }
 
 pub fn generate_lint_files(
@@ -36,8 +39,6 @@ pub fn generate_lint_files(
     deprecated: &[DeprecatedLint],
     renamed: &[RenamedLint],
 ) {
-    let mut lints = lints.to_owned();
-    lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
     FileUpdater::default().update_files_checked(
         "cargo dev update_lints",
         update_mode,
@@ -107,7 +108,7 @@ pub fn generate_lint_files(
 }
 
 pub fn print_lints() {
-    let (lints, _, _) = gather_all();
+    let lints = find_lint_decls();
     let lint_count = lints.len();
     let grouped_by_lint_group = Lint::by_lint_group(lints.into_iter());
 
@@ -205,40 +206,54 @@ pub fn gen_renamed_lints_test_fn(lints: &[RenamedLint]) -> impl Fn(&Path, &str,
     }
 }
 
-/// Gathers all lints defined in `clippy_lints/src`
+/// Finds all lint declarations (`declare_clippy_lint!`)
 #[must_use]
-pub fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>) {
+pub fn find_lint_decls() -> Vec<Lint> {
     let mut lints = Vec::with_capacity(1000);
-    let mut deprecated_lints = Vec::with_capacity(50);
-    let mut renamed_lints = Vec::with_capacity(50);
-
-    for file in clippy_lints_src_files() {
-        let path = file.path();
-        let contents =
-            fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {e}", path.display()));
-        let module = path.as_os_str().to_str().unwrap()["clippy_lints/src/".len()..].replace(['/', '\\'], "::");
-
-        // If the lints are stored in mod.rs, we get the module name from
-        // the containing directory:
-        let module = if let Some(module) = module.strip_suffix("::mod.rs") {
-            module
-        } else {
-            module.strip_suffix(".rs").unwrap_or(&module)
-        };
-
-        if module == "deprecated_lints" {
-            parse_deprecated_contents(&contents, &mut deprecated_lints, &mut renamed_lints);
-        } else {
-            parse_contents(&contents, module, &mut lints);
-        }
+    let mut contents = String::new();
+    for (file, module) in read_src_with_module("clippy_lints/src".as_ref()) {
+        parse_clippy_lint_decls(
+            File::open_read_to_cleared_string(file.path(), &mut contents),
+            &module,
+            &mut lints,
+        );
     }
-    (lints, deprecated_lints, renamed_lints)
+    lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
+    lints
 }
 
-pub fn clippy_lints_src_files() -> impl Iterator<Item = DirEntry> {
-    let iter = WalkDir::new("clippy_lints/src").into_iter();
-    iter.map(Result::unwrap)
-        .filter(|f| f.path().extension() == Some(OsStr::new("rs")))
+/// Reads the source files from the given root directory
+fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator<Item = (DirEntry, String)> {
+    WalkDir::new(src_root).into_iter().filter_map(move |e| {
+        let e = match e {
+            Ok(e) => e,
+            Err(ref e) => panic_file(e, FileAction::Read, src_root),
+        };
+        let path = e.path().as_os_str().as_encoded_bytes();
+        if let Some(path) = path.strip_suffix(b".rs")
+            && let Some(path) = path.get("clippy_lints/src/".len()..)
+        {
+            if path == b"lib" {
+                Some((e, String::new()))
+            } else {
+                let path = if let Some(path) = path.strip_suffix(b"mod")
+                    && let Some(path) = path.strip_suffix(b"/").or_else(|| path.strip_suffix(b"\\"))
+                {
+                    path
+                } else {
+                    path
+                };
+                if let Ok(path) = str::from_utf8(path) {
+                    let path = path.replace(['/', '\\'], "::");
+                    Some((e, path))
+                } else {
+                    None
+                }
+            }
+        } else {
+            None
+        }
+    })
 }
 
 macro_rules! match_tokens {
@@ -266,7 +281,7 @@ pub(crate) struct LintDeclSearchResult<'a> {
 }
 
 /// Parse a source file looking for `declare_clippy_lint` macro invocations.
-fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
+fn parse_clippy_lint_decls(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 as usize;
@@ -333,15 +348,40 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
     }
 }
 
-/// Parse a source file looking for `declare_deprecated_lint` macro invocations.
-fn parse_deprecated_contents(contents: &str, deprecated: &mut Vec<DeprecatedLint>, renamed: &mut Vec<RenamedLint>) {
-    let Some((_, contents)) = contents.split_once("\ndeclare_with_version! { DEPRECATED") else {
-        return;
-    };
-    let Some((deprecated_src, renamed_src)) = contents.split_once("\ndeclare_with_version! { RENAMED") else {
-        return;
+pub struct DeprecatedLints {
+    pub file: File<'static>,
+    pub contents: String,
+    pub deprecated: Vec<DeprecatedLint>,
+    pub renamed: Vec<RenamedLint>,
+    pub deprecated_end: u32,
+    pub renamed_end: u32,
+}
+
+#[must_use]
+#[expect(clippy::cast_possible_truncation)]
+pub fn read_deprecated_lints() -> DeprecatedLints {
+    let mut res = DeprecatedLints {
+        file: File::open(
+            "clippy_lints/src/deprecated_lints.rs",
+            OpenOptions::new().read(true).write(true),
+        ),
+        contents: String::new(),
+        deprecated: Vec::with_capacity(30),
+        renamed: Vec::with_capacity(80),
+        deprecated_end: 0,
+        renamed_end: 0,
     };
 
+    res.file.read_append_to_string(&mut res.contents);
+
+    let (_, contents) = res.contents.split_once("\ndeclare_with_version! { DEPRECATED").unwrap();
+    let (deprecated_src, contents) = contents.split_once("\n]}").unwrap();
+    res.deprecated_end = (res.contents.len() - contents.len() - 2) as u32;
+
+    let (_, contents) = contents.split_once("\ndeclare_with_version! { RENAMED").unwrap();
+    let (renamed_src, contents) = contents.split_once("\n]}").unwrap();
+    res.renamed_end = (res.contents.len() - contents.len() - 2) as u32;
+
     for line in deprecated_src.lines() {
         let mut offset = 0usize;
         let mut iter = tokenize(line).map(|t| {
@@ -362,7 +402,7 @@ fn parse_deprecated_contents(contents: &str, deprecated: &mut Vec<DeprecatedLint
             // "new_name"),
             Whitespace Literal{kind: LiteralKind::Str{..},..}(reason) CloseParen Comma
         );
-        deprecated.push(DeprecatedLint::new(name, reason));
+        res.deprecated.push(DeprecatedLint::new(name, reason));
     }
     for line in renamed_src.lines() {
         let mut offset = 0usize;
@@ -384,8 +424,10 @@ fn parse_deprecated_contents(contents: &str, deprecated: &mut Vec<DeprecatedLint
             // "new_name"),
             Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma
         );
-        renamed.push(RenamedLint::new(old_name, new_name));
+        res.renamed.push(RenamedLint::new(old_name, new_name));
     }
+
+    res
 }
 
 /// Removes the line splices and surrounding quotes from a string literal
@@ -411,7 +453,7 @@ mod tests {
     use super::*;
 
     #[test]
-    fn test_parse_contents() {
+    fn test_parse_clippy_lint_decls() {
         static CONTENTS: &str = r#"
             declare_clippy_lint! {
                 #[clippy::version = "Hello Clippy!"]
@@ -429,7 +471,7 @@ mod tests {
             }
         "#;
         let mut result = Vec::new();
-        parse_contents(CONTENTS, "module_name", &mut result);
+        parse_clippy_lint_decls(CONTENTS, "module_name", &mut result);
         for r in &mut result {
             r.declaration_range = Range::default();
         }
diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs
index 9f48832af00..87645aff674 100644
--- a/clippy_dev/src/utils.rs
+++ b/clippy_dev/src/utils.rs
@@ -12,10 +12,30 @@ static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
 #[cfg(windows)]
 static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
 
+#[derive(Clone, Copy)]
+pub enum FileAction {
+    Open,
+    Read,
+    Write,
+    Create,
+    Rename,
+}
+impl FileAction {
+    fn as_str(self) -> &'static str {
+        match self {
+            Self::Open => "opening",
+            Self::Read => "reading",
+            Self::Write => "writing",
+            Self::Create => "creating",
+            Self::Rename => "renaming",
+        }
+    }
+}
+
 #[cold]
 #[track_caller]
-fn panic_io(e: &io::Error, action: &str, path: &Path) -> ! {
-    panic!("error {action} `{}`: {}", path.display(), *e)
+pub fn panic_file(err: &impl Display, action: FileAction, path: &Path) -> ! {
+    panic!("error {} `{}`: {}", action.as_str(), path.display(), *err)
 }
 
 /// Wrapper around `std::fs::File` which panics with a path on failure.
@@ -30,7 +50,7 @@ impl<'a> File<'a> {
         let path = path.as_ref();
         match options.open(path) {
             Ok(inner) => Self { inner, path },
-            Err(e) => panic_io(&e, "opening", path),
+            Err(e) => panic_file(&e, FileAction::Open, path),
         }
     }
 
@@ -41,7 +61,7 @@ impl<'a> File<'a> {
         match options.open(path) {
             Ok(inner) => Some(Self { inner, path }),
             Err(e) if e.kind() == io::ErrorKind::NotFound => None,
-            Err(e) => panic_io(&e, "opening", path),
+            Err(e) => panic_file(&e, FileAction::Open, path),
         }
     }
 
@@ -59,7 +79,7 @@ impl<'a> File<'a> {
     pub fn read_append_to_string<'dst>(&mut self, dst: &'dst mut String) -> &'dst mut String {
         match self.inner.read_to_string(dst) {
             Ok(_) => {},
-            Err(e) => panic_io(&e, "reading", self.path),
+            Err(e) => panic_file(&e, FileAction::Read, self.path),
         }
         dst
     }
@@ -81,7 +101,7 @@ impl<'a> File<'a> {
             Err(e) => Err(e),
         };
         if let Err(e) = res {
-            panic_io(&e, "writing", self.path);
+            panic_file(&e, FileAction::Write, self.path);
         }
     }
 }
@@ -391,7 +411,7 @@ pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
     match OpenOptions::new().create_new(true).write(true).open(new_name) {
         Ok(file) => drop(file),
         Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false,
-        Err(e) => panic_io(&e, "creating", new_name),
+        Err(e) => panic_file(&e, FileAction::Create, new_name),
     }
     match fs::rename(old_name, new_name) {
         Ok(()) => true,
@@ -400,37 +420,12 @@ pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
             if e.kind() == io::ErrorKind::NotFound {
                 false
             } else {
-                panic_io(&e, "renaming", old_name);
+                panic_file(&e, FileAction::Rename, old_name);
             }
         },
     }
 }
 
-#[must_use]
-pub fn insert_at_marker(text: &str, marker: &str, new_text: &str) -> Option<String> {
-    let i = text.find(marker)?;
-    let (pre, post) = text.split_at(i);
-    Some([pre, new_text, post].into_iter().collect())
-}
-
-pub fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option<String>) {
-    let mut file = OpenOptions::new()
-        .write(true)
-        .read(true)
-        .open(path)
-        .unwrap_or_else(|e| panic_io(&e, "opening", path));
-    let mut buf = String::new();
-    file.read_to_string(&mut buf)
-        .unwrap_or_else(|e| panic_io(&e, "reading", path));
-    if let Some(new_contents) = f(&buf) {
-        file.rewind().unwrap_or_else(|e| panic_io(&e, "writing", path));
-        file.write_all(new_contents.as_bytes())
-            .unwrap_or_else(|e| panic_io(&e, "writing", path));
-        file.set_len(new_contents.len() as u64)
-            .unwrap_or_else(|e| panic_io(&e, "writing", path));
-    }
-}
-
 pub fn write_file(path: &Path, contents: &str) {
-    fs::write(path, contents).unwrap_or_else(|e| panic_io(&e, "writing", path));
+    fs::write(path, contents).unwrap_or_else(|e| panic_file(&e, FileAction::Write, path));
 }
diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs
index a1909c5363f..d37b0efd35d 100644
--- a/clippy_lints/src/deprecated_lints.rs
+++ b/clippy_lints/src/deprecated_lints.rs
@@ -44,7 +44,6 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[
     ("clippy::option_map_or_err_ok", "`clippy::manual_ok_or` covers this case"),
     #[clippy::version = "1.86.0"]
     ("clippy::match_on_vec_items", "`clippy::indexing_slicing` covers indexing and slicing on `Vec<_>`"),
-    // end deprecated lints. used by `cargo dev deprecate_lint`
 ]}
 
 #[rustfmt::skip]
@@ -195,5 +194,4 @@ declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
     ("clippy::transmute_float_to_int", "unnecessary_transmutes"),
     #[clippy::version = "1.88.0"]
     ("clippy::transmute_num_to_bytes", "unnecessary_transmutes"),
-    // end renamed lints. used by `cargo dev rename_lint`
 ]}