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);