Skip to content

Commit 18f4ce4

Browse files
committed
clippy_dev: Re-enable deleting the module declaration on deprecation and uplifting.
1 parent 0eb8a65 commit 18f4ce4

File tree

2 files changed

+117
-172
lines changed

2 files changed

+117
-172
lines changed

clippy_dev/src/edit_lints.rs

Lines changed: 101 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use crate::utils::{
66
expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target,
77
};
88
use rustc_lexer::TokenKind;
9-
use std::ffi::{OsStr, OsString};
10-
use std::path::{Path, PathBuf};
11-
use std::{fs, io};
9+
use std::ffi::OsString;
10+
use std::fs;
11+
use std::path::Path;
1212

1313
/// Runs the `deprecate` command
1414
///
@@ -23,7 +23,7 @@ pub fn deprecate<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, name
2323
let mut lints = cx.find_lint_decls();
2424
let (mut deprecated_lints, renamed_lints) = cx.read_deprecated_lints();
2525

26-
let Some(lint) = lints.iter().find(|l| l.name == name) else {
26+
let Some(lint_idx) = lints.iter().position(|l| l.name == name) else {
2727
eprintln!("error: failed to find lint `{name}`");
2828
return;
2929
};
@@ -47,30 +47,17 @@ pub fn deprecate<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, name
4747
),
4848
}
4949

50-
let mod_path = {
51-
let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module));
52-
if mod_path.is_dir() {
53-
mod_path = mod_path.join("mod");
54-
}
55-
56-
mod_path.set_extension("rs");
57-
mod_path
58-
};
59-
60-
if remove_lint_declaration(name, &mod_path, &mut lints).unwrap_or(false) {
61-
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
62-
println!("info: `{name}` has successfully been deprecated");
63-
println!("note: you must run `cargo uitest` to update the test results");
64-
} else {
65-
eprintln!("error: lint not found");
66-
}
50+
remove_lint_declaration(lint_idx, &mut lints, &mut FileUpdater::default());
51+
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
52+
println!("info: `{name}` has successfully been deprecated");
53+
println!("note: you must run `cargo uitest` to update the test results");
6754
}
6855

6956
pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'env str, new_name: &'env str) {
7057
let mut lints = cx.find_lint_decls();
7158
let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints();
7259

73-
let Some(lint) = lints.iter().find(|l| l.name == old_name) else {
60+
let Some(lint_idx) = lints.iter().position(|l| l.name == old_name) else {
7461
eprintln!("error: failed to find lint `{old_name}`");
7562
return;
7663
};
@@ -99,23 +86,18 @@ pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam
9986
),
10087
}
10188

102-
let mod_path = {
103-
let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module));
104-
if mod_path.is_dir() {
105-
mod_path = mod_path.join("mod");
89+
let mut updater = FileUpdater::default();
90+
let remove_mod = remove_lint_declaration(lint_idx, &mut lints, &mut updater);
91+
let mut update_fn = uplift_update_fn(old_name, new_name, remove_mod);
92+
for e in walk_dir_no_dot_or_target(".") {
93+
let e = expect_action(e, ErrAction::Read, ".");
94+
if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") {
95+
updater.update_file(e.path(), &mut update_fn);
10696
}
107-
108-
mod_path.set_extension("rs");
109-
mod_path
110-
};
111-
112-
if remove_lint_declaration(old_name, &mod_path, &mut lints).unwrap_or(false) {
113-
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
114-
println!("info: `{old_name}` has successfully been uplifted");
115-
println!("note: you must run `cargo uitest` to update the test results");
116-
} else {
117-
eprintln!("error: lint not found");
11897
}
98+
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
99+
println!("info: `{old_name}` has successfully been uplifted as `{new_name}`");
100+
println!("note: you must run `cargo uitest` to update the test results");
119101
}
120102

121103
/// Runs the `rename_lint` command.
@@ -173,7 +155,7 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam
173155
},
174156
}
175157

