Skip to content

clippy_dev: changes to parsing code #15270

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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