about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Tardieu <sam@rfc1149.net>2025-05-17 10:16:07 +0000
committerGitHub <noreply@github.com>2025-05-17 10:16:07 +0000
commit9ebfb84fe04e512a17ad6661030a4c6d9dca8d6b (patch)
treed13ebf6fe4986d1c23745e4d7612ce1556d6bb33
parentc97b4761d8790e61b84ba7e2d26ddb787a4b88a1 (diff)
parent232be558592cd9b75adb20cfd3b290672c78804e (diff)
downloadrust-9ebfb84fe04e512a17ad6661030a4c6d9dca8d6b.tar.gz
rust-9ebfb84fe04e512a17ad6661030a4c6d9dca8d6b.zip
Refactor and speed up `cargo dev fmt` (#14638)
Based on rust-lang/rust-clippy#14616

`cargo dev fmt` should now run almost instantly rather than taking a few
seconds.

changelog: None
-rw-r--r--Cargo.toml2
-rw-r--r--clippy_config/Cargo.toml2
-rw-r--r--clippy_dev/Cargo.toml1
-rw-r--r--clippy_dev/src/fmt.rs281
-rw-r--r--clippy_dev/src/lib.rs1
-rw-r--r--clippy_dev/src/main.rs5
-rw-r--r--clippy_dev/src/release.rs24
-rw-r--r--clippy_dev/src/update_lints.rs4
-rw-r--r--clippy_dev/src/utils.rs196
-rw-r--r--clippy_lints/Cargo.toml2
-rw-r--r--clippy_utils/Cargo.toml2
-rw-r--r--tests/ui/skip_rustfmt/non_expressive_names_error_recovery.fixed (renamed from tests/ui/syntax-error-recovery/non_expressive_names_error_recovery.fixed)0
-rw-r--r--tests/ui/skip_rustfmt/non_expressive_names_error_recovery.rs (renamed from tests/ui/syntax-error-recovery/non_expressive_names_error_recovery.rs)0
-rw-r--r--tests/ui/skip_rustfmt/non_expressive_names_error_recovery.stderr (renamed from tests/ui/syntax-error-recovery/non_expressive_names_error_recovery.stderr)2
14 files changed, 278 insertions, 244 deletions
diff --git a/Cargo.toml b/Cargo.toml
index b6a1b9314c6..2da350ba44e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,8 +1,6 @@
 [package]
 name = "clippy"
-# begin autogenerated version
 version = "0.1.89"
-# end autogenerated version
 description = "A bunch of helpful lints to avoid common pitfalls in Rust"
 repository = "https://github.com/rust-lang/rust-clippy"
 readme = "README.md"
diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml
index 1134b0e97af..0606245f990 100644
--- a/clippy_config/Cargo.toml
+++ b/clippy_config/Cargo.toml
@@ -1,8 +1,6 @@
 [package]
 name = "clippy_config"
-# begin autogenerated version
 version = "0.1.89"
-# end autogenerated version
 edition = "2024"
 publish = false
 
diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml
index a963fba7d98..10c08dba50b 100644
--- a/clippy_dev/Cargo.toml
+++ b/clippy_dev/Cargo.toml
@@ -10,7 +10,6 @@ clap = { version = "4.4", features = ["derive"] }
 indoc = "1.0"
 itertools = "0.12"
 opener = "0.7"
-shell-escape = "0.1"
 walkdir = "2.3"
 
 [package.metadata.rust-analyzer]
diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs
index b4c13213f55..298326174a4 100644
--- a/clippy_dev/src/fmt.rs
+++ b/clippy_dev/src/fmt.rs
@@ -1,19 +1,15 @@
+use crate::utils::{ClippyInfo, ErrAction, UpdateMode, panic_action, run_with_args_split, run_with_output};
 use itertools::Itertools;
 use rustc_lexer::{TokenKind, tokenize};
-use shell_escape::escape;
-use std::ffi::{OsStr, OsString};
+use std::fs;
+use std::io::{self, Read};
 use std::ops::ControlFlow;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 use std::process::{self, Command, Stdio};
-use std::{fs, io};
 use walkdir::WalkDir;
 
 pub enum Error {
-    CommandFailed(String, String),
     Io(io::Error),
-    RustfmtNotInstalled,
-    WalkDir(walkdir::Error),
-    IntellijSetupActive,
     Parse(PathBuf, usize, String),
     CheckFailed,
 }
@@ -24,37 +20,15 @@ impl From<io::Error> for Error {
     }
 }
 
