about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_dev/src/dogfood.rs49
-rw-r--r--clippy_dev/src/lib.rs7
-rw-r--r--clippy_dev/src/lint.rs56
-rw-r--r--clippy_dev/src/main.rs11
-rw-r--r--clippy_dev/src/serve.rs107
-rw-r--r--clippy_dev/src/setup/git_hook.rs6
-rw-r--r--clippy_dev/src/setup/mod.rs20
-rw-r--r--clippy_dev/src/setup/toolchain.rs19
-rw-r--r--clippy_dev/src/setup/vscode.rs6
-rw-r--r--clippy_dev/src/utils.rs62
-rw-r--r--clippy_lints/src/non_expressive_names.rs5
-rw-r--r--clippy_utils/src/ast_utils/mod.rs2
-rw-r--r--tests/ui/similar_names.rs4
-rw-r--r--tests/ui/similar_names.stderr4
14 files changed, 172 insertions, 186 deletions
diff --git a/clippy_dev/src/dogfood.rs b/clippy_dev/src/dogfood.rs
index 7e9d92458d0..d0fca952b93 100644
--- a/clippy_dev/src/dogfood.rs
+++ b/clippy_dev/src/dogfood.rs
@@ -1,35 +1,28 @@
-use crate::utils::exit_if_err;
-use std::process::Command;
+use crate::utils::{cargo_cmd, run_exit_on_err};
+use itertools::Itertools;
 
 /// # Panics
 ///
 /// Panics if unable to run the dogfood test
 #[allow(clippy::fn_params_excessive_bools)]
 pub fn dogfood(fix: bool, allow_dirty: bool, allow_staged: bool, allow_no_vcs: bool) {
-    let mut cmd = Command::new("cargo");
-
-    cmd.args(["test", "--test", "dogfood"])
-        .args(["--features", "internal"])
-        .args(["--", "dogfood_clippy", "--nocapture"]);
-
-    let mut dogfood_args = Vec::new();
-    if fix {
-        dogfood_args.push("--fix");
-    }
-
-    if allow_dirty {
-        dogfood_args.push("--allow-dirty");
-    }
-
-    if allow_staged {
-        dogfood_args.push("--allow-staged");
-    }
-
-    if allow_no_vcs {
-        dogfood_args.push("--allow-no-vcs");
-    }
-
-    cmd.env("__CLIPPY_DOGFOOD_ARGS", dogfood_args.join(" "));
-
-    exit_if_err(cmd.status());
+    run_exit_on_err(
+        "cargo test",
+        cargo_cmd()
+            .args(["test", "--test", "dogfood"])
+            .args(["--features", "internal"])
+            .args(["--", "dogfood_clippy", "--nocapture"])
+            .env(
+                "__CLIPPY_DOGFOOD_ARGS",
+                [
+                    if fix { "--fix" } else { "" },
+                    if allow_dirty { "--allow-dirty" } else { "" },
+                    if allow_staged { "--allow-staged" } else { "" },
+                    if allow_no_vcs { "--allow-no-vcs" } else { "" },
+                ]
+                .iter()
+                .filter(|x| !x.is_empty())
+                .join(" "),
+            ),
+    );
 }
diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs
index 40aadf4589a..16f413e0c86 100644
--- a/clippy_dev/src/lib.rs
+++ b/clippy_dev/src/lib.rs
@@ -15,8 +15,7 @@
 )]
 #![allow(clippy::missing_panics_doc)]
 
-// The `rustc_driver` crate seems to be required in order to use the `rust_lexer` crate.
-#[allow(unused_extern_crates)]
+#[expect(unused_extern_crates, reason = "required to link to rustc crates")]
 extern crate rustc_driver;
 extern crate rustc_lexer;
 extern crate rustc_literal_escaper;
@@ -32,4 +31,6 @@ pub mod serve;
 pub mod setup;
 pub mod sync;
 pub mod update_lints;
-pub mod utils;
+
+mod utils;
+pub use utils::{ClippyInfo, UpdateMode};
diff --git a/clippy_dev/src/lint.rs b/clippy_dev/src/lint.rs
index 0d66f167a38..2d9f563cdae 100644
--- a/clippy_dev/src/lint.rs
+++ b/clippy_dev/src/lint.rs
@@ -1,19 +1,18 @@
-use crate::utils::{cargo_clippy_path, exit_if_err};
-use std::process::{self, Command};
+use crate::utils::{ErrAction, cargo_cmd, expect_action, run_exit_on_err};
+use std::process::Command;
 use std::{env, fs};
 
-pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>) {
-    let is_file = match fs::metadata(path) {
-        Ok(metadata) => metadata.is_file(),
-        Err(e) => {
-            eprintln!("Failed to read {path}: {e:?}");
-            process::exit(1);
-        },
-    };
+#[cfg(not(windows))]
+static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
+#[cfg(windows)]
+static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
 
+pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>) {
+    let is_file = expect_action(fs::metadata(path), ErrAction::Read, path).is_file();
     if is_file {
-        exit_if_err(
-            Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
+        run_exit_on_err(
+            "cargo run",
+            cargo_cmd()
                 .args(["run", "--bin", "clippy-driver", "--"])
                 .args(["-L", "./target/debug"])
                 .args(["-Z", "no-codegen"])
@@ -21,24 +20,25 @@ pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>
                 .arg(path)
                 .args(args)
                 // Prevent rustc from creating `rustc-ice-*` files the console output is enough.
-                .env("RUSTC_ICE", "0")
-                .status(),
+                .env("RUSTC_ICE", "0"),
         );
     } else {
-        exit_if_err(
-            Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
-                .arg("build")
-                .status(),
-        );
-
-        let status = Command::new(cargo_clippy_path())
-            .arg("clippy")
-            .args(args)
-            // Prevent rustc from creating `rustc-ice-*` files the console output is enough.
-            .env("RUSTC_ICE", "0")
-            .current_dir(path)
-            .status();
+        // Ideally this would just be `cargo run`, but the working directory needs to be
+        // set to clippy's directory when building, and the target project's directory
+        // when running clippy. `cargo` can only set a single working directory for both
+        // when using `run`.
+        run_exit_on_err("cargo build", cargo_cmd().arg("build"));
 
-        exit_if_err(status);
+        let mut exe = env::current_exe().expect("failed to get current executable name");
+        exe.set_file_name(CARGO_CLIPPY_EXE);
+        run_exit_on_err(
+            "cargo clippy",
+            Command::new(exe)
+                .arg("clippy")
+                .args(args)
+                // Prevent rustc from creating `rustc-ice-*` files the console output is enough.
+                .env("RUSTC_ICE", "0")
+                .current_dir(path),
+        );
     }
 }
diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs
index 26aa269fb63..5fef231f6ca 100644
--- a/clippy_dev/src/main.rs
+++ b/clippy_dev/src/main.rs
@@ -4,14 +4,15 @@
 
 use clap::{Args, Parser, Subcommand};
 use clippy_dev::{
-    deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync, update_lints, utils,
+    ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync,
+    update_lints,
 };
 use std::convert::Infallible;
 use std::env;
 
 fn main() {
     let dev = Dev::parse();
-    let clippy = utils::ClippyInfo::search_for_manifest();
+    let clippy = ClippyInfo::search_for_manifest();
     if let Err(e) = env::set_current_dir(&clippy.path) {
         panic!("error setting current directory to `{}`: {e}", clippy.path.display());
     }
@@ -26,8 +27,8 @@ fn main() {
             allow_staged,
             allow_no_vcs,
         } => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs),
-        DevCommand::Fmt { check } => fmt::run(utils::UpdateMode::from_check(check)),
-        DevCommand::UpdateLints { check } => update_lints::update(utils::UpdateMode::from_check(check)),
+        DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)),
+        DevCommand::UpdateLints { check } => update_lints::update(UpdateMode::from_check(check)),
         DevCommand::NewLint {
             pass,
             name,
@@ -35,7 +36,7 @@ fn main() {
             r#type,
             msrv,
         } => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) {
-            Ok(()) => update_lints::update(utils::UpdateMode::Change),
+            Ok(()) => update_lints::update(UpdateMode::Change),
             Err(e) => eprintln!("Unable to create lint: {e}"),
         },
         DevCommand::Setup(SetupCommand { subcommand }) => match subcommand {
diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs
index 498ffeba9d6..d9e01813381 100644
--- a/clippy_dev/src/serve.rs
+++ b/clippy_dev/src/serve.rs
@@ -1,7 +1,11 @@
+use crate::utils::{ErrAction, cargo_cmd, expect_action};
+use core::fmt::Display;
+use core::mem;
 use std::path::Path;
 use std::process::Command;
 use std::time::{Duration, SystemTime};
-use std::{env, thread};
+use std::{fs, thread};
+use walkdir::WalkDir;
 
 #[cfg(windows)]
 const PYTHON: &str = "python";
@@ -18,56 +22,83 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
         Some(lint) => format!("http://localhost:{port}/#{lint}"),
     });
 
