Skip to content

Commit 86f6c71

Browse files
committed
Improve cargo dev rename_lint
* Rename the module containing the lint if it has the same name * Check for identifier boundaries when replacing the lint name * Don't require a new name when uplifting lints
1 parent cc0e28d commit 86f6c71

File tree

5 files changed

+102
-18
lines changed

5 files changed

+102
-18
lines changed

clippy_dev/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.0.1"
44
edition = "2021"
55

66
[dependencies]
7+
aho-corasick = "0.7"
78
clap = "2.33"
89
indoc = "1.0"
910
itertools = "0.10.1"

clippy_dev/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#![feature(let_chains)]
12
#![feature(let_else)]
23
#![feature(once_cell)]
34
#![feature(rustc_private)]

clippy_dev/src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fn main() {
6161
},
6262
("rename_lint", Some(matches)) => {
6363
let old_name = matches.value_of("old_name").unwrap();
64-
let new_name = matches.value_of("new_name").unwrap();
64+
let new_name = matches.value_of("new_name").unwrap_or(old_name);
6565
let uplift = matches.is_present("uplift");
6666
update_lints::rename(old_name, new_name, uplift);
6767
},
@@ -250,13 +250,13 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
250250
.arg(
251251
Arg::with_name("new_name")
252252
.index(2)
253-
.required(true)
253+
.required_unless("uplift")
254254
.help("The new name of the lint"),
255255
)
256256
.arg(
257257
Arg::with_name("uplift")
258258
.long("uplift")
259-
.help("This lint was uplifted into rustc"),
259+
.help("This lint will be uplifted into rustc"),
260260
),
261261
)
262262
.get_matches()

clippy_dev/src/update_lints.rs

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
use core::fmt::Write;
1+
use aho_corasick::AhoCorasickBuilder;
2+
use core::fmt::Write as _;
23
use itertools::Itertools;
34
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
45
use std::collections::{HashMap, HashSet};
56
use std::ffi::OsStr;
67
use std::fs;
8+
use std::io::Write as _;
79
use std::path::{Path, PathBuf};
810
use walkdir::{DirEntry, WalkDir};
911