176-
let mut mod_edit = ModEdit::None;
158+
let mut rename_mod = false;
177159
if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() {
178160
let lint = &mut lints[lint_idx];
179161
if lint.module.ends_with(old_name)
@@ -185,7 +167,7 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam
185167
let mut new_path = lint.path.with_file_name(new_name).into_os_string();
186168
new_path.push(".rs");
187169
if try_rename_file(lint.path.as_ref(), new_path.as_ref()) {
188-
mod_edit = ModEdit::Rename;
170+
rename_mod = true;
189171
}
190172

191173
lint.module = cx.str_buf.with(|buf| {
@@ -212,7 +194,7 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam
212194
return;
213195
}
214196

215-
let mut update_fn = file_update_fn(old_name, new_name, mod_edit);
197+
let mut update_fn = rename_update_fn(old_name, new_name, rename_mod);
216198
for e in walk_dir_no_dot_or_target(".") {
217199
let e = expect_action(e, ErrAction::Read, ".");
218200
if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") {
@@ -227,110 +209,38 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam
227209
println!("note: `cargo uibless` still needs to be run to update the test results");
228210
}
229211

230-
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint<'_>>) -> io::Result<bool> {
231-
fn remove_lint(name: &str, lints: &mut Vec<Lint<'_>>) {
232-
lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));
233-
}
234-
235-
fn remove_test_assets(name: &str) {
236-
let test_file_stem = format!("tests/ui/{name}");
237-
let path = Path::new(&test_file_stem);
238-
239-
// Some lints have their own directories, delete them
240-
if path.is_dir() {
241-
let _ = fs::remove_dir_all(path);
242-
return;
243-
}
244-
245-
// Remove all related test files
246-
let _ = fs::remove_file(path.with_extension("rs"));
247-
let _ = fs::remove_file(path.with_extension("stderr"));
248-
let _ = fs::remove_file(path.with_extension("fixed"));
249-
}
250-
251-
fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) {
252-
let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| {
253-
content
254-
.find("declare_lint_pass!")
255-
.unwrap_or_else(|| panic!("failed to find `impl_lint_pass`"))
256-
});
257-
let mut impl_lint_pass_end = content[impl_lint_pass_start..]
258-
.find(']')
259-
.expect("failed to find `impl_lint_pass` terminator");
260-
261-
impl_lint_pass_end += impl_lint_pass_start;
262-
if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(lint_name_upper) {
263-
let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len());
264-
for c in content[lint_name_end..impl_lint_pass_end].chars() {
265-
// Remove trailing whitespace
266-
if c == ',' || c.is_whitespace() {
267-
lint_name_end += 1;
268-
} else {
269-
break;
270-
}
212+
/// Removes a lint's declaration and test files. Returns whether the module containing the
213+
/// lint was deleted.
214+
fn remove_lint_declaration(lint_idx: usize, lints: &mut Vec<Lint<'_>>, updater: &mut FileUpdater) -> bool {
215+
let lint = lints.remove(lint_idx);
216+
let delete_mod = if lints.iter().all(|l| l.module != lint.module) {
217+
delete_file_if_exists(lint.path.as_ref())
218+
} else {
219+
updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus {
220+
let mut start = &src[..lint.declaration_range.start];
221+
if start.ends_with("\n\n") {
222+
start = &start[..start.len() - 1];
271223
}
272-
273-
content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, "");
274-
}
275-
}
276-
277-
if path.exists()
278-
&& let Some(lint) = lints.iter().find(|l| l.name == name)
279-
{
280-
if lint.module == name {
281-
// The lint name is the same as the file, we can just delete the entire file
282-
fs::remove_file(path)?;
283-
} else {
284-
// We can't delete the entire file, just remove the declaration
285-
286-
if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) {
287-
// Remove clippy_lints/src/some_mod/some_lint.rs
288-
let mut lint_mod_path = path.to_path_buf();
289-
lint_mod_path.set_file_name(name);
290-
lint_mod_path.set_extension("rs");
291-
292-
let _ = fs::remove_file(lint_mod_path);
224+
let mut end = &src[lint.declaration_range.end..];
225+
if end.starts_with("\n\n") {
226+
end = &end[1..];
293227
}
294-
295-
let mut content =
296-
fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy()));
297-
298-
eprintln!(
299-
"warn: you will have to manually remove any code related to `{name}` from `{}`",
300-
path.display()
301-
);
302-
303-
assert!(
304-
content[lint.declaration_range].contains(&name.to_uppercase()),
305-
"error: `{}` does not contain lint `{}`'s declaration",
306-
path.display(),
307-
lint.name
308-
);
309-
310-
// Remove lint declaration (declare_clippy_lint!)
311-
content.replace_range(lint.declaration_range, "");
312-
313-
// Remove the module declaration (mod xyz;)
314-
let mod_decl = format!("\nmod {name};");
315-
content = content.replacen(&mod_decl, "", 1);
316-
317-
remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content);
318-
fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy()));
319-
}
320-
321-
remove_test_assets(name);
322-
remove_lint(name, lints);
323-
return Ok(true);
324-
}
325-
326-
Ok(false)
327-
}
328-
329-
#[derive(Clone, Copy)]
330-
enum ModEdit {
331-
None,
332-
Delete,
333-
Rename,
228+
dst.push_str(start);
229+
dst.push_str(end);
230+
UpdateStatus::Changed
231+
});
232+
false
233+
};
234+
delete_test_files(
235+
lint.name,
236+
&lints[lint_idx..]
237+
.iter()
238+
.map(|l| l.name)
239+
.take_while(|&n| n.starts_with(lint.name))
240+
.collect::<Vec<_>>(),
241+
);
242+
243+
delete_mod
334244
}
335245