+    let mut last_update = mtime("util/gh-pages/index.html");
     loop {
-        let index_time = mtime("util/gh-pages/index.html");
-        let times = [
-            "clippy_lints/src",
-            "util/gh-pages/index_template.html",
-            "tests/compile-test.rs",
-        ]
-        .map(mtime);
-
-        if times.iter().any(|&time| index_time < time) {
-            Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
-                .arg("collect-metadata")
-                .spawn()
-                .unwrap()
-                .wait()
-                .unwrap();
+        if is_metadata_outdated(mem::replace(&mut last_update, SystemTime::now())) {
+            // Ignore the command result; we'll fall back to displaying the old metadata.
+            let _ = expect_action(
+                cargo_cmd().arg("collect-metadata").status(),
+                ErrAction::Run,
+                "cargo collect-metadata",
+            );
+            last_update = SystemTime::now();
         }
+
+        // Only start the web server the first time around.
         if let Some(url) = url.take() {
             thread::spawn(move || {
-                let mut child = Command::new(PYTHON)
-                    .arg("-m")
-                    .arg("http.server")
-                    .arg(port.to_string())
-                    .current_dir("util/gh-pages")
-                    .spawn()
-                    .unwrap();
+                let mut child = expect_action(
+                    Command::new(PYTHON)
+                        .args(["-m", "http.server", port.to_string().as_str()])
+                        .current_dir("util/gh-pages")
+                        .spawn(),
+                    ErrAction::Run,
+                    "python -m http.server",
+                );
                 // Give some time for python to start
                 thread::sleep(Duration::from_millis(500));
                 // Launch browser after first export.py has completed and http.server is up
                 let _result = opener::open(url);
-                child.wait().unwrap();
+                expect_action(child.wait(), ErrAction::Run, "python -m http.server");
             });
         }
+
+        // Delay to avoid updating the metadata too aggressively.
         thread::sleep(Duration::from_millis(1000));
     }
 }
 
