1 use aho_corasick
::AhoCorasickBuilder
;
2 use core
::fmt
::Write
as _
;
3 use itertools
::Itertools
;
4 use rustc_lexer
::{tokenize, unescape, LiteralKind, TokenKind}
;
5 use std
::collections
::{HashMap, HashSet}
;
8 use std
::io
::{self, Read as _, Seek as _, Write as _}
;
9 use std
::path
::{Path, PathBuf}
;
10 use walkdir
::{DirEntry, WalkDir}
;
12 use crate::clippy_project_root
;
14 const GENERATED_FILE_COMMENT
: &str = "// This file was generated by `cargo dev update_lints`.\n\
15 // Use that command to update this file and do not edit by hand.\n\
16 // Manual edits will be overwritten.\n\n";
18 const DOCS_LINK
: &str = "https://rust-lang.github.io/rust-clippy/master/index.html";
20 #[derive(Clone, Copy, PartialEq, Eq)]
26 /// Runs the `update_lints` command.
28 /// This updates various generated values from the lint source code.
30 /// `update_mode` indicates if the files should be updated or if updates should be checked for.
34 /// Panics if a file path could not read from or then written to
35 pub fn update(update_mode
: UpdateMode
) {
36 let (lints
, deprecated_lints
, renamed_lints
) = gather_all();
37 generate_lint_files(update_mode
, &lints
, &deprecated_lints
, &renamed_lints
);
40 fn generate_lint_files(
41 update_mode
: UpdateMode
,
43 deprecated_lints
: &[DeprecatedLint
],
44 renamed_lints
: &[RenamedLint
],
46 let internal_lints
= Lint
::internal_lints(lints
);
47 let usable_lints
= Lint
::usable_lints(lints
);
48 let mut sorted_usable_lints
= usable_lints
.clone();
49 sorted_usable_lints
.sort_by_key(|lint
| lint
.name
.clone());
51 replace_region_in_file(
53 Path
::new("README.md"),
55 " lints included in this crate!]",
57 write
!(res
, "{}", round_to_fifty(usable_lints
.len())).unwrap();
61 replace_region_in_file(
63 Path
::new("book/src/README.md"),
65 " lints included in this crate!]",
67 write
!(res
, "{}", round_to_fifty(usable_lints
.len())).unwrap();
71 replace_region_in_file(
73 Path
::new("CHANGELOG.md"),
74 "<!-- begin autogenerated links to lint list -->\n",
75 "<!-- end autogenerated links to lint list -->",
77 for lint
in usable_lints
80 .chain(deprecated_lints
.iter().map(|l
| &*l
.name
))
84 .map(|l
| l
.old_name
.strip_prefix("clippy::").unwrap_or(&l
.old_name
)),
88 writeln
!(res
, "[`{}`]: {}#{}", lint
, DOCS_LINK
, lint
).unwrap();
93 // This has to be in lib.rs, otherwise rustfmt doesn't work
94 replace_region_in_file(
96 Path
::new("clippy_lints/src/lib.rs"),
97 "// begin lints modules, do not remove this comment, it’s used in `update_lints`\n",
98 "// end lints modules, do not remove this comment, it’s used in `update_lints`",
100 for lint_mod
in usable_lints
.iter().map(|l
| &l
.module
).unique().sorted() {
101 writeln
!(res
, "mod {};", lint_mod
).unwrap();
107 "clippy_lints/src/lib.register_lints.rs",
109 &gen_register_lint_list(internal_lints
.iter(), usable_lints
.iter()),
112 "clippy_lints/src/lib.deprecated.rs",
114 &gen_deprecated(deprecated_lints
),
117 let all_group_lints
= usable_lints
.iter().filter(|l
| {
120 "correctness" | "suspicious" | "style" | "complexity" | "perf"
123 let content
= gen_lint_group_list("all", all_group_lints
);
124 process_file("clippy_lints/src/lib.register_all.rs", update_mode
, &content
);
126 for (lint_group
, lints
) in Lint
::by_lint_group(usable_lints
.into_iter().chain(internal_lints
)) {
127 let content
= gen_lint_group_list(&lint_group
, lints
.iter());
129 &format
!("clippy_lints/src/lib.register_{}.rs", lint_group
),
135 let content
= gen_deprecated_lints_test(deprecated_lints
);
136 process_file("tests/ui/deprecated.rs", update_mode
, &content
);
138 let content
= gen_renamed_lints_test(renamed_lints
);
139 process_file("tests/ui/rename.rs", update_mode
, &content
);
142 pub fn print_lints() {
143 let (lint_list
, _
, _
) = gather_all();
144 let usable_lints
= Lint
::usable_lints(&lint_list
);
145 let usable_lint_count
= usable_lints
.len();
146 let grouped_by_lint_group
= Lint
::by_lint_group(usable_lints
.into_iter());
148 for (lint_group
, mut lints
) in grouped_by_lint_group
{
149 println
!("\n## {}", lint_group
);
151 lints
.sort_by_key(|l
| l
.name
.clone());
154 println
!("* [{}]({}#{}) ({})", lint
.name
, DOCS_LINK
, lint
.name
, lint
.desc
);
158 println
!("there are {} lints", usable_lint_count
);
161 /// Runs the `rename_lint` command.
163 /// This does the following:
164 /// * Adds an entry to `renamed_lints.rs`.
165 /// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`).
166 /// * Renames the lint struct to the new name.
167 /// * Renames the module containing the lint struct to the new name if it shares a name with the
171 /// Panics for the following conditions:
172 /// * If a file path could not read from or then written to
173 /// * If either lint name has a prefix
174 /// * If `old_name` doesn't name an existing lint.
175 /// * If `old_name` names a deprecated or renamed lint.
176 #[allow(clippy::too_many_lines)]
177 pub fn rename(old_name
: &str, new_name
: &str, uplift
: bool
) {
178 if let Some((prefix
, _
)) = old_name
.split_once("::") {
179 panic
!("`{}` should not contain the `{}` prefix", old_name
, prefix
);
181 if let Some((prefix
, _
)) = new_name
.split_once("::") {
182 panic
!("`{}` should not contain the `{}` prefix", new_name
, prefix
);
185 let (mut lints
, deprecated_lints
, mut renamed_lints
) = gather_all();
186 let mut old_lint_index
= None
;
187 let mut found_new_name
= false;
188 for (i
, lint
) in lints
.iter().enumerate() {
189 if lint
.name
== old_name
{
190 old_lint_index
= Some(i
);
191 } else if lint
.name
== new_name
{
192 found_new_name
= true;
195 let old_lint_index
= old_lint_index
.unwrap_or_else(|| panic
!("could not find lint `{}`", old_name
));
197 let lint
= RenamedLint
{
198 old_name
: format
!("clippy::{}", old_name
),
199 new_name
: if uplift
{
202 format
!("clippy::{}", new_name
)
206 // Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in
209 !renamed_lints
.iter().any(|l
| lint
.old_name
== l
.old_name
),
210 "`{}` has already been renamed",
214 !deprecated_lints
.iter().any(|l
| lint
.old_name
== l
.name
),
215 "`{}` has already been deprecated",
219 // Update all lint level attributes. (`clippy::lint_name`)
220 for file
in WalkDir
::new(clippy_project_root())
224 let name
= f
.path().file_name();
225 let ext
= f
.path().extension();
226 (ext
== Some(OsStr
::new("rs")) || ext
== Some(OsStr
::new("fixed")))
227 && name
!= Some(OsStr
::new("rename.rs"))
228 && name
!= Some(OsStr
::new("renamed_lints.rs"))
231 rewrite_file(file
.path(), |s
| {
232 replace_ident_like(s
, &[(&lint
.old_name
, &lint
.new_name
)])
236 renamed_lints
.push(lint
);
237 renamed_lints
.sort_by(|lhs
, rhs
| {
239 .starts_with("clippy::")
240 .cmp(&rhs
.new_name
.starts_with("clippy::"))
242 .then_with(|| lhs
.old_name
.cmp(&rhs
.old_name
))
246 Path
::new("clippy_lints/src/renamed_lints.rs"),
247 &gen_renamed_lints_list(&renamed_lints
),
251 write_file(Path
::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints
));
253 "`{}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually.",
256 } else if found_new_name
{
257 write_file(Path
::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints
));
259 "`{}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually.",
263 // Rename the lint struct and source files sharing a name with the lint.
264 let lint
= &mut lints
[old_lint_index
];
265 let old_name_upper
= old_name
.to_uppercase();
266 let new_name_upper
= new_name
.to_uppercase();
267 lint
.name
= new_name
.into();
269 // Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist.
271 Path
::new(&format
!("tests/ui/{}.rs", old_name
)),
272 Path
::new(&format
!("tests/ui/{}.rs", new_name
)),
275 Path
::new(&format
!("tests/ui/{}.stderr", old_name
)),
276 Path
::new(&format
!("tests/ui/{}.stderr", new_name
)),
279 Path
::new(&format
!("tests/ui/{}.fixed", old_name
)),
280 Path
::new(&format
!("tests/ui/{}.fixed", new_name
)),
284 // Try to rename the file containing the lint if the file name matches the lint's name.
286 let replacements
= if lint
.module
== old_name
288 Path
::new(&format
!("clippy_lints/src/{}.rs", old_name
)),
289 Path
::new(&format
!("clippy_lints/src/{}.rs", new_name
)),
291 // Edit the module name in the lint list. Note there could be multiple lints.
292 for lint
in lints
.iter_mut().filter(|l
| l
.module
== old_name
) {
293 lint
.module
= new_name
.into();
295 replacements
= [(&*old_name_upper
, &*new_name_upper
), (old_name
, new_name
)];
296 replacements
.as_slice()
297 } else if !lint
.module
.contains("::")
298 // Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs`
300 Path
::new(&format
!("clippy_lints/src/{}/{}.rs", lint
.module
, old_name
)),
301 Path
::new(&format
!("clippy_lints/src/{}/{}.rs", lint
.module
, new_name
)),
304 // Edit the module name in the lint list. Note there could be multiple lints, or none.
305 let renamed_mod
= format
!("{}::{}", lint
.module
, old_name
);
306 for lint
in lints
.iter_mut().filter(|l
| l
.module
== renamed_mod
) {
307 lint
.module
= format
!("{}::{}", lint
.module
, new_name
);
309 replacements
= [(&*old_name_upper
, &*new_name_upper
), (old_name
, new_name
)];
310 replacements
.as_slice()
312 replacements
= [(&*old_name_upper
, &*new_name_upper
), ("", "")];
316 // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being
318 for (_
, file
) in clippy_lints_src_files().filter(|(rel_path
, _
)| rel_path
!= OsStr
::new("renamed_lints.rs")) {
319 rewrite_file(file
.path(), |s
| replace_ident_like(s
, replacements
));
322 generate_lint_files(UpdateMode
::Change
, &lints
, &deprecated_lints
, &renamed_lints
);
323 println
!("{} has been successfully renamed", old_name
);
326 println
!("note: `cargo uitest` still needs to be run to update the test results");
329 /// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there
330 /// were no replacements.
331 fn replace_ident_like(contents
: &str, replacements
: &[(&str, &str)]) -> Option
<String
> {
332 fn is_ident_char(c
: u8) -> bool
{
333 matches
!(c
, b'a'
..=b'z'
| b'A'
..=b'Z'
| b'
0'
..=b'
9'
| b'_'
)
336 let searcher
= AhoCorasickBuilder
::new()
338 .match_kind(aho_corasick
::MatchKind
::LeftmostLongest
)
339 .build_with_size
::<u16, _
, _
>(replacements
.iter().map(|&(x
, _
)| x
.as_bytes()))
342 let mut result
= String
::with_capacity(contents
.len() + 1024);
344 let mut edited
= false;
345 for m
in searcher
.find_iter(contents
) {
346 let (old
, new
) = replacements
[m
.pattern()];
347 result
.push_str(&contents
[pos
..m
.start()]);
349 if !is_ident_char(contents
.as_bytes().get(m
.start().wrapping_sub(1)).copied().unwrap_or(0))
350 && !is_ident_char(contents
.as_bytes().get(m
.end()).copied().unwrap_or(0))
360 result
.push_str(&contents
[pos
..]);
361 edited
.then(|| result
)
364 fn round_to_fifty(count
: usize) -> usize {
368 fn process_file(path
: impl AsRef
<Path
>, update_mode
: UpdateMode
, content
: &str) {
369 if update_mode
== UpdateMode
::Check
{
371 fs
::read_to_string(&path
).unwrap_or_else(|e
| panic
!("Cannot read from {}: {}", path
.as_ref().display(), e
));
372 if content
!= old_content
{
376 fs
::write(&path
, content
.as_bytes())
377 .unwrap_or_else(|e
| panic
!("Cannot write to {}: {}", path
.as_ref().display(), e
));
381 fn exit_with_failure() {
383 "Not all lints defined properly. \
384 Please run `cargo dev update_lints` to make sure all lints are defined properly."
386 std
::process
::exit(1);
389 /// Lint data parsed from the Clippy source code.
390 #[derive(Clone, PartialEq, Eq, Debug)]
400 fn new(name
: &str, group
: &str, desc
: &str, module
: &str) -> Self {
402 name
: name
.to_lowercase(),
404 desc
: remove_line_splices(desc
),
405 module
: module
.into(),
409 /// Returns all non-deprecated lints and non-internal lints
411 fn usable_lints(lints
: &[Self]) -> Vec
<Self> {
414 .filter(|l
| !l
.group
.starts_with("internal"))
419 /// Returns all internal lints (not `internal_warn` lints)
421 fn internal_lints(lints
: &[Self]) -> Vec
<Self> {
422 lints
.iter().filter(|l
| l
.group
== "internal").cloned().collect()
425 /// Returns the lints in a `HashMap`, grouped by the different lint groups
427 fn by_lint_group(lints
: impl Iterator
<Item
= Self>) -> HashMap
<String
, Vec
<Self>> {
428 lints
.map(|lint
| (lint
.group
.to_string(), lint
)).into_group_map()
432 #[derive(Clone, PartialEq, Eq, Debug)]
433 struct DeprecatedLint
{
437 impl DeprecatedLint
{
438 fn new(name
: &str, reason
: &str) -> Self {
440 name
: name
.to_lowercase(),
441 reason
: remove_line_splices(reason
),
451 fn new(old_name
: &str, new_name
: &str) -> Self {
453 old_name
: remove_line_splices(old_name
),
454 new_name
: remove_line_splices(new_name
),
459 /// Generates the code for registering a group
460 fn gen_lint_group_list
<'a
>(group_name
: &str, lints
: impl Iterator
<Item
= &'a Lint
>) -> String
{
461 let mut details
: Vec
<_
> = lints
.map(|l
| (&l
.module
, l
.name
.to_uppercase())).collect();
462 details
.sort_unstable();
464 let mut output
= GENERATED_FILE_COMMENT
.to_string();
468 "store.register_group(true, \"clippy::{0}\", Some(\"clippy_{0}\"), vec![",
471 for (module
, name
) in details
{
472 let _
= writeln
!(output
, " LintId::of({}::{}),", module
, name
);
474 output
.push_str("])\n");
479 /// Generates the `register_removed` code
481 fn gen_deprecated(lints
: &[DeprecatedLint
]) -> String
{
482 let mut output
= GENERATED_FILE_COMMENT
.to_string();
483 output
.push_str("{\n");
488 " store.register_removed(\n",
489 " \"clippy::{}\",\n",
493 lint
.name
, lint
.reason
,
496 output
.push_str("}\n");
501 /// Generates the code for registering lints
503 fn gen_register_lint_list
<'a
>(
504 internal_lints
: impl Iterator
<Item
= &'a Lint
>,
505 usable_lints
: impl Iterator
<Item
= &'a Lint
>,
507 let mut details
: Vec
<_
> = internal_lints
508 .map(|l
| (false, &l
.module
, l
.name
.to_uppercase()))
509 .chain(usable_lints
.map(|l
| (true, &l
.module
, l
.name
.to_uppercase())))
511 details
.sort_unstable();
513 let mut output
= GENERATED_FILE_COMMENT
.to_string();
514 output
.push_str("store.register_lints(&[\n");
516 for (is_public
, module_name
, lint_name
) in details
{
518 output
.push_str(" #[cfg(feature = \"internal\")]\n");
520 let _
= writeln
!(output
, " {}::{},", module_name
, lint_name
);
522 output
.push_str("])\n");
527 fn gen_deprecated_lints_test(lints
: &[DeprecatedLint
]) -> String
{
528 let mut res
: String
= GENERATED_FILE_COMMENT
.into();
530 writeln
!(res
, "#![warn(clippy::{})]", lint
.name
).unwrap();
532 res
.push_str("\nfn main() {}\n");
536 fn gen_renamed_lints_test(lints
: &[RenamedLint
]) -> String
{
537 let mut seen_lints
= HashSet
::new();
538 let mut res
: String
= GENERATED_FILE_COMMENT
.into();
539 res
.push_str("// run-rustfix\n\n");
541 if seen_lints
.insert(&lint
.new_name
) {
542 writeln
!(res
, "#![allow({})]", lint
.new_name
).unwrap();
547 if seen_lints
.insert(&lint
.old_name
) {
548 writeln
!(res
, "#![warn({})]", lint
.old_name
).unwrap();
551 res
.push_str("\nfn main() {}\n");
555 fn gen_renamed_lints_list(lints
: &[RenamedLint
]) -> String
{
556 const HEADER
: &str = "\
557 // This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\
559 pub static RENAMED_LINTS: &[(&str, &str)] = &[\n";
561 let mut res
= String
::from(HEADER
);
563 writeln
!(res
, " (\"{}\", \"{}\"),", lint
.old_name
, lint
.new_name
).unwrap();
565 res
.push_str("];\n");
569 /// Gathers all lints defined in `clippy_lints/src`
570 fn gather_all() -> (Vec
<Lint
>, Vec
<DeprecatedLint
>, Vec
<RenamedLint
>) {
571 let mut lints
= Vec
::with_capacity(1000);
572 let mut deprecated_lints
= Vec
::with_capacity(50);
573 let mut renamed_lints
= Vec
::with_capacity(50);
575 for (rel_path
, file
) in clippy_lints_src_files() {
576 let path
= file
.path();
578 fs
::read_to_string(path
).unwrap_or_else(|e
| panic
!("Cannot read from `{}`: {}", path
.display(), e
));
579 let module
= rel_path
581 .map(|c
| c
.as_os_str().to_str().unwrap())
585 // If the lints are stored in mod.rs, we get the module name from
586 // the containing directory:
587 let module
= if let Some(module
) = module
.strip_suffix("::mod.rs") {
590 module
.strip_suffix(".rs").unwrap_or(&module
)
594 "deprecated_lints" => parse_deprecated_contents(&contents
, &mut deprecated_lints
),
595 "renamed_lints" => parse_renamed_contents(&contents
, &mut renamed_lints
),
596 _
=> parse_contents(&contents
, module
, &mut lints
),
599 (lints
, deprecated_lints
, renamed_lints
)
602 fn clippy_lints_src_files() -> impl Iterator
<Item
= (PathBuf
, DirEntry
)> {
603 let root_path
= clippy_project_root().join("clippy_lints/src");
604 let iter
= WalkDir
::new(&root_path
).into_iter();
605 iter
.map(Result
::unwrap
)
606 .filter(|f
| f
.path().extension() == Some(OsStr
::new("rs")))
607 .map(move |f
| (f
.path().strip_prefix(&root_path
).unwrap().to_path_buf(), f
))
610 macro_rules
! match_tokens
{
611 ($iter
:ident
, $
($token
:ident $
({$($fields:tt)*}
)? $
(($capture
:ident
))?
)*) => {
613 $
($
(let $capture
=)?
if let Some((TokenKind
::$token $
({$($fields)*}
)?
, _x
)) = $iter
.next() {
618 #[allow(clippy::unused_unit)]
619 { ($($($capture,)?)*) }
624 /// Parse a source file looking for `declare_clippy_lint` macro invocations.
625 fn parse_contents(contents
: &str, module
: &str, lints
: &mut Vec
<Lint
>) {
626 let mut offset
= 0usize
;
627 let mut iter
= tokenize(contents
).map(|t
| {
628 let range
= offset
..offset
+ t
.len
;
630 (t
.kind
, &contents
[range
])
633 while iter
.any(|(kind
, s
)| kind
== TokenKind
::Ident
&& s
== "declare_clippy_lint") {
636 .filter(|&(kind
, _
)| !matches
!(kind
, TokenKind
::Whitespace
| TokenKind
::LineComment { .. }
));
638 match_tokens
!(iter
, Bang OpenBrace
);
640 // #[clippy::version = "version"] pub
641 Some((TokenKind
::Pound
, _
)) => {
642 match_tokens
!(iter
, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident
);
645 Some((TokenKind
::Ident
, _
)) => (),
648 let (name
, group
, desc
) = match_tokens
!(
655 Literal{..}
(desc
) CloseBrace
657 lints
.push(Lint
::new(name
, group
, desc
, module
));
661 /// Parse a source file looking for `declare_deprecated_lint` macro invocations.
662 fn parse_deprecated_contents(contents
: &str, lints
: &mut Vec
<DeprecatedLint
>) {
663 let mut offset
= 0usize
;
664 let mut iter
= tokenize(contents
).map(|t
| {
665 let range
= offset
..offset
+ t
.len
;
667 (t
.kind
, &contents
[range
])
669 while iter
.any(|(kind
, s
)| kind
== TokenKind
::Ident
&& s
== "declare_deprecated_lint") {
672 .filter(|&(kind
, _
)| !matches
!(kind
, TokenKind
::Whitespace
| TokenKind
::LineComment { .. }
));
673 let (name
, reason
) = match_tokens
!(
677 // #[clippy::version = "version"]
678 Pound OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket
680 Ident
Ident(name
) Comma
682 Literal{kind: LiteralKind::Str{..}
,..}(reason
)
686 lints
.push(DeprecatedLint
::new(name
, reason
));
690 fn parse_renamed_contents(contents
: &str, lints
: &mut Vec
<RenamedLint
>) {
691 for line
in contents
.lines() {
692 let mut offset
= 0usize
;
693 let mut iter
= tokenize(line
).map(|t
| {
694 let range
= offset
..offset
+ t
.len
;
696 (t
.kind
, &line
[range
])
698 let (old_name
, new_name
) = match_tokens
!(
701 Whitespace OpenParen Literal{kind: LiteralKind::Str{..}
,..}(old_name
) Comma
703 Whitespace Literal{kind: LiteralKind::Str{..}
,..}(new_name
) CloseParen Comma
705 lints
.push(RenamedLint
::new(old_name
, new_name
));
709 /// Removes the line splices and surrounding quotes from a string literal
710 fn remove_line_splices(s
: &str) -> String
{
716 .and_then(|s| s.strip_suffix('"'
))
717 .unwrap_or_else(|| panic
!("expected quoted string, found `{}`", s
));
718 let mut res
= String
::with_capacity(s
.len());
719 unescape
::unescape_literal(s
, unescape
::Mode
::Str
, &mut |range
, _
| res
.push_str(&s
[range
]));
723 /// Replaces a region in a file delimited by two lines matching regexes.
725 /// `path` is the relative path to the file on which you want to perform the replacement.
727 /// See `replace_region_in_text` for documentation of the other options.
731 /// Panics if the path could not read or then written
732 fn replace_region_in_file(
733 update_mode
: UpdateMode
,
737 write_replacement
: impl FnMut(&mut String
),
739 let contents
= fs
::read_to_string(path
).unwrap_or_else(|e
| panic
!("Cannot read from `{}`: {}", path
.display(), e
));
740 let new_contents
= match replace_region_in_text(&contents
, start
, end
, write_replacement
) {
742 Err(delim
) => panic
!("Couldn't find `{}` in file `{}`", delim
, path
.display()),
746 UpdateMode
::Check
if contents
!= new_contents
=> exit_with_failure(),
747 UpdateMode
::Check
=> (),
748 UpdateMode
::Change
=> {
749 if let Err(e
) = fs
::write(path
, new_contents
.as_bytes()) {
750 panic
!("Cannot write to `{}`: {}", path
.display(), e
);
756 /// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters
757 /// were found, or the missing delimiter if not.
758 fn replace_region_in_text
<'a
>(
762 mut write_replacement
: impl FnMut(&mut String
),
763 ) -> Result
<String
, &'a
str> {
764 let (text_start
, rest
) = text
.split_once(start
).ok_or(start
)?
;
765 let (_
, text_end
) = rest
.split_once(end
).ok_or(end
)?
;
767 let mut res
= String
::with_capacity(text
.len() + 4096);
768 res
.push_str(text_start
);
770 write_replacement(&mut res
);
772 res
.push_str(text_end
);
777 fn try_rename_file(old_name
: &Path
, new_name
: &Path
) -> bool
{
778 match fs
::OpenOptions
::new().create_new(true).write(true).open(new_name
) {
779 Ok(file
) => drop(file
),
780 Err(e
) if matches
!(e
.kind(), io
::ErrorKind
::AlreadyExists
| io
::ErrorKind
::NotFound
) => return false,
781 Err(e
) => panic_file(e
, new_name
, "create"),
783 match fs
::rename(old_name
, new_name
) {
786 drop(fs
::remove_file(new_name
));
787 if e
.kind() == io
::ErrorKind
::NotFound
{
790 panic_file(e
, old_name
, "rename");
796 #[allow(clippy::needless_pass_by_value)]
797 fn panic_file(error
: io
::Error
, name
: &Path
, action
: &str) -> ! {
798 panic
!("failed to {} file `{}`: {}", action
, name
.display(), error
)
801 fn rewrite_file(path
: &Path
, f
: impl FnOnce(&str) -> Option
<String
>) {
802 let mut file
= fs
::OpenOptions
::new()
806 .unwrap_or_else(|e
| panic_file(e
, path
, "open"));
807 let mut buf
= String
::new();
808 file
.read_to_string(&mut buf
)
809 .unwrap_or_else(|e
| panic_file(e
, path
, "read"));
810 if let Some(new_contents
) = f(&buf
) {
811 file
.rewind().unwrap_or_else(|e
| panic_file(e
, path
, "write"));
812 file
.write_all(new_contents
.as_bytes())
813 .unwrap_or_else(|e
| panic_file(e
, path
, "write"));
814 file
.set_len(new_contents
.len() as u64)
815 .unwrap_or_else(|e
| panic_file(e
, path
, "write"));
819 fn write_file(path
: &Path
, contents
: &str) {
820 fs
::write(path
, contents
).unwrap_or_else(|e
| panic_file(e
, path
, "write"));
828 fn test_parse_contents() {
829 static CONTENTS
: &str = r
#"
830 declare_clippy_lint! {
831 #[clippy::version = "Hello Clippy!"]
838 declare_clippy_lint!{
839 #[clippy::version = "Test version"]
845 let mut result
= Vec
::new();
846 parse_contents(CONTENTS
, "module_name", &mut result
);
849 Lint
::new("ptr_arg", "style", "\"really long text\"", "module_name"),
850 Lint
::new("doc_markdown", "pedantic", "\"single line\"", "module_name"),
852 assert_eq
!(expected
, result
);
856 fn test_parse_deprecated_contents() {
857 static DEPRECATED_CONTENTS
: &str = r
#"
859 declare_deprecated_lint! {
860 #[clippy::version = "I'm a version"]
861 pub SHOULD_ASSERT_EQ,
862 "`assert!()` will be more flexible with RFC 2011"
866 let mut result
= Vec
::new();
867 parse_deprecated_contents(DEPRECATED_CONTENTS
, &mut result
);
869 let expected
= vec
![DeprecatedLint
::new(
871 "\"`assert!()` will be more flexible with RFC 2011\"",
873 assert_eq
!(expected
, result
);
877 fn test_usable_lints() {
879 Lint
::new("should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name"),
880 Lint
::new("should_assert_eq2", "internal", "\"abc\"", "module_name"),
881 Lint
::new("should_assert_eq2", "internal_style", "\"abc\"", "module_name"),
883 let expected
= vec
![Lint
::new(
889 assert_eq
!(expected
, Lint
::usable_lints(&lints
));
893 fn test_by_lint_group() {
895 Lint
::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
896 Lint
::new("should_assert_eq2", "group2", "\"abc\"", "module_name"),
897 Lint
::new("incorrect_match", "group1", "\"abc\"", "module_name"),
899 let mut expected
: HashMap
<String
, Vec
<Lint
>> = HashMap
::new();
901 "group1".to_string(),
903 Lint
::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
904 Lint
::new("incorrect_match", "group1", "\"abc\"", "module_name"),
908 "group2".to_string(),
909 vec
![Lint
::new("should_assert_eq2", "group2", "\"abc\"", "module_name")],
911 assert_eq
!(expected
, Lint
::by_lint_group(lints
.into_iter()));
915 fn test_gen_deprecated() {
917 DeprecatedLint
::new("should_assert_eq", "\"has been superseded by should_assert_eq2\""),
918 DeprecatedLint
::new("another_deprecated", "\"will be removed\""),
921 let expected
= GENERATED_FILE_COMMENT
.to_string()
924 " store.register_removed(",
925 " \"clippy::should_assert_eq\",",
926 " \"has been superseded by should_assert_eq2\",",
928 " store.register_removed(",
929 " \"clippy::another_deprecated\",",
930 " \"will be removed\",",
937 assert_eq
!(expected
, gen_deprecated(&lints
));
941 fn test_gen_lint_group_list() {
943 Lint
::new("abc", "group1", "\"abc\"", "module_name"),
944 Lint
::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
945 Lint
::new("internal", "internal_style", "\"abc\"", "module_name"),
947 let expected
= GENERATED_FILE_COMMENT
.to_string()
949 "store.register_group(true, \"clippy::group1\", Some(\"clippy_group1\"), vec![",
950 " LintId::of(module_name::ABC),",
951 " LintId::of(module_name::INTERNAL),",
952 " LintId::of(module_name::SHOULD_ASSERT_EQ),",
958 let result
= gen_lint_group_list("group1", lints
.iter());
960 assert_eq
!(expected
, result
);