-impl From<walkdir::Error> for Error {
-    fn from(error: walkdir::Error) -> Self {
-        Self::WalkDir(error)
-    }
-}
-
 impl Error {
     fn display(&self) {
         match self {
             Self::CheckFailed => {
                 eprintln!("Formatting check failed!\nRun `cargo dev fmt` to update.");
             },
-            Self::CommandFailed(command, stderr) => {
-                eprintln!("error: command `{command}` failed!\nstderr: {stderr}");
-            },
             Self::Io(err) => {
                 eprintln!("error: {err}");
             },
-            Self::RustfmtNotInstalled => {
-                eprintln!("error: rustfmt nightly is not installed.");
-            },
-            Self::WalkDir(err) => {
-                eprintln!("error: {err}");
-            },
-            Self::IntellijSetupActive => {
-                eprintln!(
-                    "error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`.\n\
-                    Not formatting because that would format the local repo as well!\n\
-                    Please revert the changes to `Cargo.toml`s with `cargo dev remove intellij`."
-                );
-            },
             Self::Parse(path, line, msg) => {
                 eprintln!("error parsing `{}:{line}`: {msg}", path.display());
             },
@@ -62,12 +36,6 @@ impl Error {
     }
 }
 
-struct FmtContext {
-    check: bool,
-    verbose: bool,
-    rustfmt_path: String,
-}
-
 struct ClippyConf<'a> {
     name: &'a str,
     attrs: &'a str,
@@ -257,155 +225,120 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
     Ok(())
 }
 
-fn run_rustfmt(context: &FmtContext) -> Result<(), Error> {
-    // if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to
-    // format because rustfmt would also format the entire rustc repo as it is a local
-    // dependency
-    if fs::read_to_string("Cargo.toml")
-        .expect("Failed to read clippy Cargo.toml")
-        .contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]")
-    {
-        return Err(Error::IntellijSetupActive);
-    }
-
-    check_for_rustfmt(context)?;
+fn run_rustfmt(clippy: &ClippyInfo, update_mode: UpdateMode) {
+    let mut rustfmt_path = String::from_utf8(run_with_output(
+        "rustup which rustfmt",
+        Command::new("rustup").args(["which", "rustfmt"]),
+    ))
+    .expect("invalid rustfmt path");
+    rustfmt_path.truncate(rustfmt_path.trim_end().len());
 
-    cargo_fmt(context, ".".as_ref())?;
-    cargo_fmt(context, "clippy_dev".as_ref())?;
-    cargo_fmt(context, "rustc_tools_util".as_ref())?;
-    cargo_fmt(context, "lintcheck".as_ref())?;
+    let mut cargo_path = String::from_utf8(run_with_output(
+        "rustup which cargo",
+        Command::new("rustup").args(["which", "cargo"]),
+    ))
+    .expect("invalid cargo path");
+    cargo_path.truncate(cargo_path.trim_end().len());
+
+    // Start all format jobs first before waiting on the results.
+    let mut children = Vec::with_capacity(16);
+    for &path in &[
+        ".",
+        "clippy_config",
+        "clippy_dev",
+        "clippy_lints",
+        "clippy_utils",
+        "rustc_tools_util",
+        "lintcheck",
+    ] {
+        let mut cmd = Command::new(&cargo_path);
+        cmd.current_dir(clippy.path.join(path))
+            .args(["fmt"])
+            .env("RUSTFMT", &rustfmt_path)
+            .stdout(Stdio::null())
+            .stdin(Stdio::null())
+            .stderr(Stdio::piped());
+        if update_mode.is_check() {
+            cmd.arg("--check");
+        }
+        match cmd.spawn() {
+            Ok(x) => children.push(("cargo fmt", x)),
+            Err(ref e) => panic_action(&e, ErrAction::Run, "cargo fmt".as_ref()),
+        }
+    }
 
-    let chunks = WalkDir::new("tests")
-        .into_iter()
-        .filter_map(|entry| {
-            let entry = entry.expect("failed to find tests");
-            let path = entry.path();
-            if path.extension() != Some("rs".as_ref())
-                || path
-                    .components()
-                    .nth_back(1)
-                    .is_some_and(|c| c.as_os_str() == "syntax-error-recovery")
-                || entry.file_name() == "ice-3891.rs"
-            {
-                None
-            } else {
-                Some(entry.into_path().into_os_string())
+    run_with_args_split(
+        || {
+            let mut cmd = Command::new(&rustfmt_path);
+            if update_mode.is_check() {
+                cmd.arg("--check");
             }
-        })
-        .chunks(250);
+            cmd.stdout(Stdio::null())
+                .stdin(Stdio::null())
+                .stderr(Stdio::piped())
+                .args(["--config", "show_parse_errors=false"]);
+            cmd
+        },
+        |cmd| match cmd.spawn() {
+            Ok(x) => children.push(("rustfmt", x)),
+            Err(ref e) => panic_action(&e, ErrAction::Run, "rustfmt".as_ref()),
+        },
+        WalkDir::new("tests")
+            .into_iter()
+            .filter_entry(|p| p.path().file_name().is_none_or(|x| x != "skip_rustfmt"))
+            .filter_map(|e| {
+                let e = e.expect("error reading `tests`");
+                e.path()
+                    .as_os_str()
+                    .as_encoded_bytes()
+                    .ends_with(b".rs")
+                    .then(|| e.into_path().into_os_string())
+            }),
+    );
 
-    for chunk in &chunks {
-        rustfmt(context, chunk)?;
+    for (name, child) in &mut children {
+        match child.wait() {
+            Ok(status) => match (update_mode, status.exit_ok()) {
+                (UpdateMode::Check | UpdateMode::Change, Ok(())) => {},
+                (UpdateMode::Check, Err(_)) => {
+                    let mut s = String::new();
+                    if let Some(mut stderr) = child.stderr.take()
+                        && stderr.read_to_string(&mut s).is_ok()
+                    {
+                        eprintln!("{s}");
+                    }
+                    eprintln!("Formatting check failed!\nRun `cargo dev fmt` to update.");
+                    process::exit(1);
+                },
+                (UpdateMode::Change, Err(e)) => {
+                    let mut s = String::new();
+                    if let Some(mut stderr) = child.stderr.take()
+                        && stderr.read_to_string(&mut s).is_ok()
+                    {
+                        eprintln!("{s}");
+                    }
+                    panic_action(&e, ErrAction::Run, name.as_ref());
+                },
+            },
+            Err(ref e) => panic_action(e, ErrAction::Run, name.as_ref()),
+        }
     }
-    Ok(())
 }
 
 // the "main" function of cargo dev fmt
-pub fn run(check: bool, verbose: bool) {
-    let output = Command::new("rustup")
-        .args(["which", "rustfmt"])
-        .stderr(Stdio::inherit())
-        .output()
-        .expect("error running `rustup which rustfmt`");
-    if !output.status.success() {
-        eprintln!("`rustup which rustfmt` did not execute successfully");
-        process::exit(1);
+pub fn run(clippy: &ClippyInfo, update_mode: UpdateMode) {
+    if clippy.has_intellij_hook {
+        eprintln!(
+            "error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`.\n\
+            Not formatting because that would format the local repo as well!\n\
+            Please revert the changes to `Cargo.toml`s with `cargo dev remove intellij`."
+        );
+        return;
     }
-    let mut rustfmt_path = String::from_utf8(output.stdout).expect("invalid rustfmt path");
-    rustfmt_path.truncate(rustfmt_path.trim_end().len());
+    run_rustfmt(clippy, update_mode);
 
-    let context = FmtContext {
-        check,
-        verbose,
-        rustfmt_path,
-    };
-    if let Err(e) = run_rustfmt(&context).and_then(|()| fmt_conf(check)) {
+    if let Err(e) = fmt_conf(update_mode.is_check()) {
         e.display();
         process::exit(1);
     }
 }
-
-fn format_command(program: impl AsRef<OsStr>, dir: impl AsRef<Path>, args: &[impl AsRef<OsStr>]) -> String {
-    let arg_display: Vec<_> = args.iter().map(|a| escape(a.as_ref().to_string_lossy())).collect();
-
-    format!(
-        "cd {} && {} {}",
-        escape(dir.as_ref().to_string_lossy()),
-        escape(program.as_ref().to_string_lossy()),
-        arg_display.join(" ")
-    )
-}
-
-fn exec_fmt_command(
-    context: &FmtContext,
-    program: impl AsRef<OsStr>,
-    dir: impl AsRef<Path>,
-    args: &[impl AsRef<OsStr>],
-) -> Result<(), Error> {
-    if context.verbose {
-        println!("{}", format_command(&program, &dir, args));
-    }
-
-    let output = Command::new(&program)
-        .env("RUSTFMT", &context.rustfmt_path)
-        .current_dir(&dir)
-        .args(args.iter())
-        .output()
-        .unwrap();
-    let success = output.status.success();
-
-    match (context.check, success) {
-        (_, true) => Ok(()),
-        (true, false) => Err(Error::CheckFailed),
-        (false, false) => {
-            let stderr = std::str::from_utf8(&output.stderr).unwrap_or("");
-            Err(Error::CommandFailed(
-                format_command(&program, &dir, args),
-                String::from(stderr),
-            ))
-        },
-    }
-}
-
-fn cargo_fmt(context: &FmtContext, path: &Path) -> Result<(), Error> {
-    let mut args = vec!["fmt", "--all"];
-    if context.check {
-        args.push("--check");
-    }
-    exec_fmt_command(context, "cargo", path, &args)
-}
-
-fn check_for_rustfmt(context: &FmtContext) -> Result<(), Error> {
-    let program = "rustfmt";
-    let dir = std::env::current_dir()?;
-    let args = &["--version"];
-
-    if context.verbose {
-        println!("{}", format_command(program, &dir, args));
-    }
-
-    let output = Command::new(program).current_dir(&dir).args(args.iter()).output()?;
-
-    if output.status.success() {
-        Ok(())
-    } else if std::str::from_utf8(&output.stderr)
-        .unwrap_or("")
-        .starts_with("error: 'rustfmt' is not installed")
-    {
-        Err(Error::RustfmtNotInstalled)
-    } else {
-        Err(Error::CommandFailed(
-            format_command(program, &dir, args),
-            std::str::from_utf8(&output.stderr).unwrap_or("").to_string(),
-        ))
-    }
-}
-
-fn rustfmt(context: &FmtContext, paths: impl Iterator<Item = OsString>) -> Result<(), Error> {
-    let mut args = Vec::new();
-    if context.check {
-        args.push(OsString::from("--check"));
-    }
-    args.extend(paths);
-    exec_fmt_command(context, &context.rustfmt_path, std::env::current_dir()?, &args)
-}
diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs
index 1cfcbdfe855..3361443196a 100644
--- a/clippy_dev/src/lib.rs
+++ b/clippy_dev/src/lib.rs
@@ -1,5 +1,6 @@
 #![feature(
     rustc_private,
+    exit_status_error,
     if_let_guard,
     let_chains,
     os_str_slice,
diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs
index 5dce0be742b..ebcd8611d78 100644
--- a/clippy_dev/src/main.rs
+++ b/clippy_dev/src/main.rs
@@ -26,7 +26,7 @@ fn main() {
             allow_staged,
             allow_no_vcs,
         } => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs),
-        DevCommand::Fmt { check, verbose } => fmt::run(check, verbose),
+        DevCommand::Fmt { check } => fmt::run(&clippy, utils::UpdateMode::from_check(check)),
         DevCommand::UpdateLints { check } => update_lints::update(utils::UpdateMode::from_check(check)),
         DevCommand::NewLint {
             pass,
@@ -125,9 +125,6 @@ enum DevCommand {
         #[arg(long)]
         /// Use the rustfmt --check option
         check: bool,
-        #[arg(short, long)]
-        /// Echo commands run
-        verbose: bool,
     },
     #[command(name = "update_lints")]
     /// Updates lint registration and information from the source code
diff --git a/clippy_dev/src/release.rs b/clippy_dev/src/release.rs
index d3b1a7ff320..62c1bee8185 100644
--- a/clippy_dev/src/release.rs
+++ b/clippy_dev/src/release.rs
@@ -1,4 +1,4 @@
-use crate::utils::{FileUpdater, Version, update_text_region_fn};
+use crate::utils::{FileUpdater, UpdateStatus, Version, parse_cargo_package};
 use std::fmt::Write;
 
 static CARGO_TOML_FILES: &[&str] = &[
@@ -13,15 +13,17 @@ pub fn bump_version(mut version: Version) {
 
     let mut updater = FileUpdater::default();
     for file in CARGO_TOML_FILES {
-        updater.update_file(
-            file,
-            &mut update_text_region_fn(
-                "# begin autogenerated version\n",
-                "# end autogenerated version",
-                |dst| {
-                    writeln!(dst, "version = \"{}\"", version.toml_display()).unwrap();
-                },
-            ),
-        );
+        updater.update_file(file, &mut |_, src, dst| {
+            let package = parse_cargo_package(src);
+            if package.version_range.is_empty() {
+                dst.push_str(src);
+                UpdateStatus::Unchanged
+            } else {
+                dst.push_str(&src[..package.version_range.start]);
+                write!(dst, "\"{}\"", version.toml_display()).unwrap();
+                dst.push_str(&src[package.version_range.end..]);
+                UpdateStatus::from_changed(src.get(package.version_range.clone()) != dst.get(package.version_range))
+            }
+        });
     }
 }
diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs
index 8e203ae5142..25ba2c72049 100644
--- a/clippy_dev/src/update_lints.rs
+++ b/clippy_dev/src/update_lints.rs
@@ -1,5 +1,5 @@
 use crate::utils::{
-    File, FileAction, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, panic_file, update_text_region_fn,
+    ErrAction, File, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, panic_action, update_text_region_fn,
 };
 use itertools::Itertools;
 use std::collections::HashSet;
@@ -203,7 +203,7 @@ fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator<Item = (DirE
     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),
+            Err(ref e) => panic_action(e, ErrAction::Read, src_root),
         };
         let path = e.path().as_os_str().as_encoded_bytes();
         if let Some(path) = path.strip_suffix(b".rs")
diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs
index fb2e25e655d..255e36afe69 100644
--- a/clippy_dev/src/utils.rs
+++ b/clippy_dev/src/utils.rs
@@ -1,12 +1,14 @@
 use core::fmt::{self, Display};
+use core::ops::Range;
 use core::slice;
 use core::str::FromStr;
 use rustc_lexer::{self as lexer, FrontmatterAllowed};
 use std::env;
+use std::ffi::OsStr;
 use std::fs::{self, OpenOptions};
 use std::io::{self, Read as _, Seek as _, SeekFrom, Write};
 use std::path::{Path, PathBuf};
-use std::process::{self, ExitStatus};
+use std::process::{self, Command, ExitStatus, Stdio};
 
 #[cfg(not(windows))]
 static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
@@ -14,15 +16,16 @@ static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
 static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
 
 #[derive(Clone, Copy)]
-pub enum FileAction {
+pub enum ErrAction {
     Open,
     Read,
     Write,
     Create,
     Rename,
     Delete,
+    Run,
 }
-impl FileAction {
+impl ErrAction {
     fn as_str(self) -> &'static str {
         match self {
             Self::Open => "opening",
@@ -31,13 +34,14 @@ impl FileAction {
             Self::Create => "creating",
             Self::Rename => "renaming",
             Self::Delete => "deleting",
+            Self::Run => "running",
         }
     }
 }
 
 #[cold]
 #[track_caller]
-pub fn panic_file(err: &impl Display, action: FileAction, path: &Path) -> ! {
+pub fn panic_action(err: &impl Display, action: ErrAction, path: &Path) -> ! {
     panic!("error {} `{}`: {}", action.as_str(), path.display(), *err)
 }
 
@@ -53,7 +57,7 @@ impl<'a> File<'a> {
         let path = path.as_ref();
         match options.open(path) {
             Ok(inner) => Self { inner, path },
-            Err(e) => panic_file(&e, FileAction::Open, path),
+            Err(e) => panic_action(&e, ErrAction::Open, path),
         }
     }
 
@@ -64,7 +68,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_file(&e, FileAction::Open, path),
+            Err(e) => panic_action(&e, ErrAction::Open, path),
         }
     }
 
@@ -82,7 +86,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_file(&e, FileAction::Read, self.path),
+            Err(e) => panic_action(&e, ErrAction::Read, self.path),
         }
         dst
     }
@@ -104,7 +108,7 @@ impl<'a> File<'a> {
             Err(e) => Err(e),
         };
         if let Err(e) = res {
-            panic_file(&e, FileAction::Write, self.path);
+            panic_action(&e, ErrAction::Write, self.path);
         }
     }
 }
@@ -166,9 +170,83 @@ impl Version {
     }
 }
 
+enum TomlPart<'a> {
+    Table(&'a str),
+    Value(&'a str, &'a str),
+}
+
+fn toml_iter(s: &str) -> impl Iterator<Item = (usize, TomlPart<'_>)> {
+    let mut pos = 0;
+    s.split('\n')
+        .map(move |s| {
+            let x = pos;
+            pos += s.len() + 1;
+            (x, s)
+        })
+        .filter_map(|(pos, s)| {
+            if let Some(s) = s.strip_prefix('[') {
+                s.split_once(']').map(|(name, _)| (pos, TomlPart::Table(name)))
+            } else if matches!(s.bytes().next(), Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')) {
+                s.split_once('=').map(|(key, value)| (pos, TomlPart::Value(key, value)))
+            } else {
+                None
+            }
+        })
+}
+
+pub struct CargoPackage<'a> {
+    pub name: &'a str,
+    pub version_range: Range<usize>,
+    pub not_a_platform_range: Range<usize>,
+}
+
+#[must_use]
+pub fn parse_cargo_package(s: &str) -> CargoPackage<'_> {
+    let mut in_package = false;
+    let mut in_platform_deps = false;
+    let mut name = "";
+    let mut version_range = 0..0;
+    let mut not_a_platform_range = 0..0;
+    for (offset, part) in toml_iter(s) {
+        match part {
+            TomlPart::Table(name) => {
+                if in_platform_deps {
+                    not_a_platform_range.end = offset;
+                }
+                in_package = false;
+                in_platform_deps = false;
+
+                match name.trim() {
+                    "package" => in_package = true,
+                    "target.'cfg(NOT_A_PLATFORM)'.dependencies" => {
+                        in_platform_deps = true;
+                        not_a_platform_range.start = offset;
+                    },
+                    _ => {},
+                }
+            },
+            TomlPart::Value(key, value) if in_package => match key.trim_end() {
+                "name" => name = value.trim(),
+                "version" => {
+                    version_range.start = offset + (value.len() - value.trim().len()) + key.len() + 1;
+                    version_range.end = offset + key.len() + value.trim_end().len() + 1;
+                },
+                _ => {},
+            },
+            TomlPart::Value(..) => {},
+        }
+    }
+    CargoPackage {
+        name,
+        version_range,
+        not_a_platform_range,
+    }
+}
+
 pub struct ClippyInfo {
     pub path: PathBuf,
     pub version: Version,
+    pub has_intellij_hook: bool,
 }
 impl ClippyInfo {
     #[must_use]
@@ -178,35 +256,21 @@ impl ClippyInfo {
         loop {
             path.push("Cargo.toml");
             if let Some(mut file) = File::open_if_exists(&path, OpenOptions::new().read(true)) {
-                let mut in_package = false;
-                let mut is_clippy = false;
-                let mut version: Option<Version> = None;
-
-                // Ad-hoc parsing to avoid dependencies. We control all the file so this
-                // isn't actually a problem
-                for line in file.read_to_cleared_string(&mut buf).lines() {
-                    if line.starts_with('[') {
-                        in_package = line.starts_with("[package]");
-                    } else if in_package && let Some((name, value)) = line.split_once('=') {
-                        match name.trim() {
-                            "name" => is_clippy = value.trim() == "\"clippy\"",
-                            "version"
-                                if let Some(value) = value.trim().strip_prefix('"')
-                                    && let Some(value) = value.strip_suffix('"') =>
-                            {
-                                version = value.parse().ok();
-                            },
-                            _ => {},
-                        }
+                file.read_to_cleared_string(&mut buf);
+                let package = parse_cargo_package(&buf);
+                if package.name == "\"clippy\"" {
+                    if let Some(version) = buf[package.version_range].strip_prefix('"')
+                        && let Some(version) = version.strip_suffix('"')
+                        && let Ok(version) = version.parse()
+                    {
+                        path.pop();
+                        return ClippyInfo {
+                            path,
+                            version,
+                            has_intellij_hook: !package.not_a_platform_range.is_empty(),
+                        };
                     }
-                }
-
-                if is_clippy {
-                    let Some(version) = version else {
-                        panic!("error reading clippy version from {}", file.path.display());
-                    };
-                    path.pop();
-                    return ClippyInfo { path, version };
+                    panic!("error reading clippy version from `{}`", file.path.display());
                 }
             }
 
@@ -259,6 +323,11 @@ impl UpdateMode {
     pub fn from_check(check: bool) -> Self {
         if check { Self::Check } else { Self::Change }
     }
+
+    #[must_use]
+    pub fn is_check(self) -> bool {
+        matches!(self, Self::Check)
+    }
 }
 
 #[derive(Default)]
@@ -547,7 +616,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(ref e) => panic_file(e, FileAction::Create, new_name),
+        Err(ref e) => panic_action(e, ErrAction::Create, new_name),
     }
     match fs::rename(old_name, new_name) {
         Ok(()) => true,
@@ -558,7 +627,7 @@ pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
             if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) {
                 false
             } else {
-                panic_file(e, FileAction::Rename, old_name);
+                panic_action(e, ErrAction::Rename, old_name);
             }
         },
     }
@@ -569,7 +638,7 @@ pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool {
     match fs::create_dir(new_name) {
         Ok(()) => {},
         Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false,
-        Err(ref e) => panic_file(e, FileAction::Create, new_name),
+        Err(ref e) => panic_action(e, ErrAction::Create, new_name),
     }
     // Windows can't reliably rename to an empty directory.
     #[cfg(windows)]
@@ -584,14 +653,55 @@ pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool {
             if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) {
                 false
             } else {
-                panic_file(e, FileAction::Rename, old_name);
+                panic_action(e, ErrAction::Rename, old_name);
             }
         },
     }
 }
 
 pub fn write_file(path: &Path, contents: &str) {
-    fs::write(path, contents).unwrap_or_else(|e| panic_file(&e, FileAction::Write, path));
+    fs::write(path, contents).unwrap_or_else(|e| panic_action(&e, ErrAction::Write, path));
+}
+
+#[must_use]
+pub fn run_with_output(path: &(impl AsRef<Path> + ?Sized), cmd: &mut Command) -> Vec<u8> {
+    fn f(path: &Path, cmd: &mut Command) -> Vec<u8> {
+        match cmd
+            .stdin(Stdio::null())
+            .stdout(Stdio::piped())
+            .stderr(Stdio::inherit())
+            .output()
+        {
+            Ok(x) => match x.status.exit_ok() {
+                Ok(()) => x.stdout,
+                Err(ref e) => panic_action(e, ErrAction::Run, path),
+            },
+            Err(ref e) => panic_action(e, ErrAction::Run, path),
+        }
+    }
+    f(path.as_ref(), cmd)
+}
+
+pub fn run_with_args_split(
+    mut make_cmd: impl FnMut() -> Command,
+    mut run_cmd: impl FnMut(&mut Command),
+    args: impl Iterator<Item: AsRef<OsStr>>,
+) {
+    let mut cmd = make_cmd();
+    let mut len = 0;
+    for arg in args {
+        len += arg.as_ref().len();
+        cmd.arg(arg);
+        // Very conservative limit
+        if len > 10000 {
+            run_cmd(&mut cmd);
+            cmd = make_cmd();
+            len = 0;
+        }
+    }
+    if len != 0 {
+        run_cmd(&mut cmd);
+    }
 }
 
 #[expect(clippy::must_use_candidate)]
