Skip to content

Commit 3fe5fb2

Browse files
committed
clippy_dev: Split rename and deprecate out of update_lints
1 parent 2f39264 commit 3fe5fb2

File tree

6 files changed

+460
-442
lines changed

6 files changed

+460
-442
lines changed

clippy_dev/src/deprecate_lint.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
use crate::update_lints::{DeprecatedLint, Lint, gather_all, generate_lint_files};
2+
use crate::utils::{UpdateMode, Version, insert_at_marker, rewrite_file};
3+
use std::ffi::OsStr;
4+
use std::path::{Path, PathBuf};
5+
use std::{fs, io};
6+
7+
/// Runs the `deprecate` command
8+
///
9+
/// This does the following:
10+
/// * Adds an entry to `deprecated_lints.rs`.
11+
/// * Removes the lint declaration (and the entire file if applicable)
12+
///
13+
/// # Panics
14+
///
15+
/// If a file path could not read from or written to
16+
pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
17+
let prefixed_name = if name.starts_with("clippy::") {
18+
name.to_owned()
19+
} else {
20+
format!("clippy::{name}")
21+
};
22+
let stripped_name = &prefixed_name[8..];
23+
24+
let (mut lints, mut deprecated_lints, renamed_lints) = gather_all();
25+
let Some(lint) = lints.iter().find(|l| l.name == stripped_name) else {
26+
eprintln!("error: failed to find lint `{name}`");
27+
return;
28+
};
29+
30+
let mod_path = {
31+
let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module));
32+
if mod_path.is_dir() {
33+
mod_path = mod_path.join("mod");
34+
}
35+
36+
mod_path.set_extension("rs");
37+
mod_path
38+
};
39+
40+
if remove_lint_declaration(stripped_name, &mod_path, &mut lints).unwrap_or(false) {
41+
rewrite_file("clippy_lints/src/deprecated_lints.rs".as_ref(), |s| {
42+
insert_at_marker(
43+
s,
44+
"// end deprecated lints. used by `cargo dev deprecate_lint`",
45+
&format!(
46+
"#[clippy::version = \"{}\"]\n (\"{prefixed_name}\", \"{reason}\"),\n ",
47+
clippy_version.rust_display(),
48+
),
49+
)
50+
});
51+
52+
deprecated_lints.push(DeprecatedLint {
53+
name: prefixed_name,
54+
reason: reason.into(),
55+
});
56+
57+
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
58+
println!("info: `{name}` has successfully been deprecated");
59+
println!("note: you must run `cargo uitest` to update the test results");
60+
} else {
61+
eprintln!("error: lint not found");
62+
}
63+
}
64+
65+
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> {
66+
fn remove_lint(name: &str, lints: &mut Vec<Lint>) {
67+
lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));
68+
}
69+
70+
fn remove_test_assets(name: &str) {
71+
let test_file_stem = format!("tests/ui/{name}");
72+
let path = Path::new(&test_file_stem);
73+
74+
// Some lints have their own directories, delete them
75+
if path.is_dir() {
76+
let _ = fs::remove_dir_all(path);
77+
return;
78+
}
79+
80+
// Remove all related test files
81+
let _ = fs::remove_file(path.with_extension("rs"));
82+
let _ = fs::remove_file(path.with_extension("stderr"));
83+
let _ = fs::remove_file(path.with_extension("fixed"));
84+
}
85+
86+
fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) {
87+
let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| {
88+
content
89+
.find("declare_lint_pass!")
90+
.unwrap_or_else(|| panic!("failed to find `impl_lint_pass`"))
91+
});
92+
let mut impl_lint_pass_end = content[impl_lint_pass_start..]
93+
.find(']')
94+
.expect("failed to find `impl_lint_pass` terminator");
95+
96+
impl_lint_pass_end += impl_lint_pass_start;
97+
if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(lint_name_upper) {
98+
let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len());
99+
for c in content[lint_name_end..impl_lint_pass_end].chars() {
100+
// Remove trailing whitespace
101+
if c == ',' || c.is_whitespace() {
102+
lint_name_end += 1;
103+
} else {
104+
break;
105+
}
106+
}
107+
108+
content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, "");
109+
}
110+
}
111+
112+
if path.exists()
113+
&& let Some(lint) = lints.iter().find(|l| l.name == name)
114+
{
115+
if lint.module == name {
116+
// The lint name is the same as the file, we can just delete the entire file
117+
fs::remove_file(path)?;
118+
} else {
119+
// We can't delete the entire file, just remove the declaration
120+
121+
if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) {
122+
// Remove clippy_lints/src/some_mod/some_lint.rs
123+
let mut lint_mod_path = path.to_path_buf();
124+
lint_mod_path.set_file_name(name);
125+
lint_mod_path.set_extension("rs");
126+
127+
let _ = fs::remove_file(lint_mod_path);
128+
}
129+
130+
let mut content =
131+
fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy()));
132+
133+
eprintln!(
134+
"warn: you will have to manually remove any code related to `{name}` from `{}`",
135+
path.display()
136+
);
137+
138+
assert!(
139+
content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
140+
"error: `{}` does not contain lint `{}`'s declaration",
141+
path.display(),
142+
lint.name
143+
);
144+
145+
// Remove lint declaration (declare_clippy_lint!)
146+
content.replace_range(lint.declaration_range.clone(), "");
147+
148+
// Remove the module declaration (mod xyz;)
149+
let mod_decl = format!("\nmod {name};");
150+
content = content.replacen(&mod_decl, "", 1);
151+
152+
remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content);
153+
fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy()));
154+
}
155+
156+
remove_test_assets(name);
157+
remove_lint(name, lints);
158+
return Ok(true);
159+
}
160+
161+
Ok(false)
162+
}

