Skip to content

Split early lints into a new crate #15294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 23 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e96b926
`clippy_dev`: Move parsing code to it's own module.
Jarcho May 31, 2025
2ffe004
`clippy_dev` parsing changes:
Jarcho Jun 6, 2025
107ab93
`clippy_dev`: Keep a source map when parsing files.
Jarcho Jul 9, 2025
7bc7fe5
`clippy_dev`: print parse errors with location info.
Jarcho Jul 11, 2025
ab1f092
`clippy_dev`: parse lint pass macro calls
Jarcho Jul 12, 2025
ca06aa2
`clippy_dev`: check that all lints are registered with a lint pass
Jarcho Jul 13, 2025
eb65729
prereqs
Jarcho Jul 15, 2025
5da633a
Move `methods` lint pass to a new crate part 1/2
Jarcho Jul 15, 2025
3dba405
Move `methods` lint pass to a new crate part 2/2
Jarcho Jul 15, 2025
87b553a
Move `methods` lint declarations into sub-modules
Jarcho Jul 15, 2025
fb03a4e
Move `loops` lint pass to a new crate part 1/2
Jarcho Jul 15, 2025
ebf3c4a
Move `loops` lint pass to a new crate part 2/2
Jarcho Jul 15, 2025
909d138
Move `loops` lint declarations into sub-modules
Jarcho Jul 15, 2025
37a4811
Move `matches` lint pass to a new crate part 1/2
Jarcho Jul 15, 2025
4abbb95
Move `matches` lint pass to a new crate part 2/2
Jarcho Jul 15, 2025
ebc3934
Move `matches` lint declarations into sub-modules
Jarcho Jul 15, 2025
b05963c
Move `casts` lint pass to a new crate part 1/2
Jarcho Jul 15, 2025
20ea49c
Move `casts` lint pass to a new crate part 2/2
Jarcho Jul 15, 2025
eef9bf6
Move `casts` lint declarations to sub-modules
Jarcho Jul 15, 2025
9c68561
Move early lint passes to a new crate part 1/2
Jarcho Jul 16, 2025
c2fbb97
Move early lint passes to a new crate part 2/2
Jarcho Jul 16, 2025
13edc59
Move `misc_early` pass to the early lints crate part 1/2
Jarcho Jul 16, 2025
e8e3abc
Move `misc_early` pass to the early lints crate part 2/2
Jarcho Jul 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6304,6 +6304,7 @@ Released 2018-09-13
[`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names
[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
[`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards
[`redundant_iter_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_iter_cloned
[`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals
[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ path = "src/driver.rs"
[dependencies]
clippy_config = { path = "clippy_config" }
clippy_lints = { path = "clippy_lints" }
clippy_lints_casts = { path = "clippy_lints_casts" }
clippy_lints_early = { path = "clippy_lints_early" }
clippy_lints_loops = { path = "clippy_lints_loops" }
clippy_lints_matches = { path = "clippy_lints_matches" }
clippy_lints_methods = { path = "clippy_lints_methods" }
clippy_utils = { path = "clippy_utils" }
declare_clippy_lint = { path = "declare_clippy_lint" }
rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" }
Expand Down
1 change: 1 addition & 0 deletions clippy_dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
clap = { version = "4.4", features = ["derive"] }
indoc = "1.0"
itertools = "0.12"
memchr = "2.7.5"
opener = "0.7"
walkdir = "2.3"

Expand Down
179 changes: 79 additions & 100 deletions clippy_dev/src/deprecate_lint.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::update_lints::{DeprecatedLint, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints};
use crate::utils::{UpdateMode, Version};
use crate::parse::{ActiveLint, DeprecatedLint, LintKind, ParsedData};
use crate::update_lints::generate_lint_files;
use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists};
use core::mem;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::{fs, io};
use std::path::Path;

/// Runs the `deprecate` command
///
Expand All @@ -18,68 +19,42 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
panic!("`{name}` should not contain the `{prefix}` prefix");
}

let mut lints = find_lint_decls();
let (mut deprecated_lints, renamed_lints) = read_deprecated_lints();

let Some(lint) = lints.iter().find(|l| l.name == name) else {
let mut data = ParsedData::collect();
let Some(entry) = data.lints.get_mut(name) else {
eprintln!("error: failed to find lint `{name}`");
return;
};

let prefixed_name = String::from_iter(["clippy::", name]);
match deprecated_lints.binary_search_by(|x| x.name.cmp(&prefixed_name)) {
Ok(_) => {
println!("`{name}` is already deprecated");
return;
},
Err(idx) => deprecated_lints.insert(
idx,
DeprecatedLint {
name: prefixed_name,
reason: reason.into(),
version: clippy_version.rust_display().to_string(),
},
),
}

let mod_path = {
let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module));
if mod_path.is_dir() {
mod_path = mod_path.join("mod");
}

mod_path.set_extension("rs");
mod_path
let LintKind::Active(lint) = mem::replace(
&mut entry.kind,
LintKind::Deprecated(DeprecatedLint {
reason: reason.into(),
version: clippy_version.rust_display().to_string(),
}),
) else {
eprintln!("error: lint `{name}` is already deprecated");
return;
};

if remove_lint_declaration(name, &mod_path, &mut lints).unwrap_or(false) {
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
println!("info: `{name}` has successfully been deprecated");
println!("note: you must run `cargo uitest` to update the test results");
} else {
eprintln!("error: lint not found");
}
remove_lint_declaration(name, &lint, &data);
generate_lint_files(UpdateMode::Change, &data);
println!("info: `{name}` has successfully been deprecated");
println!("note: you must run `cargo uitest` to update the test results");
}

fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> {
fn remove_lint(name: &str, lints: &mut Vec<Lint>) {
lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));
}

fn remove_lint_declaration(name: &str, lint: &ActiveLint, data: &ParsedData) {
fn remove_test_assets(name: &str) {
let test_file_stem = format!("tests/ui/{name}");
let path = Path::new(&test_file_stem);

// Some lints have their own directories, delete them
if path.is_dir() {
let _ = fs::remove_dir_all(path);
return;
delete_dir_if_exists(path);
} else {
// Remove all related test files
delete_file_if_exists(&path.with_extension("rs"));
delete_file_if_exists(&path.with_extension("stderr"));
delete_file_if_exists(&path.with_extension("fixed"));
}

// Remove all related test files
let _ = fs::remove_file(path.with_extension("rs"));
let _ = fs::remove_file(path.with_extension("stderr"));
let _ = fs::remove_file(path.with_extension("fixed"));
}

fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) {
Expand Down Expand Up @@ -108,54 +83,58 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io
}
}

if path.exists()
&& let Some(lint) = lints.iter().find(|l| l.name == name)
{
if lint.module == name {
// The lint name is the same as the file, we can just delete the entire file
fs::remove_file(path)?;
let lint_file = &data.source_map.files[lint.decl_span.file];
if data.lints.values().any(|l| {
let other_file = &data.source_map.files[l.name_span.file];
other_file.krate == lint_file.krate && other_file.module.starts_with(&lint_file.module)
}) {
// Try to delete a sub-module that matches the lint's name
let removed_mod = if lint_file.path.file_name().map(OsStr::as_encoded_bytes) == Some(b"mod.rs") {
let mut path = lint_file.path.clone();
path.set_file_name(name);
path.set_extension("rs");
delete_file_if_exists(&path)
} else {
// We can't delete the entire file, just remove the declaration

if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) {
// Remove clippy_lints/src/some_mod/some_lint.rs
let mut lint_mod_path = path.to_path_buf();
lint_mod_path.set_file_name(name);
lint_mod_path.set_extension("rs");

let _ = fs::remove_file(lint_mod_path);
}

let mut content =
fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy()));

eprintln!(
"warn: you will have to manually remove any code related to `{name}` from `{}`",
path.display()
);

assert!(
content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
"error: `{}` does not contain lint `{}`'s declaration",
path.display(),
lint.name
);

// Remove lint declaration (declare_clippy_lint!)
content.replace_range(lint.declaration_range.clone(), "");

// Remove the module declaration (mod xyz;)
let mod_decl = format!("\nmod {name};");
content = content.replacen(&mod_decl, "", 1);

remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content);
fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy()));
}

