1
- use core:: fmt:: Write ;
1
+ use aho_corasick:: AhoCorasickBuilder ;
2
+ use core:: fmt:: Write as _;
2
3
use itertools:: Itertools ;
3
4
use rustc_lexer:: { tokenize, unescape, LiteralKind , TokenKind } ;
4
5
use std:: collections:: { HashMap , HashSet } ;
5
6
use std:: ffi:: OsStr ;
6
7
use std:: fs;
8
+ use std:: io:: Write as _;
7
9
use std:: path:: { Path , PathBuf } ;
8
10
use walkdir:: { DirEntry , WalkDir } ;
9
11
@@ -143,12 +145,20 @@ pub fn print_lints() {
143
145
144
146
/// Runs the `rename_lint` command.
145
147
///
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
+ ///
146
155
/// # Panics
147
156
/// Panics for the following conditions:
148
157
/// * If a file path could not read from or then written to
149
158
/// * If either lint name has a prefix
150
159
/// * If `old_name` doesn't name an existing lint.
151
160
/// * If `old_name` names a deprecated or renamed lint.
161
+ #[ allow( clippy:: too_many_lines) ]
152
162
pub fn rename ( old_name : & str , new_name : & str , uplift : bool ) {
153
163
if let Some ( ( prefix, _) ) = old_name. split_once ( "::" ) {
154
164
panic ! ( "`{}` should not contain the `{}` prefix" , old_name, prefix) ;
@@ -181,17 +191,17 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
181
191
// Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in
182
192
// case.)
183
193
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) ,
185
195
"`{}` has already been renamed" ,
186
196
old_name
187
197
) ;
188
198
assert ! (
189
- deprecated_lints. iter( ) . any( |l| lint. old_name == l. name) ,
199
+ ! deprecated_lints. iter( ) . any( |l| lint. old_name == l. name) ,
190
200
"`{}` has already been deprecated" ,
191
201
old_name
192
202
) ;
193
203
194
- // Update all lint level attributes.
204
+ // Update all lint level attributes. (`clippy::lint_name`)
195
205
for file in WalkDir :: new ( clippy_project_root ( ) )
196
206
. into_iter ( )
197
207
. map ( Result :: unwrap)
@@ -205,8 +215,8 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
205
215
let path = file. path ( ) ;
206
216
let contents =
207
217
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 ( ) )
210
220
. unwrap_or_else ( |e| panic ! ( "Cannot write to `{}`: {}" , path. display( ) , e) ) ;
211
221
}
212
222
}
@@ -222,24 +232,53 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
222
232
223
233
if found_new_name {
224
234
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" ,
226
236
new_name
227
237
) ;
238
+ // Still update the test file
228
239
let contents = gen_renamed_lints_test ( & renamed_lints) ;
229
240
fs:: write ( "tests/ui/rename.rs" , contents. as_bytes ( ) )
230
241
. unwrap_or_else ( |e| panic ! ( "error writing file `tests/ui/rename.rs`: {}" , e) ) ;
231
242
} 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 ( ) ;
236
262
237
- for ( _ , file) in clippy_lints_src_files ( ) {
263
+ for ( rel_path , file) in clippy_lints_src_files ( ) {
238
264
let path = file. path ( ) ;
239
265
let contents =
240
266
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 ( ) )
243
282
. unwrap_or_else ( |e| panic ! ( "Cannot write to `{}`: {}" , path. display( ) , e) ) ;
244
283
}
245
284
}
@@ -252,6 +291,41 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
252
291
. unwrap_or_else ( |e| panic ! ( "error writing file `clippy_lints/src/renamed_lints.rs`: {}" , e) ) ;
253
292
}
254
293
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
+
255
329
fn round_to_fifty ( count : usize ) -> usize {
256
330
count / 50 * 50
257
331
}
@@ -442,7 +516,12 @@ fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String {
442
516
}
443
517
444
518
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 ) ;
446
525
for lint in lints {
447
526
writeln ! ( res, " (\" {}\" , \" {}\" )," , lint. old_name, lint. new_name) . unwrap ( ) ;
448
527
}
0 commit comments