clippy_dev/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ extern crate rustc_driver;
1414
extern crate rustc_lexer;
1515
extern crate rustc_literal_escaper;
1616

17+
pub mod deprecate_lint;
1718
pub mod dogfood;
1819
pub mod fmt;
1920
pub mod lint;
2021
pub mod new_lint;
2122
pub mod release;
23+
pub mod rename_lint;
2224
pub mod serve;
2325
pub mod setup;
2426
pub mod sync;

clippy_dev/src/main.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
#![warn(rust_2018_idioms, unused_lifetimes)]
44

55
use clap::{Args, Parser, Subcommand};
6-
use clippy_dev::{dogfood, fmt, lint, new_lint, release, serve, setup, sync, update_lints, utils};
6+
use clippy_dev::{
7+
deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync, update_lints, utils,
8+
};
79
use std::convert::Infallible;
810
use std::env;
911

@@ -84,13 +86,13 @@ fn main() {
8486
old_name,
8587
new_name,
8688
uplift,
87-
} => update_lints::rename(
89+
} => rename_lint::rename(
8890
clippy.version,
8991
&old_name,
9092
new_name.as_ref().unwrap_or(&old_name),
9193
uplift,
9294
),
93-
DevCommand::Deprecate { name, reason } => update_lints::deprecate(clippy.version, &name, &reason),
95+
DevCommand::Deprecate { name, reason } => deprecate_lint::deprecate(clippy.version, &name, &reason),
9496
DevCommand::Sync(SyncCommand { subcommand }) => match subcommand {
9597
SyncSubcommand::UpdateNightly => sync::update_nightly(),
9698
},