@@ -599,7 +709,7 @@ pub fn delete_file_if_exists(path: &Path) -> bool {
     match fs::remove_file(path) {
         Ok(()) => true,
         Err(e) if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::IsADirectory) => false,
-        Err(ref e) => panic_file(e, FileAction::Delete, path),
+        Err(ref e) => panic_action(e, ErrAction::Delete, path),
     }
 }
 
@@ -607,6 +717,6 @@ pub fn delete_dir_if_exists(path: &Path) {
     match fs::remove_dir_all(path) {
         Ok(()) => {},
         Err(e) if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) => {},
-        Err(ref e) => panic_file(e, FileAction::Delete, path),
+        Err(ref e) => panic_action(e, ErrAction::Delete, path),
     }
 }
diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml
index 7e3cb404247..39e4e2e365e 100644
--- a/clippy_lints/Cargo.toml
+++ b/clippy_lints/Cargo.toml
@@ -1,8 +1,6 @@
 [package]
 name = "clippy_lints"
-# begin autogenerated version
 version = "0.1.89"
-# end autogenerated version
 description = "A bunch of helpful lints to avoid common pitfalls in Rust"
 repository = "https://github.com/rust-lang/rust-clippy"
 readme = "README.md"
diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml
index ac970e1c4b0..615c0995e8b 100644
--- a/clippy_utils/Cargo.toml
+++ b/clippy_utils/Cargo.toml
@@ -1,8 +1,6 @@
 [package]
 name = "clippy_utils"