336246
fn collect_ui_test_names(lint: &str, ignored_prefixes: &[&str], dst: &mut Vec<(OsString, bool)>) {
@@ -445,12 +355,50 @@ fn snake_to_pascal(s: &str) -> String {
445355
String::from_utf8(dst).unwrap()
446356
}
447357

448-
#[expect(clippy::too_many_lines)]
449-
fn file_update_fn<'a, 'b>(
358+
/// Creates an update function which replaces all instances of `clippy::old_name` with
359+
/// `new_name`.
360+
fn uplift_update_fn<'a>(
361+
old_name: &'a str,
362+
new_name: &'a str,
363+
remove_mod: bool,
364+
) -> impl use<'a> + FnMut(&Path, &str, &mut String) -> UpdateStatus {
365+
move |_, src, dst| {
366+
let mut copy_pos = 0u32;
367+
let mut changed = false;
368+
let mut cursor = Cursor::new(src);
369+
while let Some(ident) = cursor.find_any_ident() {
370+
match cursor.get_text(ident) {
371+
"mod"
372+
if remove_mod && cursor.match_all(&[cursor::Pat::Ident(old_name), cursor::Pat::Semi], &mut []) =>
373+
{
374+
dst.push_str(&src[copy_pos as usize..ident.pos as usize]);
375+
dst.push_str(new_name);
376+
copy_pos = cursor.pos();
377+
if src[copy_pos as usize..].starts_with('\n') {
378+
copy_pos += 1;
379+
}
380+
changed = true;
381+
},
382+
"clippy" if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::Ident(old_name)], &mut []) => {
383+
dst.push_str(&src[copy_pos as usize..ident.pos as usize]);
384+
dst.push_str(new_name);
385+
copy_pos = cursor.pos();
386+
changed = true;
387+
},
388+
389+
_ => {},
390+
}
391+
}
392+
dst.push_str(&src[copy_pos as usize..]);
393+
UpdateStatus::from_changed(changed)
394+
}
395+
}
396+
397+
fn rename_update_fn<'a>(
450398
old_name: &'a str,
451-
new_name: &'b str,
452-
mod_edit: ModEdit,
453-
) -> impl use<'a, 'b> + FnMut(&Path, &str, &mut String) -> UpdateStatus {
399+
new_name: &'a str,
400+
rename_mod: bool,
401+
) -> impl use<'a> + FnMut(&Path, &str, &mut String) -> UpdateStatus {
454402
let old_name_pascal = snake_to_pascal(old_name);
455403
let new_name_pascal = snake_to_pascal(new_name);
456404
let old_name_upper = old_name.to_ascii_uppercase();
@@ -481,34 +429,15 @@ fn file_update_fn<'a, 'b>(
481429
},
482430
// mod lint_name
483431
"mod" => {
484-
if !matches!(mod_edit, ModEdit::None)
485-
&& let Some(pos) = cursor.find_ident(old_name)
486-
{
487-
match mod_edit {
488-
ModEdit::Rename => {
489-
dst.push_str(&src[copy_pos as usize..pos as usize]);
490-
dst.push_str(new_name);
491-
copy_pos = cursor.pos();
492-
changed = true;
493-
},
494-
ModEdit::Delete if cursor.match_pat(cursor::Pat::Semi) => {
495-
let mut start = &src[copy_pos as usize..match_start as usize];
496-
if start.ends_with("\n\n") {
497-
start = &start[..start.len() - 1];
498-
}
499-
dst.push_str(start);
500-
copy_pos = cursor.pos();
501-
if src[copy_pos as usize..].starts_with("\n\n") {
502-
copy_pos += 1;
503-
}
504-
changed = true;
505-
},
506-
ModEdit::Delete | ModEdit::None => {},
507-
}
432+
if rename_mod && let Some(pos) = cursor.match_ident(old_name) {
433+
dst.push_str(&src[copy_pos as usize..pos as usize]);
434+
dst.push_str(new_name);
435+
copy_pos = cursor.pos();
436+
changed = true;
508437
}
509438
},
510439
// lint_name::
511-
name if matches!(mod_edit, ModEdit::Rename) && name == old_name => {
440+
name if rename_mod && name == old_name => {
512441
let name_end = cursor.pos();
513442
if cursor.match_pat(cursor::Pat::DoubleColon) {
514443
dst.push_str(&src[copy_pos as usize..match_start as usize]);

clippy_dev/src/parse/cursor.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,22 @@ impl<'txt> Cursor<'txt> {
219219
}
220220
}
221221

222+
/// Consume the returns the position of the next non-whitespace token if it's an
223+
/// identifier. Returns `None` otherwise.
224+
pub fn match_ident(&mut self, s: &str) -> Option<u32> {
225+
loop {
226+
match self.next_token.kind {
227+
TokenKind::Ident if s == self.peek_text() => {
228+
let pos = self.pos;
229+
self.step();
230+
return Some(pos);
231+
},
232+
TokenKind::Whitespace => self.step(),
233+
_ => return None,
234+
}
235+
}
236+
}
237+
222238
/// Continually attempt to match the pattern on subsequent tokens until a match is
223239
/// found. Returns whether the pattern was successfully matched.
224240
///

0 commit comments

Comments
 (0)