remove_test_assets(name);
remove_lint(name, lints);
return Ok(true);
false
};

FileUpdater::default().update_file(&lint_file.path, &mut |_, src, dst| {
let (a, b, c, d) = if removed_mod
&& let mod_decl = format!("\nmod {name};")
&& let Some(mod_start) = src.find(&mod_decl)
{
if mod_start < lint.decl_span.start as usize {
(
mod_start,
mod_start + mod_decl.len(),
lint.decl_span.start as usize,
lint.decl_span.end as usize,
)
} else {
(
lint.decl_span.start as usize,
lint.decl_span.end as usize,
mod_start,
mod_start + mod_decl.len(),
)
}
} else {
(
lint.decl_span.start as usize,
lint.decl_span.end as usize,
src.len(),
src.len(),
)
};
dst.push_str(&src[..a]);
dst.push_str(&src[b..c]);
dst.push_str(&src[d..]);
remove_impl_lint_pass(&name.to_uppercase(), dst);
UpdateStatus::Changed
});
} else {
// No other lint in the same module or a sub-module.
delete_file_if_exists(&lint_file.path);
}

Ok(false)
remove_test_assets(name);
}
2 changes: 1 addition & 1 deletion clippy_dev/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ fn run_rustfmt(update_mode: UpdateMode) {
.expect("invalid rustfmt path");
rustfmt_path.truncate(rustfmt_path.trim_end().len());

let args: Vec<_> = walk_dir_no_dot_or_target()
let args: Vec<_> = walk_dir_no_dot_or_target(".")
.filter_map(|e| {
let e = expect_action(e, ErrAction::Read, ".");
e.path()
Expand Down
10 changes: 8 additions & 2 deletions clippy_dev/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![feature(
rustc_private,
array_windows,
exit_status_error,
if_let_guard,
let_chains,
Expand All @@ -16,9 +17,11 @@
)]
#![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 = "needed to lint to rustc crates")]
extern crate rustc_driver;

extern crate rustc_data_structures;
extern crate rustc_index;
extern crate rustc_lexer;
extern crate rustc_literal_escaper;

Expand All @@ -34,3 +37,6 @@ pub mod setup;
pub mod sync;
pub mod update_lints;
pub mod utils;

mod parse;
mod source_map;
11 changes: 6 additions & 5 deletions clippy_dev/src/new_lint.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::utils::{RustSearcher, Token, Version};
use crate::parse::{Capture, RustSearcher, Token};
use crate::utils::Version;
use clap::ValueEnum;
use indoc::{formatdoc, writedoc};
use std::fmt::{self, Write as _};
Expand Down Expand Up @@ -522,17 +523,17 @@ fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) {
let mut context = None;
let mut decl_end = None;
let mut searcher = RustSearcher::new(contents);
while let Some(name) = searcher.find_capture_token(CaptureIdent) {
let mut captures = [Capture::EMPTY];
while let Some(name) = searcher.find_any_ident() {
match name {
"declare_clippy_lint" => {
if searcher.match_tokens(&[Bang, OpenBrace], &mut []) && searcher.find_token(CloseBrace) {
decl_end = Some(searcher.pos());
}
},
"impl" => {
let mut capture = "";
if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut [&mut capture]) {
match capture {
if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) {
match searcher.get_capture(captures[0]) {
"LateLintPass" => context = Some("LateContext"),
"EarlyLintPass" => context = Some("EarlyContext"),
_ => {},
Expand Down
Loading