@@ -6,9 +6,9 @@ use crate::utils::{
66 expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target,
77} ;
88use 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
6956pub 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 ! ( "\n mod {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
336246fn 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 ] ) ;
0 commit comments