@@ -143,12 +145,20 @@ pub fn print_lints() {
143145

144146
/// Runs the `rename_lint` command.
145147
///
148+
/// This does the following:
149+
/// * Adds an entry to `renamed_lints.rs`.
150+
/// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`).
151+
/// * Renames the lint struct to the new name.
152+
/// * Renames the module containing the lint struct to the new name if it shares a name with the
153+
/// lint.
154+
///
146155
/// # Panics
147156
/// Panics for the following conditions:
148157
/// * If a file path could not read from or then written to
149158
/// * If either lint name has a prefix
150159
/// * If `old_name` doesn't name an existing lint.
151160
/// * If `old_name` names a deprecated or renamed lint.
161+
#[allow(clippy::too_many_lines)]
152162
pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
153163
if let Some((prefix, _)) = old_name.split_once("::") {
154164
panic!("`{}` should not contain the `{}` prefix", old_name, prefix);
@@ -181,17 +191,17 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
181191
// Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in
182192
// case.)
183193
assert!(
184-
renamed_lints.iter().any(|l| lint.old_name == l.old_name),
194+
!renamed_lints.iter().any(|l| lint.old_name == l.old_name),
185195
"`{}` has already been renamed",
186196
old_name
187197
);
188198
assert!(
189-
deprecated_lints.iter().any(|l| lint.old_name == l.name),
199+
!deprecated_lints.iter().any(|l| lint.old_name == l.name),
190200
"`{}` has already been deprecated",
191201
old_name
192202
);
193203

194-
// Update all lint level attributes.
204+
// Update all lint level attributes. (`clippy::lint_name`)
195205
for file in WalkDir::new(clippy_project_root())
196206
.into_iter()
197207
.map(Result::unwrap)
@@ -205,8 +215,8 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
205215
let path = file.path();
206216
let contents =
207217
fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
208-
if contents.contains(&lint.old_name) {
209-
fs::write(path, contents.replace(&lint.old_name, &lint.new_name).as_bytes())
218+
if let Some(contents) = replace_ident_like(&contents, &[(&lint.old_name, &lint.new_name)]) {
219+
fs::write(path, contents.as_bytes())
210220
.unwrap_or_else(|e| panic!("Cannot write to `{}`: {}", path.display(), e));
211221
}
212222
}
@@ -222,24 +232,53 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
222232

223233
if found_new_name {
224234
println!(
225-
"`{}` is already defined. Removal the old lint code in `clippy_lints` will have to be done manually",
235+
"`{}` is already defined. The old linting code inside `clippy_lints` has to be updated/removed manually",
226236
new_name
227237
);
238+
// Still update the test file
228239
let contents = gen_renamed_lints_test(&renamed_lints);
229240
fs::write("tests/ui/rename.rs", contents.as_bytes())
230241
.unwrap_or_else(|e| panic!("error writing file `tests/ui/rename.rs`: {}", e));
231242
} else {
232-
// Update the lint struct name
233-
lints[old_lint_index].name = new_name.into();
234-
let old_name = old_name.to_uppercase();
235-
let new_name = new_name.to_uppercase();
243+
// Rename the lint struct and the containing module if it shares the lint name across all files.
244+
let lint = &mut lints[old_lint_index];
245+
let old_name_upper = old_name.to_uppercase();
246+
let new_name_upper = new_name.to_uppercase();
247+
let new_module_path = PathBuf::from(format!("clippy_lints/src/{}.rs", new_name));
248+
let replacements;
249+
250+
let (replacements, mut renamed_file) = if lint.module == lint.name
251+
// Only rename the module if the file doesn't already exist.
252+
&& let Ok(file) = fs::OpenOptions::new().create_new(true).write(true).open(&new_module_path)
253+
{
254+
lint.module = new_name.into();
255+
replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
256+
(replacements.as_slice(), Some(file))
257+
} else {
258+
replacements = [(&*old_name_upper, &*new_name_upper), ("", "")];
259+
(&replacements[0..1], None)
260+
};
261+
lint.name = new_name.into();
236262

237-
for (_, file) in clippy_lints_src_files() {
263+
for (rel_path, file) in clippy_lints_src_files() {
238264
let path = file.path();
239265
let contents =
240266
fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
241-
if contents.contains(&old_name) {
242-
fs::write(path, contents.replace(&old_name, &new_name).as_bytes())
267+
let new_contents = replace_ident_like(&contents, replacements);
268+
269+
if let Some(ref mut file) = renamed_file
270+
// Make sure the file is directly in the src directory and not a sub-directory.
271+
&& rel_path.parent().map_or(false, |p| p.as_os_str().is_empty())
272+
&& rel_path.file_stem() == Some(OsStr::new(old_name))
273+
{
274+
let contents = new_contents.as_ref().unwrap_or(&contents);
275+
file.write_all(contents.as_bytes())
276+
.unwrap_or_else(|e| panic!("Cannot write to `{}`: {}", new_module_path.display(), e));
277+
renamed_file = None;
278+
fs::remove_file(path)
279+
.unwrap_or_else(|e| panic!("Cannot delete file `{}`: {}", path.display(), e));
280+
} else if let Some(contents) = new_contents {
281+
fs::write(path, contents.as_bytes())
243282
.unwrap_or_else(|e| panic!("Cannot write to `{}`: {}", path.display(), e));
244283
}
245284
}
@@ -252,6 +291,41 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
252291
.unwrap_or_else(|e| panic!("error writing file `clippy_lints/src/renamed_lints.rs`: {}", e));
253292
}
254293

294+
/// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there
295+
/// were no replacements.
296+
fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> {
297+
fn is_ident_char(c: u8) -> bool {
298+
matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')
299+
}
300+
301+
let searcher = AhoCorasickBuilder::new()
302+
.dfa(true)
303+
.match_kind(aho_corasick::MatchKind::LeftmostLongest)
304+
.build_with_size::<u16, _, _>(replacements.iter().map(|&(x, _)| x.as_bytes()))
305+
.unwrap();
306+
307+
let mut result = String::with_capacity(contents.len() + 1024);
308+
let mut pos = 0;
309+
let mut edited = false;
310+
for m in searcher.find_iter(contents) {
311+
let (old, new) = replacements[m.pattern()];
312+
result.push_str(&contents[pos..m.start()]);
313+
result.push_str(
314+
if !is_ident_char(contents.as_bytes().get(m.start().wrapping_sub(1)).copied().unwrap_or(0))
315+
&& !is_ident_char(contents.as_bytes().get(m.end()).copied().unwrap_or(0))
316+
{
317+
edited = true;
318+
new
319+
} else {
320+
old
321+
},
322+
);
323+
pos = m.end();
324+
}
325+
result.push_str(&contents[pos..]);
326+
edited.then(|| result)
327+
}
328+
255329
fn round_to_fifty(count: usize) -> usize {
256330
count / 50 * 50
257331
}
@@ -442,7 +516,12 @@ fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String {
442516
}
443517

444518
fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String {
445-
let mut res = String::from("pub static RENAMED_LINTS: &[(&str, &str)] = &[\n");
519+
const HEADER: &str = "\
520+
// This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\
521+
#[rustfmt::skip]\n\
522+
pub static RENAMED_LINTS: &[(&str, &str)] = &[\n";
523+
524+
let mut res = String::from(HEADER);
446525
for lint in lints {
447526
writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap();
448527
}

clippy_lints/src/renamed_lints.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// This file is managed by `cargo dev rename_lint`. Prefer using that when possible.
2+
3+
#[rustfmt::skip]
14
pub static RENAMED_LINTS: &[(&str, &str)] = &[
25
("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions"),
36
("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions"),

0 commit comments

Comments
 (0)