-fn mtime(path: impl AsRef<Path>) -> SystemTime {
-    let path = path.as_ref();
-    if path.is_dir() {
-        path.read_dir()
-            .into_iter()
-            .flatten()
-            .flatten()
-            .map(|entry| mtime(entry.path()))
-            .max()
-            .unwrap_or(SystemTime::UNIX_EPOCH)
-    } else {
-        path.metadata()
-            .and_then(|metadata| metadata.modified())
-            .unwrap_or(SystemTime::UNIX_EPOCH)
+fn log_err_and_continue<T>(res: Result<T, impl Display>, path: &Path) -> Option<T> {
+    match res {
+        Ok(x) => Some(x),
+        Err(ref e) => {
+            eprintln!("error reading `{}`: {e}", path.display());
+            None
+        },
     }
 }
+
+fn mtime(path: &str) -> SystemTime {
+    log_err_and_continue(fs::metadata(path), path.as_ref())
+        .and_then(|metadata| log_err_and_continue(metadata.modified(), path.as_ref()))
+        .unwrap_or(SystemTime::UNIX_EPOCH)
+}
+
+fn is_metadata_outdated(time: SystemTime) -> bool {
+    // Ignore all IO errors here. We don't want to stop them from hosting the server.
+    if time < mtime("util/gh-pages/index_template.html") || time < mtime("tests/compile-test.rs") {
+        return true;
+    }
+    let Some(dir) = log_err_and_continue(fs::read_dir("."), ".".as_ref()) else {
+        return false;
+    };
+    dir.map_while(|e| log_err_and_continue(e, ".".as_ref())).any(|e| {
+        let name = e.file_name();
+        let name_bytes = name.as_encoded_bytes();
+        if (name_bytes.starts_with(b"clippy_lints") && name_bytes != b"clippy_lints_internal")
+            || name_bytes == b"clippy_config"
+        {
+            WalkDir::new(&name)
+                .into_iter()
+                .map_while(|e| log_err_and_continue(e, name.as_ref()))
+                .filter(|e| e.file_type().is_file())
+                .filter_map(|e| {
+                    log_err_and_continue(e.metadata(), e.path())
+                        .and_then(|m| log_err_and_continue(m.modified(), e.path()))
+                })
+                .any(|ftime| time < ftime)
+        } else {
+            false
+        }
+    })
+}
diff --git a/clippy_dev/src/setup/git_hook.rs b/clippy_dev/src/setup/git_hook.rs
index c7c53bc69d0..c5a1e8264c7 100644
--- a/clippy_dev/src/setup/git_hook.rs
+++ b/clippy_dev/src/setup/git_hook.rs
@@ -1,8 +1,6 @@
 use std::fs;
 use std::path::Path;
 
-use super::verify_inside_clippy_dir;
-
 /// Rusts setup uses `git rev-parse --git-common-dir` to get the root directory of the repo.
 /// I've decided against this for the sake of simplicity and to make sure that it doesn't install
 /// the hook if `clippy_dev` would be used in the rust tree. The hook also references this tool
@@ -35,10 +33,6 @@ pub fn install_hook(force_override: bool) {
 }
 
 fn check_precondition(force_override: bool) -> bool {
-    if !verify_inside_clippy_dir() {
-        return false;
-    }
-
     // Make sure that we can find the git repository
     let git_path = Path::new(REPO_GIT_DIR);
     if !git_path.exists() || !git_path.is_dir() {
diff --git a/clippy_dev/src/setup/mod.rs b/clippy_dev/src/setup/mod.rs
index b0d31814639..5e938fff126 100644
--- a/clippy_dev/src/setup/mod.rs
+++ b/clippy_dev/src/setup/mod.rs
@@ -2,23 +2,3 @@ pub mod git_hook;
 pub mod intellij;
 pub mod toolchain;
 pub mod vscode;
-
-use std::path::Path;
-
-const CLIPPY_DEV_DIR: &str = "clippy_dev";
-
-/// This function verifies that the tool is being executed in the clippy directory.
-/// This is useful to ensure that setups only modify Clippy's resources. The verification
-/// is done by checking that `clippy_dev` is a sub directory of the current directory.
-///
-/// It will print an error message and return `false` if the directory could not be
-/// verified.
-fn verify_inside_clippy_dir() -> bool {
-    let path = Path::new(CLIPPY_DEV_DIR);
-    if path.exists() && path.is_dir() {
-        true
-    } else {
-        eprintln!("error: unable to verify that the working directory is clippy's directory");
-        false
-    }
-}
diff --git a/clippy_dev/src/setup/toolchain.rs b/clippy_dev/src/setup/toolchain.rs
index ecd80215f7e..c64ae4ef3c3 100644
--- a/clippy_dev/src/setup/toolchain.rs
+++ b/clippy_dev/src/setup/toolchain.rs
@@ -1,20 +1,12 @@
+use crate::utils::{cargo_cmd, run_exit_on_err};
 use std::env::consts::EXE_SUFFIX;
 use std::env::current_dir;
 use std::ffi::OsStr;
 use std::fs;
 use std::path::{Path, PathBuf};
-use std::process::Command;
 use walkdir::WalkDir;
 
-use crate::utils::exit_if_err;
-
-use super::verify_inside_clippy_dir;
-
 pub fn create(standalone: bool, force: bool, release: bool, name: &str) {
-    if !verify_inside_clippy_dir() {
-        return;
-    }
-
     let rustup_home = std::env::var("RUSTUP_HOME").unwrap();
     let toolchain = std::env::var("RUSTUP_TOOLCHAIN").unwrap();
 
@@ -51,11 +43,10 @@ pub fn create(standalone: bool, force: bool, release: bool, name: &str) {
         }
     }
 
-    let status = Command::new("cargo")
-        .arg("build")
-        .args(release.then_some("--release"))
-        .status();
-    exit_if_err(status);
+    run_exit_on_err(
+        "cargo build",
+        cargo_cmd().arg("build").args(release.then_some("--release")),
+    );
 
     install_bin("cargo-clippy", &dest, standalone, release);
     install_bin("clippy-driver", &dest, standalone, release);
diff --git a/clippy_dev/src/setup/vscode.rs b/clippy_dev/src/setup/vscode.rs
index a37c873eed4..a24aef65991 100644
--- a/clippy_dev/src/setup/vscode.rs
+++ b/clippy_dev/src/setup/vscode.rs
@@ -1,8 +1,6 @@
 use std::fs;
 use std::path::Path;
 
-use super::verify_inside_clippy_dir;
-
 const VSCODE_DIR: &str = ".vscode";
 const TASK_SOURCE_FILE: &str = "util/etc/vscode-tasks.json";
 const TASK_TARGET_FILE: &str = ".vscode/tasks.json";
@@ -22,10 +20,6 @@ pub fn install_tasks(force_override: bool) {
 }
 
 fn check_install_precondition(force_override: bool) -> bool {
-    if !verify_inside_clippy_dir() {
-        return false;
-    }
-
     let vs_dir_path = Path::new(VSCODE_DIR);
     if vs_dir_path.exists() {
         // verify the target will be valid
diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs
index 89962a11034..057951d0e33 100644
--- a/clippy_dev/src/utils.rs
+++ b/clippy_dev/src/utils.rs
@@ -8,15 +8,10 @@ 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, Command, ExitStatus, Stdio};
+use std::process::{self, Command, Stdio};
 use std::{env, thread};
 use walkdir::WalkDir;
 
-#[cfg(not(windows))]
-static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
-#[cfg(windows)]
-static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
-
 #[derive(Clone, Copy)]
 pub enum ErrAction {
     Open,
@@ -118,16 +113,14 @@ impl<'a> File<'a> {
     }
 }
 
-/// Returns the path to the `cargo-clippy` binary
-///
-/// # Panics
-///
-/// Panics if the path of current executable could not be retrieved.
+/// Creates a `Command` for running cargo.
 #[must_use]
-pub fn cargo_clippy_path() -> PathBuf {
-    let mut path = env::current_exe().expect("failed to get current executable name");
-    path.set_file_name(CARGO_CLIPPY_EXE);
-    path
+pub fn cargo_cmd() -> Command {
+    if let Some(path) = env::var_os("CARGO") {
+        Command::new(path)
+    } else {
+        Command::new("cargo")
+    }
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -288,19 +281,6 @@ impl ClippyInfo {
     }
 }
 
-/// # Panics
-/// Panics if given command result was failed.
-pub fn exit_if_err(status: io::Result<ExitStatus>) {
-    match status.expect("failed to run command").code() {
-        Some(0) => {},
-        Some(n) => process::exit(n),
-        None => {
-            eprintln!("Killed by signal");
-            process::exit(1);
-        },
-    }
-}
-
 #[derive(Clone, Copy)]
 pub enum UpdateStatus {
     Unchanged,
@@ -341,6 +321,7 @@ pub struct FileUpdater {
     dst_buf: String,
 }
 impl FileUpdater {
+    #[track_caller]
     fn update_file_checked_inner(
         &mut self,
         tool: &str,
@@ -364,6 +345,7 @@ impl FileUpdater {
         }
     }
 
+    #[track_caller]
     fn update_file_inner(&mut self, path: &Path, update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus) {
         let mut file = File::open(path, OpenOptions::new().read(true).write(true));
         file.read_to_cleared_string(&mut self.src_buf);
@@ -373,6 +355,7 @@ impl FileUpdater {
         }
     }
 
+    #[track_caller]
     pub fn update_file_checked(
         &mut self,
         tool: &str,
@@ -383,6 +366,7 @@ impl FileUpdater {
         self.update_file_checked_inner(tool, mode, path.as_ref(), update);
     }
 
+    #[track_caller]
     pub fn update_file(
         &mut self,
         path: impl AsRef<Path>,
@@ -450,7 +434,6 @@ pub enum Token<'a> {
     OpenParen,
     Pound,
     Semi,
-    Slash,
 }
 
 pub struct RustSearcher<'txt> {
@@ -528,7 +511,6 @@ impl<'txt> RustSearcher<'txt> {
                 | (Token::OpenParen, lexer::TokenKind::OpenParen)
                 | (Token::Pound, lexer::TokenKind::Pound)
                 | (Token::Semi, lexer::TokenKind::Semi)
-                | (Token::Slash, lexer::TokenKind::Slash)
                 | (
                     Token::LitStr,
                     lexer::TokenKind::Literal {
@@ -601,7 +583,7 @@ impl<'txt> RustSearcher<'txt> {
     }
 }
 
-#[expect(clippy::must_use_candidate)]
+#[track_caller]
 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),
@@ -623,7 +605,7 @@ pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
     }
 }
 
-#[expect(clippy::must_use_candidate)]
+#[track_caller]
 pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool {
     match fs::create_dir(new_name) {
         Ok(()) => {},
@@ -649,10 +631,19 @@ pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool {
     }
 }
 
-pub fn write_file(path: &Path, contents: &str) {
-    expect_action(fs::write(path, contents), ErrAction::Write, path);
+#[track_caller]
+pub fn run_exit_on_err(path: &(impl AsRef<Path> + ?Sized), cmd: &mut Command) {
+    match expect_action(cmd.status(), ErrAction::Run, path.as_ref()).code() {
+        Some(0) => {},
+        Some(n) => process::exit(n),
+        None => {
+            eprintln!("{} killed by signal", path.as_ref().display());
+            process::exit(1);
+        },
+    }
 }
 
+#[track_caller]
 #[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> {
@@ -738,7 +729,7 @@ pub fn split_args_for_threads(
     }
 }
 
-#[expect(clippy::must_use_candidate)]
+#[track_caller]
 pub fn delete_file_if_exists(path: &Path) -> bool {
     match fs::remove_file(path) {
         Ok(()) => true,
@@ -747,6 +738,7 @@ pub fn delete_file_if_exists(path: &Path) -> bool {
     }
 }
 
+#[track_caller]
 pub fn delete_dir_if_exists(path: &Path) {
     match fs::remove_dir_all(path) {
         Ok(()) => {},
diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs
index c5873589b26..1961ac1516d 100644
--- a/clippy_lints/src/non_expressive_names.rs
+++ b/clippy_lints/src/non_expressive_names.rs
@@ -248,6 +248,11 @@ impl SimilarNamesNameVisitor<'_, '_, '_> {
                 continue;
             }
 
+            // Skip similarity check if both names are exactly 3 characters
+            if count == 3 && existing_name.len == 3 {
+                continue;
+            }
+
             let dissimilar = match existing_name.len.cmp(&count) {
                 Ordering::Greater => existing_name.len - count != 1 || levenstein_not_1(interned_name, existing_str),
                 Ordering::Less => count - existing_name.len != 1 || levenstein_not_1(existing_str, interned_name),
diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs
index 96f0273c439..7803f8c0d34 100644
--- a/clippy_utils/src/ast_utils/mod.rs
+++ b/clippy_utils/src/ast_utils/mod.rs
@@ -325,7 +325,7 @@ pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> b
     over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind)
 }
 
-#[expect(clippy::similar_names, clippy::too_many_lines)] // Just a big match statement
+#[expect(clippy::too_many_lines)] // Just a big match statement
 pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
     use ItemKind::*;
     match (l, r) {
diff --git a/tests/ui/similar_names.rs b/tests/ui/similar_names.rs
index 69b6ab6220b..55a141209f0 100644
--- a/tests/ui/similar_names.rs
+++ b/tests/ui/similar_names.rs
@@ -89,6 +89,10 @@ fn main() {
 
     let iter: i32;
     let item: i32;
+
+    // 3 letter names are allowed to be similar
+    let kta: i32;
+    let ktv: i32;
 }
 
 fn foo() {
diff --git a/tests/ui/similar_names.stderr b/tests/ui/similar_names.stderr
index 8d722fb8b56..c226f73d4db 100644
--- a/tests/ui/similar_names.stderr
+++ b/tests/ui/similar_names.stderr
@@ -49,13 +49,13 @@ LL |     let parser: i32;
    |         ^^^^^^
 
 error: binding's name is too similar to existing binding
-  --> tests/ui/similar_names.rs:98:16
+  --> tests/ui/similar_names.rs:102:16
    |
 LL |         bpple: sprang,
    |                ^^^^^^
    |
 note: existing binding defined here
-  --> tests/ui/similar_names.rs:97:16
+  --> tests/ui/similar_names.rs:101:16
    |
 LL |         apple: spring,
    |                ^^^^^^