]>
Commit | Line | Data |
---|---|---|
064997fb | 1 | use crate::clippy_project_root; |
04454e1e | 2 | use aho_corasick::AhoCorasickBuilder; |
064997fb | 3 | use indoc::writedoc; |
c295e0f8 | 4 | use itertools::Itertools; |
04454e1e FG |
5 | use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; |
6 | use std::collections::{HashMap, HashSet}; | |
c295e0f8 | 7 | use std::ffi::OsStr; |
064997fb FG |
8 | use std::fmt::Write; |
9 | use std::fs::{self, OpenOptions}; | |
10 | use std::io::{self, Read, Seek, SeekFrom, Write as _}; | |
11 | use std::ops::Range; | |
04454e1e FG |
12 | use std::path::{Path, PathBuf}; |
13 | use walkdir::{DirEntry, WalkDir}; | |
c295e0f8 | 14 | |
c295e0f8 XL |
15 | const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ |
16 | // Use that command to update this file and do not edit by hand.\n\ | |
17 | // Manual edits will be overwritten.\n\n"; | |
18 | ||
04454e1e | 19 | const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; |
f20569fa | 20 | |
923072b8 | 21 | #[derive(Clone, Copy, PartialEq, Eq)] |
f20569fa XL |
22 | pub enum UpdateMode { |
23 | Check, | |
24 | Change, | |
25 | } | |
26 | ||
c295e0f8 XL |
27 | /// Runs the `update_lints` command. |
28 | /// | |
29 | /// This updates various generated values from the lint source code. | |
30 | /// | |
31 | /// `update_mode` indicates if the files should be updated or if updates should be checked for. | |
32 | /// | |
33 | /// # Panics | |
34 | /// | |
35 | /// Panics if a file path could not read from or then written to | |
04454e1e FG |
36 | pub fn update(update_mode: UpdateMode) { |
37 | let (lints, deprecated_lints, renamed_lints) = gather_all(); | |
38 | generate_lint_files(update_mode, &lints, &deprecated_lints, &renamed_lints); | |
39 | } | |
f20569fa | 40 | |
04454e1e FG |
41 | fn generate_lint_files( |
42 | update_mode: UpdateMode, | |
43 | lints: &[Lint], | |
44 | deprecated_lints: &[DeprecatedLint], | |
45 | renamed_lints: &[RenamedLint], | |
46 | ) { | |
47 | let internal_lints = Lint::internal_lints(lints); | |
48 | let usable_lints = Lint::usable_lints(lints); | |
f20569fa XL |
49 | let mut sorted_usable_lints = usable_lints.clone(); |
50 | sorted_usable_lints.sort_by_key(|lint| lint.name.clone()); | |
51 | ||
04454e1e FG |
52 | replace_region_in_file( |
53 | update_mode, | |
f20569fa | 54 | Path::new("README.md"), |
04454e1e FG |
55 | "[There are over ", |
56 | " lints included in this crate!]", | |
57 | |res| { | |
58 | write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap(); | |
f20569fa | 59 | }, |
04454e1e | 60 | ); |
f20569fa | 61 | |
923072b8 FG |
62 | replace_region_in_file( |
63 | update_mode, | |
64 | Path::new("book/src/README.md"), | |
65 | "[There are over ", | |
66 | " lints included in this crate!]", | |
67 | |res| { | |
68 | write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap(); | |
69 | }, | |
70 | ); | |
71 | ||
04454e1e FG |
72 | replace_region_in_file( |
73 | update_mode, | |
f20569fa | 74 | Path::new("CHANGELOG.md"), |
04454e1e | 75 | "<!-- begin autogenerated links to lint list -->\n", |
f20569fa | 76 | "<!-- end autogenerated links to lint list -->", |
04454e1e FG |
77 | |res| { |
78 | for lint in usable_lints | |
79 | .iter() | |
923072b8 FG |
80 | .map(|l| &*l.name) |
81 | .chain(deprecated_lints.iter().map(|l| &*l.name)) | |
82 | .chain( | |
83 | renamed_lints | |
84 | .iter() | |
85 | .map(|l| l.old_name.strip_prefix("clippy::").unwrap_or(&l.old_name)), | |
86 | ) | |
04454e1e FG |
87 | .sorted() |
88 | { | |
89 | writeln!(res, "[`{}`]: {}#{}", lint, DOCS_LINK, lint).unwrap(); | |
90 | } | |
91 | }, | |
92 | ); | |
f20569fa | 93 | |
c295e0f8 | 94 | // This has to be in lib.rs, otherwise rustfmt doesn't work |
04454e1e FG |
95 | replace_region_in_file( |
96 | update_mode, | |
f20569fa | 97 | Path::new("clippy_lints/src/lib.rs"), |
04454e1e FG |
98 | "// begin lints modules, do not remove this comment, it’s used in `update_lints`\n", |
99 | "// end lints modules, do not remove this comment, it’s used in `update_lints`", | |
100 | |res| { | |
101 | for lint_mod in usable_lints.iter().map(|l| &l.module).unique().sorted() { | |
102 | writeln!(res, "mod {};", lint_mod).unwrap(); | |
103 | } | |
104 | }, | |
105 | ); | |
f20569fa | 106 | |
c295e0f8 XL |
107 | process_file( |
108 | "clippy_lints/src/lib.register_lints.rs", | |
109 | update_mode, | |
110 | &gen_register_lint_list(internal_lints.iter(), usable_lints.iter()), | |
111 | ); | |
112 | process_file( | |
113 | "clippy_lints/src/lib.deprecated.rs", | |
114 | update_mode, | |
04454e1e | 115 | &gen_deprecated(deprecated_lints), |
c295e0f8 XL |
116 | ); |
117 | ||
118 | let all_group_lints = usable_lints.iter().filter(|l| { | |
119 | matches!( | |
120 | &*l.group, | |
121 | "correctness" | "suspicious" | "style" | "complexity" | "perf" | |
f20569fa | 122 | ) |
c295e0f8 XL |
123 | }); |
124 | let content = gen_lint_group_list("all", all_group_lints); | |
125 | process_file("clippy_lints/src/lib.register_all.rs", update_mode, &content); | |
f20569fa | 126 | |
c295e0f8 XL |
127 | for (lint_group, lints) in Lint::by_lint_group(usable_lints.into_iter().chain(internal_lints)) { |
128 | let content = gen_lint_group_list(&lint_group, lints.iter()); | |
129 | process_file( | |
130 | &format!("clippy_lints/src/lib.register_{}.rs", lint_group), | |
131 | update_mode, | |
132 | &content, | |
f20569fa | 133 | ); |
f20569fa | 134 | } |
04454e1e FG |
135 | |
136 | let content = gen_deprecated_lints_test(deprecated_lints); | |
137 | process_file("tests/ui/deprecated.rs", update_mode, &content); | |
138 | ||
139 | let content = gen_renamed_lints_test(renamed_lints); | |
140 | process_file("tests/ui/rename.rs", update_mode, &content); | |
f20569fa XL |
141 | } |
142 | ||
143 | pub fn print_lints() { | |
04454e1e | 144 | let (lint_list, _, _) = gather_all(); |
f20569fa XL |
145 | let usable_lints = Lint::usable_lints(&lint_list); |
146 | let usable_lint_count = usable_lints.len(); | |
147 | let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter()); | |
148 | ||
149 | for (lint_group, mut lints) in grouped_by_lint_group { | |
f20569fa XL |
150 | println!("\n## {}", lint_group); |
151 | ||
152 | lints.sort_by_key(|l| l.name.clone()); | |
153 | ||
154 | for lint in lints { | |
155 | println!("* [{}]({}#{}) ({})", lint.name, DOCS_LINK, lint.name, lint.desc); | |
156 | } | |
157 | } | |
158 | ||
159 | println!("there are {} lints", usable_lint_count); | |
160 | } | |
161 | ||
04454e1e FG |
162 | /// Runs the `rename_lint` command. |
163 | /// | |
164 | /// This does the following: | |
165 | /// * Adds an entry to `renamed_lints.rs`. | |
166 | /// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`). | |
167 | /// * Renames the lint struct to the new name. | |
168 | /// * Renames the module containing the lint struct to the new name if it shares a name with the | |
169 | /// lint. | |
170 | /// | |
171 | /// # Panics | |
172 | /// Panics for the following conditions: | |
173 | /// * If a file path could not read from or then written to | |
174 | /// * If either lint name has a prefix | |
175 | /// * If `old_name` doesn't name an existing lint. | |
176 | /// * If `old_name` names a deprecated or renamed lint. | |
177 | #[allow(clippy::too_many_lines)] | |
178 | pub fn rename(old_name: &str, new_name: &str, uplift: bool) { | |
179 | if let Some((prefix, _)) = old_name.split_once("::") { | |
180 | panic!("`{}` should not contain the `{}` prefix", old_name, prefix); | |
181 | } | |
182 | if let Some((prefix, _)) = new_name.split_once("::") { | |
183 | panic!("`{}` should not contain the `{}` prefix", new_name, prefix); | |
184 | } | |
185 | ||
186 | let (mut lints, deprecated_lints, mut renamed_lints) = gather_all(); | |
187 | let mut old_lint_index = None; | |
188 | let mut found_new_name = false; | |
189 | for (i, lint) in lints.iter().enumerate() { | |
190 | if lint.name == old_name { | |
191 | old_lint_index = Some(i); | |
192 | } else if lint.name == new_name { | |
193 | found_new_name = true; | |
194 | } | |
195 | } | |
196 | let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{}`", old_name)); | |
197 | ||
198 | let lint = RenamedLint { | |
199 | old_name: format!("clippy::{}", old_name), | |
200 | new_name: if uplift { | |
201 | new_name.into() | |
202 | } else { | |
203 | format!("clippy::{}", new_name) | |
204 | }, | |
205 | }; | |
206 | ||
207 | // Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in | |
208 | // case. | |
209 | assert!( | |
210 | !renamed_lints.iter().any(|l| lint.old_name == l.old_name), | |
211 | "`{}` has already been renamed", | |
212 | old_name | |
213 | ); | |
214 | assert!( | |
215 | !deprecated_lints.iter().any(|l| lint.old_name == l.name), | |
216 | "`{}` has already been deprecated", | |
217 | old_name | |
218 | ); | |
219 | ||
220 | // Update all lint level attributes. (`clippy::lint_name`) | |
221 | for file in WalkDir::new(clippy_project_root()) | |
222 | .into_iter() | |
223 | .map(Result::unwrap) | |
224 | .filter(|f| { | |
225 | let name = f.path().file_name(); | |
226 | let ext = f.path().extension(); | |
227 | (ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed"))) | |
228 | && name != Some(OsStr::new("rename.rs")) | |
229 | && name != Some(OsStr::new("renamed_lints.rs")) | |
230 | }) | |
231 | { | |
232 | rewrite_file(file.path(), |s| { | |
233 | replace_ident_like(s, &[(&lint.old_name, &lint.new_name)]) | |
234 | }); | |
235 | } | |
236 | ||
237 | renamed_lints.push(lint); | |
238 | renamed_lints.sort_by(|lhs, rhs| { | |
239 | lhs.new_name | |
240 | .starts_with("clippy::") | |
241 | .cmp(&rhs.new_name.starts_with("clippy::")) | |
242 | .reverse() | |
243 | .then_with(|| lhs.old_name.cmp(&rhs.old_name)) | |
244 | }); | |
245 | ||
246 | write_file( | |
247 | Path::new("clippy_lints/src/renamed_lints.rs"), | |
248 | &gen_renamed_lints_list(&renamed_lints), | |
249 | ); | |
250 | ||
251 | if uplift { | |
252 | write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints)); | |
253 | println!( | |
254 | "`{}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually.", | |
255 | old_name | |
256 | ); | |
257 | } else if found_new_name { | |
258 | write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints)); | |
259 | println!( | |
260 | "`{}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually.", | |
261 | new_name | |
262 | ); | |
263 | } else { | |
264 | // Rename the lint struct and source files sharing a name with the lint. | |
265 | let lint = &mut lints[old_lint_index]; | |
266 | let old_name_upper = old_name.to_uppercase(); | |
267 | let new_name_upper = new_name.to_uppercase(); | |
268 | lint.name = new_name.into(); | |
269 | ||
270 | // Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist. | |
271 | if try_rename_file( | |
272 | Path::new(&format!("tests/ui/{}.rs", old_name)), | |
273 | Path::new(&format!("tests/ui/{}.rs", new_name)), | |
274 | ) { | |
275 | try_rename_file( | |
276 | Path::new(&format!("tests/ui/{}.stderr", old_name)), | |
277 | Path::new(&format!("tests/ui/{}.stderr", new_name)), | |
278 | ); | |
279 | try_rename_file( | |
280 | Path::new(&format!("tests/ui/{}.fixed", old_name)), | |
281 | Path::new(&format!("tests/ui/{}.fixed", new_name)), | |
282 | ); | |
283 | } | |
284 | ||
285 | // Try to rename the file containing the lint if the file name matches the lint's name. | |
286 | let replacements; | |
287 | let replacements = if lint.module == old_name | |
288 | && try_rename_file( | |
289 | Path::new(&format!("clippy_lints/src/{}.rs", old_name)), | |
290 | Path::new(&format!("clippy_lints/src/{}.rs", new_name)), | |
291 | ) { | |
292 | // Edit the module name in the lint list. Note there could be multiple lints. | |
293 | for lint in lints.iter_mut().filter(|l| l.module == old_name) { | |
294 | lint.module = new_name.into(); | |
295 | } | |
296 | replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)]; | |
297 | replacements.as_slice() | |
298 | } else if !lint.module.contains("::") | |
299 | // Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs` | |
300 | && try_rename_file( | |
301 | Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, old_name)), | |
302 | Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, new_name)), | |
303 | ) | |
304 | { | |
305 | // Edit the module name in the lint list. Note there could be multiple lints, or none. | |
306 | let renamed_mod = format!("{}::{}", lint.module, old_name); | |
307 | for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) { | |
308 | lint.module = format!("{}::{}", lint.module, new_name); | |
309 | } | |
310 | replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)]; | |
311 | replacements.as_slice() | |
312 | } else { | |
313 | replacements = [(&*old_name_upper, &*new_name_upper), ("", "")]; | |
314 | &replacements[0..1] | |
315 | }; | |
316 | ||
317 | // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being | |
318 | // renamed. | |
319 | for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("renamed_lints.rs")) { | |
320 | rewrite_file(file.path(), |s| replace_ident_like(s, replacements)); | |
321 | } | |
322 | ||
323 | generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); | |
324 | println!("{} has been successfully renamed", old_name); | |
325 | } | |
326 | ||
327 | println!("note: `cargo uitest` still needs to be run to update the test results"); | |
328 | } | |
329 | ||
064997fb FG |
330 | const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note"; |
331 | /// Runs the `deprecate` command | |
332 | /// | |
333 | /// This does the following: | |
334 | /// * Adds an entry to `deprecated_lints.rs`. | |
335 | /// * Removes the lint declaration (and the entire file if applicable) | |
336 | /// | |
337 | /// # Panics | |
338 | /// | |
339 | /// If a file path could not read from or written to | |
340 | pub fn deprecate(name: &str, reason: Option<&String>) { | |
341 | fn finish( | |
342 | (lints, mut deprecated_lints, renamed_lints): (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>), | |
343 | name: &str, | |
344 | reason: &str, | |
345 | ) { | |
346 | deprecated_lints.push(DeprecatedLint { | |
347 | name: name.to_string(), | |
348 | reason: reason.to_string(), | |
349 | declaration_range: Range::default(), | |
350 | }); | |
351 | ||
352 | generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); | |
353 | println!("info: `{}` has successfully been deprecated", name); | |
354 | ||
355 | if reason == DEFAULT_DEPRECATION_REASON { | |
356 | println!("note: the deprecation reason must be updated in `clippy_lints/src/deprecated_lints.rs`"); | |
357 | } | |
358 | println!("note: you must run `cargo uitest` to update the test results"); | |
359 | } | |
360 | ||
361 | let reason = reason.map_or(DEFAULT_DEPRECATION_REASON, String::as_str); | |
362 | let name_lower = name.to_lowercase(); | |
363 | let name_upper = name.to_uppercase(); | |
364 | ||
365 | let (mut lints, deprecated_lints, renamed_lints) = gather_all(); | |
366 | let Some(lint) = lints.iter().find(|l| l.name == name_lower) else { eprintln!("error: failed to find lint `{}`", name); return; }; | |
367 | ||
368 | let mod_path = { | |
369 | let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module)); | |
370 | if mod_path.is_dir() { | |
371 | mod_path = mod_path.join("mod"); | |
372 | } | |
373 | ||
374 | mod_path.set_extension("rs"); | |
375 | mod_path | |
376 | }; | |
377 | ||
378 | let deprecated_lints_path = &*clippy_project_root().join("clippy_lints/src/deprecated_lints.rs"); | |
379 | ||
380 | if remove_lint_declaration(&name_lower, &mod_path, &mut lints).unwrap_or(false) { | |
381 | declare_deprecated(&name_upper, deprecated_lints_path, reason).unwrap(); | |
382 | finish((lints, deprecated_lints, renamed_lints), name, reason); | |
383 | return; | |
384 | } | |
385 | ||
386 | eprintln!("error: lint not found"); | |
387 | } | |
388 | ||
389 | fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> { | |
390 | fn remove_lint(name: &str, lints: &mut Vec<Lint>) { | |
391 | lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); | |
392 | } | |
393 | ||
394 | fn remove_test_assets(name: &str) { | |
395 | let test_file_stem = format!("tests/ui/{}", name); | |
396 | let path = Path::new(&test_file_stem); | |
397 | ||
398 | // Some lints have their own directories, delete them | |
399 | if path.is_dir() { | |
400 | fs::remove_dir_all(path).ok(); | |
401 | return; | |
402 | } | |
403 | ||
404 | // Remove all related test files | |
405 | fs::remove_file(path.with_extension("rs")).ok(); | |
406 | fs::remove_file(path.with_extension("stderr")).ok(); | |
407 | fs::remove_file(path.with_extension("fixed")).ok(); | |
408 | } | |
409 | ||
410 | fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) { | |
411 | let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| { | |
412 | content | |
413 | .find("declare_lint_pass!") | |
414 | .unwrap_or_else(|| panic!("failed to find `impl_lint_pass`")) | |
415 | }); | |
416 | let mut impl_lint_pass_end = content[impl_lint_pass_start..] | |
417 | .find(']') | |
418 | .expect("failed to find `impl_lint_pass` terminator"); | |
419 | ||
420 | impl_lint_pass_end += impl_lint_pass_start; | |
421 | if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(&lint_name_upper) { | |
422 | let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len()); | |
423 | for c in content[lint_name_end..impl_lint_pass_end].chars() { | |
424 | // Remove trailing whitespace | |
425 | if c == ',' || c.is_whitespace() { | |
426 | lint_name_end += 1; | |
427 | } else { | |
428 | break; | |
429 | } | |
430 | } | |
431 | ||
432 | content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, ""); | |
433 | } | |
434 | } | |
435 | ||
436 | if path.exists() { | |
437 | if let Some(lint) = lints.iter().find(|l| l.name == name) { | |
438 | if lint.module == name { | |
439 | // The lint name is the same as the file, we can just delete the entire file | |
440 | fs::remove_file(path)?; | |
441 | } else { | |
442 | // We can't delete the entire file, just remove the declaration | |
443 | ||
444 | if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) { | |
445 | // Remove clippy_lints/src/some_mod/some_lint.rs | |
446 | let mut lint_mod_path = path.to_path_buf(); | |
447 | lint_mod_path.set_file_name(name); | |
448 | lint_mod_path.set_extension("rs"); | |
449 | ||
450 | fs::remove_file(lint_mod_path).ok(); | |
451 | } | |
452 | ||
453 | let mut content = | |
454 | fs::read_to_string(&path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); | |
455 | ||
456 | eprintln!( | |
457 | "warn: you will have to manually remove any code related to `{}` from `{}`", | |
458 | name, | |
459 | path.display() | |
460 | ); | |
461 | ||
462 | assert!( | |
463 | content[lint.declaration_range.clone()].contains(&name.to_uppercase()), | |
464 | "error: `{}` does not contain lint `{}`'s declaration", | |
465 | path.display(), | |
466 | lint.name | |
467 | ); | |
468 | ||
469 | // Remove lint declaration (declare_clippy_lint!) | |
470 | content.replace_range(lint.declaration_range.clone(), ""); | |
471 | ||
472 | // Remove the module declaration (mod xyz;) | |
473 | let mod_decl = format!("\nmod {};", name); | |
474 | content = content.replacen(&mod_decl, "", 1); | |
475 | ||
476 | remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content); | |
477 | fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy())); | |
478 | } | |
479 | ||
480 | remove_test_assets(name); | |
481 | remove_lint(name, lints); | |
482 | return Ok(true); | |
483 | } | |
484 | } | |
485 | ||
486 | Ok(false) | |
487 | } | |
488 | ||
489 | fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> { | |
490 | let mut file = OpenOptions::new().write(true).open(path)?; | |
491 | ||
492 | file.seek(SeekFrom::End(0))?; | |
493 | ||
494 | let version = crate::new_lint::get_stabilization_version(); | |
495 | let deprecation_reason = if reason == DEFAULT_DEPRECATION_REASON { | |
496 | "TODO" | |
497 | } else { | |
498 | reason | |
499 | }; | |
500 | ||
501 | writedoc!( | |
502 | file, | |
503 | " | |
504 | ||
505 | declare_deprecated_lint! {{ | |
506 | /// ### What it does | |
507 | /// Nothing. This lint has been deprecated. | |
508 | /// | |
509 | /// ### Deprecation reason | |
510 | /// {} | |
511 | #[clippy::version = \"{}\"] | |
512 | pub {}, | |
513 | \"{}\" | |
514 | }} | |
515 | ||
516 | ", | |
517 | deprecation_reason, | |
518 | version, | |
519 | name, | |
520 | reason, | |
521 | ) | |
522 | } | |
523 | ||
04454e1e FG |
524 | /// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there |
525 | /// were no replacements. | |
526 | fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> { | |
527 | fn is_ident_char(c: u8) -> bool { | |
528 | matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_') | |
529 | } | |
530 | ||
531 | let searcher = AhoCorasickBuilder::new() | |
532 | .dfa(true) | |
533 | .match_kind(aho_corasick::MatchKind::LeftmostLongest) | |
534 | .build_with_size::<u16, _, _>(replacements.iter().map(|&(x, _)| x.as_bytes())) | |
535 | .unwrap(); | |
536 | ||
537 | let mut result = String::with_capacity(contents.len() + 1024); | |
538 | let mut pos = 0; | |
539 | let mut edited = false; | |
540 | for m in searcher.find_iter(contents) { | |
541 | let (old, new) = replacements[m.pattern()]; | |
542 | result.push_str(&contents[pos..m.start()]); | |
543 | result.push_str( | |
544 | if !is_ident_char(contents.as_bytes().get(m.start().wrapping_sub(1)).copied().unwrap_or(0)) | |
545 | && !is_ident_char(contents.as_bytes().get(m.end()).copied().unwrap_or(0)) | |
546 | { | |
547 | edited = true; | |
548 | new | |
549 | } else { | |
550 | old | |
551 | }, | |
552 | ); | |
553 | pos = m.end(); | |
554 | } | |
555 | result.push_str(&contents[pos..]); | |
064997fb | 556 | edited.then_some(result) |
04454e1e FG |
557 | } |
558 | ||
f20569fa XL |
559 | fn round_to_fifty(count: usize) -> usize { |
560 | count / 50 * 50 | |
561 | } | |
c295e0f8 XL |
562 | |
563 | fn process_file(path: impl AsRef<Path>, update_mode: UpdateMode, content: &str) { | |
564 | if update_mode == UpdateMode::Check { | |
565 | let old_content = | |
566 | fs::read_to_string(&path).unwrap_or_else(|e| panic!("Cannot read from {}: {}", path.as_ref().display(), e)); | |
567 | if content != old_content { | |
568 | exit_with_failure(); | |
569 | } | |
570 | } else { | |
571 | fs::write(&path, content.as_bytes()) | |
572 | .unwrap_or_else(|e| panic!("Cannot write to {}: {}", path.as_ref().display(), e)); | |
573 | } | |
574 | } | |
575 | ||
576 | fn exit_with_failure() { | |
577 | println!( | |
578 | "Not all lints defined properly. \ | |
579 | Please run `cargo dev update_lints` to make sure all lints are defined properly." | |
580 | ); | |
581 | std::process::exit(1); | |
582 | } | |
583 | ||
584 | /// Lint data parsed from the Clippy source code. | |
923072b8 | 585 | #[derive(Clone, PartialEq, Eq, Debug)] |
c295e0f8 XL |
586 | struct Lint { |
587 | name: String, | |
588 | group: String, | |
589 | desc: String, | |
c295e0f8 | 590 | module: String, |
064997fb | 591 | declaration_range: Range<usize>, |
c295e0f8 XL |
592 | } |
593 | ||
594 | impl Lint { | |
595 | #[must_use] | |
064997fb | 596 | fn new(name: &str, group: &str, desc: &str, module: &str, declaration_range: Range<usize>) -> Self { |
c295e0f8 XL |
597 | Self { |
598 | name: name.to_lowercase(), | |
04454e1e FG |
599 | group: group.into(), |
600 | desc: remove_line_splices(desc), | |
601 | module: module.into(), | |
064997fb | 602 | declaration_range, |
c295e0f8 XL |
603 | } |
604 | } | |
605 | ||
606 | /// Returns all non-deprecated lints and non-internal lints | |
607 | #[must_use] | |
608 | fn usable_lints(lints: &[Self]) -> Vec<Self> { | |
609 | lints | |
610 | .iter() | |
04454e1e | 611 | .filter(|l| !l.group.starts_with("internal")) |
c295e0f8 XL |
612 | .cloned() |
613 | .collect() | |
614 | } | |
615 | ||
616 | /// Returns all internal lints (not `internal_warn` lints) | |
617 | #[must_use] | |
618 | fn internal_lints(lints: &[Self]) -> Vec<Self> { | |
619 | lints.iter().filter(|l| l.group == "internal").cloned().collect() | |
620 | } | |
621 | ||
c295e0f8 XL |
622 | /// Returns the lints in a `HashMap`, grouped by the different lint groups |
623 | #[must_use] | |
624 | fn by_lint_group(lints: impl Iterator<Item = Self>) -> HashMap<String, Vec<Self>> { | |
625 | lints.map(|lint| (lint.group.to_string(), lint)).into_group_map() | |
626 | } | |
627 | } | |
628 | ||
923072b8 | 629 | #[derive(Clone, PartialEq, Eq, Debug)] |
04454e1e FG |
630 | struct DeprecatedLint { |
631 | name: String, | |
632 | reason: String, | |
064997fb | 633 | declaration_range: Range<usize>, |
04454e1e FG |
634 | } |
635 | impl DeprecatedLint { | |
064997fb | 636 | fn new(name: &str, reason: &str, declaration_range: Range<usize>) -> Self { |
04454e1e FG |
637 | Self { |
638 | name: name.to_lowercase(), | |
639 | reason: remove_line_splices(reason), | |
064997fb | 640 | declaration_range, |
04454e1e FG |
641 | } |
642 | } | |
643 | } | |
644 | ||
645 | struct RenamedLint { | |
646 | old_name: String, | |
647 | new_name: String, | |
648 | } | |
649 | impl RenamedLint { | |
650 | fn new(old_name: &str, new_name: &str) -> Self { | |
651 | Self { | |
652 | old_name: remove_line_splices(old_name), | |
653 | new_name: remove_line_splices(new_name), | |
654 | } | |
655 | } | |
656 | } | |
657 | ||
c295e0f8 XL |
658 | /// Generates the code for registering a group |
659 | fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lint>) -> String { | |
660 | let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect(); | |
661 | details.sort_unstable(); | |
662 | ||
663 | let mut output = GENERATED_FILE_COMMENT.to_string(); | |
664 | ||
04454e1e FG |
665 | let _ = writeln!( |
666 | output, | |
667 | "store.register_group(true, \"clippy::{0}\", Some(\"clippy_{0}\"), vec![", | |
c295e0f8 | 668 | group_name |
04454e1e | 669 | ); |
c295e0f8 | 670 | for (module, name) in details { |
04454e1e | 671 | let _ = writeln!(output, " LintId::of({}::{}),", module, name); |
c295e0f8 XL |
672 | } |
673 | output.push_str("])\n"); | |
674 | ||
675 | output | |
676 | } | |
677 | ||
c295e0f8 XL |
678 | /// Generates the `register_removed` code |
679 | #[must_use] | |
04454e1e | 680 | fn gen_deprecated(lints: &[DeprecatedLint]) -> String { |
c295e0f8 XL |
681 | let mut output = GENERATED_FILE_COMMENT.to_string(); |
682 | output.push_str("{\n"); | |
04454e1e FG |
683 | for lint in lints { |
684 | let _ = write!( | |
685 | output, | |
c295e0f8 XL |
686 | concat!( |
687 | " store.register_removed(\n", | |
688 | " \"clippy::{}\",\n", | |
689 | " \"{}\",\n", | |
690 | " );\n" | |
691 | ), | |
04454e1e FG |
692 | lint.name, lint.reason, |
693 | ); | |
c295e0f8 XL |
694 | } |
695 | output.push_str("}\n"); | |
696 | ||
697 | output | |
698 | } | |
699 | ||
700 | /// Generates the code for registering lints | |
701 | #[must_use] | |
702 | fn gen_register_lint_list<'a>( | |
703 | internal_lints: impl Iterator<Item = &'a Lint>, | |
704 | usable_lints: impl Iterator<Item = &'a Lint>, | |
705 | ) -> String { | |
706 | let mut details: Vec<_> = internal_lints | |
707 | .map(|l| (false, &l.module, l.name.to_uppercase())) | |
708 | .chain(usable_lints.map(|l| (true, &l.module, l.name.to_uppercase()))) | |
709 | .collect(); | |
710 | details.sort_unstable(); | |
711 | ||
712 | let mut output = GENERATED_FILE_COMMENT.to_string(); | |
713 | output.push_str("store.register_lints(&[\n"); | |
714 | ||
715 | for (is_public, module_name, lint_name) in details { | |
716 | if !is_public { | |
5099ac24 | 717 | output.push_str(" #[cfg(feature = \"internal\")]\n"); |
c295e0f8 | 718 | } |
04454e1e | 719 | let _ = writeln!(output, " {}::{},", module_name, lint_name); |
c295e0f8 XL |
720 | } |
721 | output.push_str("])\n"); | |
722 | ||
723 | output | |
724 | } | |
725 | ||
04454e1e FG |
726 | fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String { |
727 | let mut res: String = GENERATED_FILE_COMMENT.into(); | |
728 | for lint in lints { | |
729 | writeln!(res, "#![warn(clippy::{})]", lint.name).unwrap(); | |
730 | } | |
731 | res.push_str("\nfn main() {}\n"); | |
732 | res | |
c295e0f8 XL |
733 | } |
734 | ||
04454e1e FG |
735 | fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String { |
736 | let mut seen_lints = HashSet::new(); | |
737 | let mut res: String = GENERATED_FILE_COMMENT.into(); | |
738 | res.push_str("// run-rustfix\n\n"); | |
739 | for lint in lints { | |
740 | if seen_lints.insert(&lint.new_name) { | |
741 | writeln!(res, "#![allow({})]", lint.new_name).unwrap(); | |
742 | } | |
743 | } | |
744 | seen_lints.clear(); | |
745 | for lint in lints { | |
746 | if seen_lints.insert(&lint.old_name) { | |
747 | writeln!(res, "#![warn({})]", lint.old_name).unwrap(); | |
748 | } | |
c295e0f8 | 749 | } |
04454e1e FG |
750 | res.push_str("\nfn main() {}\n"); |
751 | res | |
752 | } | |
c295e0f8 | 753 | |
04454e1e FG |
754 | fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String { |
755 | const HEADER: &str = "\ | |
756 | // This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\ | |
757 | #[rustfmt::skip]\n\ | |
758 | pub static RENAMED_LINTS: &[(&str, &str)] = &[\n"; | |
c295e0f8 | 759 | |
04454e1e FG |
760 | let mut res = String::from(HEADER); |
761 | for lint in lints { | |
762 | writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap(); | |
763 | } | |
764 | res.push_str("];\n"); | |
765 | res | |
c295e0f8 XL |
766 | } |
767 | ||
04454e1e FG |
768 | /// Gathers all lints defined in `clippy_lints/src` |
769 | fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>) { | |
770 | let mut lints = Vec::with_capacity(1000); | |
771 | let mut deprecated_lints = Vec::with_capacity(50); | |
772 | let mut renamed_lints = Vec::with_capacity(50); | |
773 | ||
774 | for (rel_path, file) in clippy_lints_src_files() { | |
775 | let path = file.path(); | |
776 | let contents = | |
777 | fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e)); | |
778 | let module = rel_path | |
779 | .components() | |
780 | .map(|c| c.as_os_str().to_str().unwrap()) | |
781 | .collect::<Vec<_>>() | |
782 | .join("::"); | |
783 | ||
784 | // If the lints are stored in mod.rs, we get the module name from | |
785 | // the containing directory: | |
786 | let module = if let Some(module) = module.strip_suffix("::mod.rs") { | |
787 | module | |
788 | } else { | |
789 | module.strip_suffix(".rs").unwrap_or(&module) | |
790 | }; | |
791 | ||
792 | match module { | |
793 | "deprecated_lints" => parse_deprecated_contents(&contents, &mut deprecated_lints), | |
794 | "renamed_lints" => parse_renamed_contents(&contents, &mut renamed_lints), | |
795 | _ => parse_contents(&contents, module, &mut lints), | |
796 | } | |
797 | } | |
798 | (lints, deprecated_lints, renamed_lints) | |
c295e0f8 XL |
799 | } |
800 | ||
04454e1e FG |
801 | fn clippy_lints_src_files() -> impl Iterator<Item = (PathBuf, DirEntry)> { |
802 | let root_path = clippy_project_root().join("clippy_lints/src"); | |
803 | let iter = WalkDir::new(&root_path).into_iter(); | |
804 | iter.map(Result::unwrap) | |
c295e0f8 | 805 | .filter(|f| f.path().extension() == Some(OsStr::new("rs"))) |
04454e1e FG |
806 | .map(move |f| (f.path().strip_prefix(&root_path).unwrap().to_path_buf(), f)) |
807 | } | |
808 | ||
809 | macro_rules! match_tokens { | |
810 | ($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => { | |
811 | { | |
064997fb FG |
812 | $($(let $capture =)? if let Some(LintDeclSearchResult { |
813 | token_kind: TokenKind::$token $({$($fields)*})?, | |
814 | content: _x, | |
815 | .. | |
816 | }) = $iter.next() { | |
04454e1e FG |
817 | _x |
818 | } else { | |
819 | continue; | |
820 | };)* | |
821 | #[allow(clippy::unused_unit)] | |
822 | { ($($($capture,)?)*) } | |
823 | } | |
824 | } | |
825 | } | |
826 | ||
064997fb FG |
827 | pub(crate) use match_tokens; |
828 | ||
829 | pub(crate) struct LintDeclSearchResult<'a> { | |
830 | pub token_kind: TokenKind, | |
831 | pub content: &'a str, | |
832 | pub range: Range<usize>, | |
833 | } | |
834 | ||
04454e1e FG |
835 | /// Parse a source file looking for `declare_clippy_lint` macro invocations. |
836 | fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) { | |
837 | let mut offset = 0usize; | |
838 | let mut iter = tokenize(contents).map(|t| { | |
839 | let range = offset..offset + t.len; | |
840 | offset = range.end; | |
064997fb FG |
841 | |
842 | LintDeclSearchResult { | |
843 | token_kind: t.kind, | |
844 | content: &contents[range.clone()], | |
845 | range, | |
846 | } | |
04454e1e FG |
847 | }); |
848 | ||
064997fb FG |
849 | while let Some(LintDeclSearchResult { range, .. }) = iter.find( |
850 | |LintDeclSearchResult { | |
851 | token_kind, content, .. | |
852 | }| token_kind == &TokenKind::Ident && *content == "declare_clippy_lint", | |
853 | ) { | |
854 | let start = range.start; | |
855 | ||
04454e1e FG |
856 | let mut iter = iter |
857 | .by_ref() | |
064997fb | 858 | .filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); |
04454e1e FG |
859 | // matches `!{` |
860 | match_tokens!(iter, Bang OpenBrace); | |
861 | match iter.next() { | |
862 | // #[clippy::version = "version"] pub | |
064997fb FG |
863 | Some(LintDeclSearchResult { |
864 | token_kind: TokenKind::Pound, | |
865 | .. | |
866 | }) => { | |
04454e1e FG |
867 | match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident); |
868 | }, | |
869 | // pub | |
064997fb FG |
870 | Some(LintDeclSearchResult { |
871 | token_kind: TokenKind::Ident, | |
872 | .. | |
873 | }) => (), | |
04454e1e FG |
874 | _ => continue, |
875 | } | |
064997fb | 876 | |
04454e1e FG |
877 | let (name, group, desc) = match_tokens!( |
878 | iter, | |
879 | // LINT_NAME | |
880 | Ident(name) Comma | |
881 | // group, | |
882 | Ident(group) Comma | |
064997fb FG |
883 | // "description" |
884 | Literal{..}(desc) | |
04454e1e | 885 | ); |
064997fb FG |
886 | |
887 | if let Some(LintDeclSearchResult { | |
888 | token_kind: TokenKind::CloseBrace, | |
889 | range, | |
890 | .. | |
891 | }) = iter.next() | |
892 | { | |
893 | lints.push(Lint::new(name, group, desc, module, start..range.end)); | |
894 | } | |
04454e1e FG |
895 | } |
896 | } | |
897 | ||
898 | /// Parse a source file looking for `declare_deprecated_lint` macro invocations. | |
899 | fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) { | |
900 | let mut offset = 0usize; | |
901 | let mut iter = tokenize(contents).map(|t| { | |
902 | let range = offset..offset + t.len; | |
903 | offset = range.end; | |
064997fb FG |
904 | |
905 | LintDeclSearchResult { | |
906 | token_kind: t.kind, | |
907 | content: &contents[range.clone()], | |
908 | range, | |
909 | } | |
04454e1e | 910 | }); |
064997fb FG |
911 | |
912 | while let Some(LintDeclSearchResult { range, .. }) = iter.find( | |
913 | |LintDeclSearchResult { | |
914 | token_kind, content, .. | |
915 | }| token_kind == &TokenKind::Ident && *content == "declare_deprecated_lint", | |
916 | ) { | |
917 | let start = range.start; | |
918 | ||
919 | let mut iter = iter.by_ref().filter(|LintDeclSearchResult { ref token_kind, .. }| { | |
920 | !matches!(token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }) | |
921 | }); | |
04454e1e FG |
922 | let (name, reason) = match_tokens!( |
923 | iter, | |
924 | // !{ | |
925 | Bang OpenBrace | |
926 | // #[clippy::version = "version"] | |
927 | Pound OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket | |
928 | // pub LINT_NAME, | |
929 | Ident Ident(name) Comma | |
930 | // "description" | |
931 | Literal{kind: LiteralKind::Str{..},..}(reason) | |
04454e1e | 932 | ); |
064997fb FG |
933 | |
934 | if let Some(LintDeclSearchResult { | |
935 | token_kind: TokenKind::CloseBrace, | |
936 | range, | |
937 | .. | |
938 | }) = iter.next() | |
939 | { | |
940 | lints.push(DeprecatedLint::new(name, reason, start..range.end)); | |
941 | } | |
04454e1e FG |
942 | } |
943 | } | |
944 | ||
945 | fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) { | |
946 | for line in contents.lines() { | |
947 | let mut offset = 0usize; | |
948 | let mut iter = tokenize(line).map(|t| { | |
949 | let range = offset..offset + t.len; | |
950 | offset = range.end; | |
064997fb FG |
951 | |
952 | LintDeclSearchResult { | |
953 | token_kind: t.kind, | |
954 | content: &line[range.clone()], | |
955 | range, | |
956 | } | |
04454e1e | 957 | }); |
064997fb | 958 | |
04454e1e FG |
959 | let (old_name, new_name) = match_tokens!( |
960 | iter, | |
961 | // ("old_name", | |
962 | Whitespace OpenParen Literal{kind: LiteralKind::Str{..},..}(old_name) Comma | |
963 | // "new_name"), | |
964 | Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma | |
965 | ); | |
966 | lints.push(RenamedLint::new(old_name, new_name)); | |
967 | } | |
c295e0f8 XL |
968 | } |
969 | ||
04454e1e FG |
970 | /// Removes the line splices and surrounding quotes from a string literal |
971 | fn remove_line_splices(s: &str) -> String { | |
972 | let s = s | |
973 | .strip_prefix('r') | |
974 | .unwrap_or(s) | |
975 | .trim_matches('#') | |
976 | .strip_prefix('"') | |
977 | .and_then(|s| s.strip_suffix('"')) | |
978 | .unwrap_or_else(|| panic!("expected quoted string, found `{}`", s)); | |
979 | let mut res = String::with_capacity(s.len()); | |
980 | unescape::unescape_literal(s, unescape::Mode::Str, &mut |range, _| res.push_str(&s[range])); | |
981 | res | |
c295e0f8 XL |
982 | } |
983 | ||
984 | /// Replaces a region in a file delimited by two lines matching regexes. | |
985 | /// | |
986 | /// `path` is the relative path to the file on which you want to perform the replacement. | |
987 | /// | |
988 | /// See `replace_region_in_text` for documentation of the other options. | |
989 | /// | |
990 | /// # Panics | |
991 | /// | |
992 | /// Panics if the path could not read or then written | |
04454e1e FG |
993 | fn replace_region_in_file( |
994 | update_mode: UpdateMode, | |
c295e0f8 XL |
995 | path: &Path, |
996 | start: &str, | |
997 | end: &str, | |
04454e1e FG |
998 | write_replacement: impl FnMut(&mut String), |
999 | ) { | |
1000 | let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e)); | |
1001 | let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) { | |
1002 | Ok(x) => x, | |
1003 | Err(delim) => panic!("Couldn't find `{}` in file `{}`", delim, path.display()), | |
1004 | }; | |
1005 | ||
1006 | match update_mode { | |
1007 | UpdateMode::Check if contents != new_contents => exit_with_failure(), | |
1008 | UpdateMode::Check => (), | |
1009 | UpdateMode::Change => { | |
1010 | if let Err(e) = fs::write(path, new_contents.as_bytes()) { | |
1011 | panic!("Cannot write to `{}`: {}", path.display(), e); | |
c295e0f8 | 1012 | } |
04454e1e | 1013 | }, |
c295e0f8 | 1014 | } |
04454e1e | 1015 | } |
c295e0f8 | 1016 | |
04454e1e FG |
1017 | /// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters |
1018 | /// were found, or the missing delimiter if not. | |
1019 | fn replace_region_in_text<'a>( | |
1020 | text: &str, | |
1021 | start: &'a str, | |
1022 | end: &'a str, | |
1023 | mut write_replacement: impl FnMut(&mut String), | |
1024 | ) -> Result<String, &'a str> { | |
1025 | let (text_start, rest) = text.split_once(start).ok_or(start)?; | |
1026 | let (_, text_end) = rest.split_once(end).ok_or(end)?; | |
1027 | ||
1028 | let mut res = String::with_capacity(text.len() + 4096); | |
1029 | res.push_str(text_start); | |
1030 | res.push_str(start); | |
1031 | write_replacement(&mut res); | |
1032 | res.push_str(end); | |
1033 | res.push_str(text_end); | |
1034 | ||
1035 | Ok(res) | |
c295e0f8 XL |
1036 | } |
1037 | ||
04454e1e FG |
1038 | fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { |
1039 | match fs::OpenOptions::new().create_new(true).write(true).open(new_name) { | |
1040 | Ok(file) => drop(file), | |
1041 | Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false, | |
1042 | Err(e) => panic_file(e, new_name, "create"), | |
1043 | }; | |
1044 | match fs::rename(old_name, new_name) { | |
1045 | Ok(()) => true, | |
1046 | Err(e) => { | |
1047 | drop(fs::remove_file(new_name)); | |
1048 | if e.kind() == io::ErrorKind::NotFound { | |
1049 | false | |
1050 | } else { | |
1051 | panic_file(e, old_name, "rename"); | |
1052 | } | |
1053 | }, | |
1054 | } | |
c295e0f8 XL |
1055 | } |
1056 | ||
04454e1e FG |
1057 | #[allow(clippy::needless_pass_by_value)] |
1058 | fn panic_file(error: io::Error, name: &Path, action: &str) -> ! { | |
1059 | panic!("failed to {} file `{}`: {}", action, name.display(), error) | |
c295e0f8 XL |
1060 | } |
1061 | ||
04454e1e FG |
1062 | fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option<String>) { |
1063 | let mut file = fs::OpenOptions::new() | |
1064 | .write(true) | |
1065 | .read(true) | |
1066 | .open(path) | |
1067 | .unwrap_or_else(|e| panic_file(e, path, "open")); | |
1068 | let mut buf = String::new(); | |
1069 | file.read_to_string(&mut buf) | |
1070 | .unwrap_or_else(|e| panic_file(e, path, "read")); | |
1071 | if let Some(new_contents) = f(&buf) { | |
1072 | file.rewind().unwrap_or_else(|e| panic_file(e, path, "write")); | |
1073 | file.write_all(new_contents.as_bytes()) | |
1074 | .unwrap_or_else(|e| panic_file(e, path, "write")); | |
1075 | file.set_len(new_contents.len() as u64) | |
1076 | .unwrap_or_else(|e| panic_file(e, path, "write")); | |
1077 | } | |
c295e0f8 | 1078 | } |
c295e0f8 | 1079 | |
04454e1e FG |
1080 | fn write_file(path: &Path, contents: &str) { |
1081 | fs::write(path, contents).unwrap_or_else(|e| panic_file(e, path, "write")); | |
c295e0f8 XL |
1082 | } |
1083 | ||
1084 | #[cfg(test)] | |
1085 | mod tests { | |
1086 | use super::*; | |
1087 | ||
1088 | #[test] | |
04454e1e FG |
1089 | fn test_parse_contents() { |
1090 | static CONTENTS: &str = r#" | |
1091 | declare_clippy_lint! { | |
1092 | #[clippy::version = "Hello Clippy!"] | |
1093 | pub PTR_ARG, | |
1094 | style, | |
1095 | "really long \ | |
1096 | text" | |
1097 | } | |
c295e0f8 | 1098 | |
04454e1e FG |
1099 | declare_clippy_lint!{ |
1100 | #[clippy::version = "Test version"] | |
1101 | pub DOC_MARKDOWN, | |
1102 | pedantic, | |
1103 | "single line" | |
1104 | } | |
1105 | "#; | |
1106 | let mut result = Vec::new(); | |
1107 | parse_contents(CONTENTS, "module_name", &mut result); | |
064997fb FG |
1108 | for r in &mut result { |
1109 | r.declaration_range = Range::default(); | |
1110 | } | |
04454e1e FG |
1111 | |
1112 | let expected = vec![ | |
064997fb FG |
1113 | Lint::new( |
1114 | "ptr_arg", | |
1115 | "style", | |
1116 | "\"really long text\"", | |
1117 | "module_name", | |
1118 | Range::default(), | |
1119 | ), | |
1120 | Lint::new( | |
1121 | "doc_markdown", | |
1122 | "pedantic", | |
1123 | "\"single line\"", | |
1124 | "module_name", | |
1125 | Range::default(), | |
1126 | ), | |
04454e1e | 1127 | ]; |
c295e0f8 XL |
1128 | assert_eq!(expected, result); |
1129 | } | |
1130 | ||
1131 | #[test] | |
04454e1e FG |
1132 | fn test_parse_deprecated_contents() { |
1133 | static DEPRECATED_CONTENTS: &str = r#" | |
1134 | /// some doc comment | |
1135 | declare_deprecated_lint! { | |
1136 | #[clippy::version = "I'm a version"] | |
1137 | pub SHOULD_ASSERT_EQ, | |
1138 | "`assert!()` will be more flexible with RFC 2011" | |
1139 | } | |
1140 | "#; | |
1141 | ||
1142 | let mut result = Vec::new(); | |
1143 | parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result); | |
064997fb FG |
1144 | for r in &mut result { |
1145 | r.declaration_range = Range::default(); | |
1146 | } | |
04454e1e FG |
1147 | |
1148 | let expected = vec![DeprecatedLint::new( | |
1149 | "should_assert_eq", | |
1150 | "\"`assert!()` will be more flexible with RFC 2011\"", | |
064997fb | 1151 | Range::default(), |
04454e1e | 1152 | )]; |
c295e0f8 XL |
1153 | assert_eq!(expected, result); |
1154 | } | |
1155 | ||
1156 | #[test] | |
1157 | fn test_usable_lints() { | |
1158 | let lints = vec![ | |
064997fb FG |
1159 | Lint::new( |
1160 | "should_assert_eq2", | |
1161 | "Not Deprecated", | |
1162 | "\"abc\"", | |
1163 | "module_name", | |
1164 | Range::default(), | |
1165 | ), | |
1166 | Lint::new( | |
1167 | "should_assert_eq2", | |
1168 | "internal", | |
1169 | "\"abc\"", | |
1170 | "module_name", | |
1171 | Range::default(), | |
1172 | ), | |
1173 | Lint::new( | |
1174 | "should_assert_eq2", | |
1175 | "internal_style", | |
1176 | "\"abc\"", | |
1177 | "module_name", | |
1178 | Range::default(), | |
1179 | ), | |
c295e0f8 XL |
1180 | ]; |
1181 | let expected = vec![Lint::new( | |
1182 | "should_assert_eq2", | |
1183 | "Not Deprecated", | |
04454e1e | 1184 | "\"abc\"", |
c295e0f8 | 1185 | "module_name", |
064997fb | 1186 | Range::default(), |
c295e0f8 XL |
1187 | )]; |
1188 | assert_eq!(expected, Lint::usable_lints(&lints)); | |
1189 | } | |
1190 | ||
1191 | #[test] | |
1192 | fn test_by_lint_group() { | |
1193 | let lints = vec![ | |
064997fb FG |
1194 | Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), |
1195 | Lint::new( | |
1196 | "should_assert_eq2", | |
1197 | "group2", | |
1198 | "\"abc\"", | |
1199 | "module_name", | |
1200 | Range::default(), | |
1201 | ), | |
1202 | Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()), | |
c295e0f8 XL |
1203 | ]; |
1204 | let mut expected: HashMap<String, Vec<Lint>> = HashMap::new(); | |
1205 | expected.insert( | |
1206 | "group1".to_string(), | |
1207 | vec![ | |
064997fb FG |
1208 | Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), |
1209 | Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()), | |
c295e0f8 XL |
1210 | ], |
1211 | ); | |
1212 | expected.insert( | |
1213 | "group2".to_string(), | |
064997fb FG |
1214 | vec![Lint::new( |
1215 | "should_assert_eq2", | |
1216 | "group2", | |
1217 | "\"abc\"", | |
1218 | "module_name", | |
1219 | Range::default(), | |
1220 | )], | |
c295e0f8 XL |
1221 | ); |
1222 | assert_eq!(expected, Lint::by_lint_group(lints.into_iter())); | |
1223 | } | |
1224 | ||
c295e0f8 XL |
1225 | #[test] |
1226 | fn test_gen_deprecated() { | |
1227 | let lints = vec![ | |
064997fb FG |
1228 | DeprecatedLint::new( |
1229 | "should_assert_eq", | |
1230 | "\"has been superseded by should_assert_eq2\"", | |
1231 | Range::default(), | |
1232 | ), | |
1233 | DeprecatedLint::new("another_deprecated", "\"will be removed\"", Range::default()), | |
c295e0f8 XL |
1234 | ]; |
1235 | ||
1236 | let expected = GENERATED_FILE_COMMENT.to_string() | |
1237 | + &[ | |
1238 | "{", | |
1239 | " store.register_removed(", | |
1240 | " \"clippy::should_assert_eq\",", | |
1241 | " \"has been superseded by should_assert_eq2\",", | |
1242 | " );", | |
1243 | " store.register_removed(", | |
1244 | " \"clippy::another_deprecated\",", | |
1245 | " \"will be removed\",", | |
1246 | " );", | |
1247 | "}", | |
1248 | ] | |
1249 | .join("\n") | |
1250 | + "\n"; | |
1251 | ||
04454e1e | 1252 | assert_eq!(expected, gen_deprecated(&lints)); |
c295e0f8 XL |
1253 | } |
1254 | ||
1255 | #[test] | |
1256 | fn test_gen_lint_group_list() { | |
1257 | let lints = vec![ | |
064997fb FG |
1258 | Lint::new("abc", "group1", "\"abc\"", "module_name", Range::default()), |
1259 | Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), | |
1260 | Lint::new("internal", "internal_style", "\"abc\"", "module_name", Range::default()), | |
c295e0f8 XL |
1261 | ]; |
1262 | let expected = GENERATED_FILE_COMMENT.to_string() | |
1263 | + &[ | |
1264 | "store.register_group(true, \"clippy::group1\", Some(\"clippy_group1\"), vec![", | |
1265 | " LintId::of(module_name::ABC),", | |
1266 | " LintId::of(module_name::INTERNAL),", | |
1267 | " LintId::of(module_name::SHOULD_ASSERT_EQ),", | |
1268 | "])", | |
1269 | ] | |
1270 | .join("\n") | |
1271 | + "\n"; | |
1272 | ||
1273 | let result = gen_lint_group_list("group1", lints.iter()); | |
1274 | ||
1275 | assert_eq!(expected, result); | |
1276 | } | |
1277 | } |