clippy_dev/src/rename_lint.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use crate::update_lints::{
2+
RenamedLint, clippy_lints_src_files, gather_all, gen_renamed_lints_test, generate_lint_files,
3+
};
4+
use crate::utils::{
5+
UpdateMode, Version, insert_at_marker, replace_ident_like, rewrite_file, try_rename_file, write_file,
6+
};
7+
use std::ffi::OsStr;
8+
use std::path::Path;
9+
use walkdir::WalkDir;
10+
11+
/// Runs the `rename_lint` command.
12+
///
13+
/// This does the following:
14+
/// * Adds an entry to `renamed_lints.rs`.
15+
/// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`).
16+
/// * Renames the lint struct to the new name.
17+
/// * Renames the module containing the lint struct to the new name if it shares a name with the
18+
/// lint.
19+
///
20+
/// # Panics
21+
/// Panics for the following conditions:
22+
/// * If a file path could not read from or then written to
23+
/// * If either lint name has a prefix
24+
/// * If `old_name` doesn't name an existing lint.
25+
/// * If `old_name` names a deprecated or renamed lint.
26+
#[allow(clippy::too_many_lines)]
27+
pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: bool) {
28+
if let Some((prefix, _)) = old_name.split_once("::") {
29+
panic!("`{old_name}` should not contain the `{prefix}` prefix");
30+
}
31+
if let Some((prefix, _)) = new_name.split_once("::") {
32+
panic!("`{new_name}` should not contain the `{prefix}` prefix");
33+
}
34+
35+
let (mut lints, deprecated_lints, mut renamed_lints) = gather_all();
36+
let mut old_lint_index = None;
37+
let mut found_new_name = false;
38+
for (i, lint) in lints.iter().enumerate() {
39+
if lint.name == old_name {
40+
old_lint_index = Some(i);
41+
} else if lint.name == new_name {
42+
found_new_name = true;
43+
}
44+
}
45+
let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{old_name}`"));
46+
47+
let lint = RenamedLint {
48+
old_name: format!("clippy::{old_name}"),
49+
new_name: if uplift {
50+
new_name.into()
51+
} else {
52+
format!("clippy::{new_name}")
53+
},
54+
};
55+
56+
// Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in
57+
// case.
58+
assert!(
59+
!renamed_lints.iter().any(|l| lint.old_name == l.old_name),
60+
"`{old_name}` has already been renamed"
61+
);
62+
assert!(
63+
!deprecated_lints.iter().any(|l| lint.old_name == l.name),
64+
"`{old_name}` has already been deprecated"
65+
);
66+
67+
// Update all lint level attributes. (`clippy::lint_name`)
68+
for file in WalkDir::new(".").into_iter().map(Result::unwrap).filter(|f| {
69+
let name = f.path().file_name();
70+
let ext = f.path().extension();
71+
(ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed")))
72+
&& name != Some(OsStr::new("rename.rs"))
73+
&& name != Some(OsStr::new("deprecated_lints.rs"))
74+
}) {
75+
rewrite_file(file.path(), |s| {
76+
replace_ident_like(s, &[(&lint.old_name, &lint.new_name)])
77+
});
78+
}
79+
80+
rewrite_file(Path::new("clippy_lints/src/deprecated_lints.rs"), |s| {
81+
insert_at_marker(
82+
s,
83+
"// end renamed lints. used by `cargo dev rename_lint`",
84+
&format!(
85+
"#[clippy::version = \"{}\"]\n \
86+
(\"{}\", \"{}\"),\n ",
87+
clippy_version.rust_display(),
88+
lint.old_name,
89+
lint.new_name,
90+
),
91+
)
92+
});
93+
94+
renamed_lints.push(lint);
95+
renamed_lints.sort_by(|lhs, rhs| {
96+
lhs.new_name
97+
.starts_with("clippy::")
98+
.cmp(&rhs.new_name.starts_with("clippy::"))
99+
.reverse()
100+
.then_with(|| lhs.old_name.cmp(&rhs.old_name))
101+
});
102+
103+
if uplift {
104+
write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
105+
println!(
106+
"`{old_name}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually."
107+
);
108+
} else if found_new_name {
109+
write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
110+
println!(
111+
"`{new_name}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually."
112+
);
113+
} else {
114+
// Rename the lint struct and source files sharing a name with the lint.
115+
let lint = &mut lints[old_lint_index];
116+
let old_name_upper = old_name.to_uppercase();
117+
let new_name_upper = new_name.to_uppercase();
118+
lint.name = new_name.into();
119+
120+
// Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist.
121+
if try_rename_file(
122+
Path::new(&format!("tests/ui/{old_name}.rs")),
123+
Path::new(&format!("tests/ui/{new_name}.rs")),
124+
) {
125+
try_rename_file(
126+
Path::new(&format!("tests/ui/{old_name}.stderr")),
127+
Path::new(&format!("tests/ui/{new_name}.stderr")),
128+
);
129+
try_rename_file(
130+
Path::new(&format!("tests/ui/{old_name}.fixed")),
131+
Path::new(&format!("tests/ui/{new_name}.fixed")),
132+
);
133+
}
134+
135+
// Try to rename the file containing the lint if the file name matches the lint's name.
136+
let replacements;
137+
let replacements = if lint.module == old_name
138+
&& try_rename_file(
139+
Path::new(&format!("clippy_lints/src/{old_name}.rs")),
140+
Path::new(&format!("clippy_lints/src/{new_name}.rs")),
141+
) {
142+
// Edit the module name in the lint list. Note there could be multiple lints.
143+
for lint in lints.iter_mut().filter(|l| l.module == old_name) {
144+
lint.module = new_name.into();
145+
}
146+
replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
147+
replacements.as_slice()
148+
} else if !lint.module.contains("::")
149+
// Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs`
150+
&& try_rename_file(
151+
Path::new(&format!("clippy_lints/src/{}/{old_name}.rs", lint.module)),
152+
Path::new(&format!("clippy_lints/src/{}/{new_name}.rs", lint.module)),
153+
)
154+
{
155+
// Edit the module name in the lint list. Note there could be multiple lints, or none.
156+
let renamed_mod = format!("{}::{old_name}", lint.module);
157+
for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) {
158+
lint.module = format!("{}::{new_name}", lint.module);
159+
}
160+
replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
161+
replacements.as_slice()
162+
} else {
163+
replacements = [(&*old_name_upper, &*new_name_upper), ("", "")];
164+
&replacements[0..1]
165+
};
166+
167+
// Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being
168+
// renamed.
169+
for file in clippy_lints_src_files() {
170+
if file
171+
.path()
172+
.as_os_str()
173+
.to_str()
174+
.is_none_or(|x| x["clippy_lints/src/".len()..] != *"deprecated_lints.rs")
175+
{
176+
rewrite_file(file.path(), |s| replace_ident_like(s, replacements));
177+
}
178+
}
179+
180+
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
181+
println!("{old_name} has been successfully renamed");
182+
}
183+
184+
println!("note: `cargo uitest` still needs to be run to update the test results");
185+
}

0 commit comments

Comments
 (0)