-# begin autogenerated version
 version = "0.1.89"
-# end autogenerated version
 edition = "2024"
 description = "Helpful tools for writing lints, provided as they are used in Clippy"
 repository = "https://github.com/rust-lang/rust-clippy"
diff --git a/tests/ui/syntax-error-recovery/non_expressive_names_error_recovery.fixed b/tests/ui/skip_rustfmt/non_expressive_names_error_recovery.fixed
index c96a53ba2cd..c96a53ba2cd 100644
--- a/tests/ui/syntax-error-recovery/non_expressive_names_error_recovery.fixed
+++ b/tests/ui/skip_rustfmt/non_expressive_names_error_recovery.fixed
diff --git a/tests/ui/syntax-error-recovery/non_expressive_names_error_recovery.rs b/tests/ui/skip_rustfmt/non_expressive_names_error_recovery.rs
index a3a35eb26d1..a3a35eb26d1 100644
--- a/tests/ui/syntax-error-recovery/non_expressive_names_error_recovery.rs
+++ b/tests/ui/skip_rustfmt/non_expressive_names_error_recovery.rs
diff --git a/tests/ui/syntax-error-recovery/non_expressive_names_error_recovery.stderr b/tests/ui/skip_rustfmt/non_expressive_names_error_recovery.stderr
index e334ca5241e..4998b9bd2cc 100644
--- a/tests/ui/syntax-error-recovery/non_expressive_names_error_recovery.stderr
+++ b/tests/ui/skip_rustfmt/non_expressive_names_error_recovery.stderr
@@ -1,5 +1,5 @@
 error: expected one of `!`, `(`, `+`, `,`, `::`, `<`, or `>`, found `)`
-  --> tests/ui/syntax-error-recovery/non_expressive_names_error_recovery.rs:6:19
+  --> tests/ui/skip_rustfmt/non_expressive_names_error_recovery.rs:6:19
    |
 LL | fn aa(a: Aa<String) {
    |                   ^ expected one of 7 possible tokens