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("CHANGELOG.md"),
64 "<!-- begin autogenerated links to lint list -->\n",
65 "<!-- end autogenerated links to lint list -->",
67 for lint in usable_lints
70 .chain(deprecated_lints.iter().map(|l| &*l.name))
74 .map(|l| l.old_name.strip_prefix("clippy::").unwrap_or(&l.old_name)),
78 writeln!(res, "[`{}`]: {}#{}", lint, DOCS_LINK, lint).unwrap();
83 // This has to be in lib.rs, otherwise rustfmt doesn't work
84 replace_region_in_file(
86 Path::new("clippy_lints/src/lib.rs"),
87 "// begin lints modules, do not remove this comment, it’s used in `update_lints`\n",
88 "// end lints modules, do not remove this comment, it’s used in `update_lints`",
90 for lint_mod in usable_lints.iter().map(|l| &l.module).unique().sorted() {
91 writeln!(res, "mod {};", lint_mod).unwrap();
97 "clippy_lints/src/lib.register_lints.rs",
99 &gen_register_lint_list(internal_lints.iter(), usable_lints.iter()),
102 "clippy_lints/src/lib.deprecated.rs",
104 &gen_deprecated(deprecated_lints),
107 let all_group_lints = usable_lints.iter().filter(|l| {
110 "correctness" | "suspicious" | "style" | "complexity" | "perf"
113 let content = gen_lint_group_list("all", all_group_lints);
114 process_file("clippy_lints/src/lib.register_all.rs", update_mode, &content);
116 for (lint_group, lints) in Lint::by_lint_group(usable_lints.into_iter().chain(internal_lints)) {
117 let content = gen_lint_group_list(&lint_group, lints.iter());
119 &format!("clippy_lints/src/lib.register_{}.rs", lint_group),
125 let content = gen_deprecated_lints_test(deprecated_lints);
126 process_file("tests/ui/deprecated.rs", update_mode, &content);
128 let content = gen_renamed_lints_test(renamed_lints);
129 process_file("tests/ui/rename.rs", update_mode, &content);
132 pub fn print_lints() {
133 let (lint_list, _, _) = gather_all();
134 let usable_lints = Lint::usable_lints(&lint_list);
135 let usable_lint_count = usable_lints.len();
136 let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter());
138 for (lint_group, mut lints) in grouped_by_lint_group {
139 println!("\n## {}", lint_group);
141 lints.sort_by_key(|l| l.name.clone());
144 println!("* [{}]({}#{}) ({})", lint.name, DOCS_LINK, lint.name, lint.desc);
148 println!("there are {} lints", usable_lint_count);
151 /// Runs the `rename_lint` command.
153 /// This does the following:
154 /// * Adds an entry to `renamed_lints.rs`.
155 /// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`).
156 /// * Renames the lint struct to the new name.
157 /// * Renames the module containing the lint struct to the new name if it shares a name with the
161 /// Panics for the following conditions:
162 /// * If a file path could not read from or then written to
163 /// * If either lint name has a prefix
164 /// * If `old_name` doesn't name an existing lint.
165 /// * If `old_name` names a deprecated or renamed lint.
166 #[allow(clippy::too_many_lines)]
167 pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
168 if let Some((prefix, _)) = old_name.split_once("::") {
169 panic!("`{}` should not contain the `{}` prefix", old_name, prefix);
171 if let Some((prefix, _)) = new_name.split_once("::") {
172 panic!("`{}` should not contain the `{}` prefix", new_name, prefix);
175 let (mut lints, deprecated_lints, mut renamed_lints) = gather_all();
176 let mut old_lint_index = None;
177 let mut found_new_name = false;
178 for (i, lint) in lints.iter().enumerate() {
179 if lint.name == old_name {
180 old_lint_index = Some(i);
181 } else if lint.name == new_name {
182 found_new_name = true;
185 let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{}`", old_name));
187 let lint = RenamedLint {
188 old_name: format!("clippy::{}", old_name),
189 new_name: if uplift {
192 format!("clippy::{}", new_name)
196 // Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in
199 !renamed_lints.iter().any(|l| lint.old_name == l.old_name),
200 "`{}` has already been renamed",
204 !deprecated_lints.iter().any(|l| lint.old_name == l.name),
205 "`{}` has already been deprecated",
209 // Update all lint level attributes. (`clippy::lint_name`)
210 for file in WalkDir::new(clippy_project_root())
214 let name = f.path().file_name();
215 let ext = f.path().extension();
216 (ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed")))
217 && name != Some(OsStr::new("rename.rs"))
218 && name != Some(OsStr::new("renamed_lints.rs"))
221 rewrite_file(file.path(), |s| {
222 replace_ident_like(s, &[(&lint.old_name, &lint.new_name)])
226 renamed_lints.push(lint);
227 renamed_lints.sort_by(|lhs, rhs| {
229 .starts_with("clippy::")
230 .cmp(&rhs.new_name.starts_with("clippy::"))
232 .then_with(|| lhs.old_name.cmp(&rhs.old_name))
236 Path::new("clippy_lints/src/renamed_lints.rs"),
237 &gen_renamed_lints_list(&renamed_lints),
241 write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
243 "`{}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually.",
246 } else if found_new_name {
247 write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
249 "`{}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually.",
253 // Rename the lint struct and source files sharing a name with the lint.
254 let lint = &mut lints[old_lint_index];
255 let old_name_upper = old_name.to_uppercase();
256 let new_name_upper = new_name.to_uppercase();
257 lint.name = new_name.into();
259 // Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist.
261 Path::new(&format!("tests/ui/{}.rs", old_name)),
262 Path::new(&format!("tests/ui/{}.rs", new_name)),
265 Path::new(&format!("tests/ui/{}.stderr", old_name)),
266 Path::new(&format!("tests/ui/{}.stderr", new_name)),
269 Path::new(&format!("tests/ui/{}.fixed", old_name)),
270 Path::new(&format!("tests/ui/{}.fixed", new_name)),
274 // Try to rename the file containing the lint if the file name matches the lint's name.
276 let replacements = if lint.module == old_name
278 Path::new(&format!("clippy_lints/src/{}.rs", old_name)),
279 Path::new(&format!("clippy_lints/src/{}.rs", new_name)),
281 // Edit the module name in the lint list. Note there could be multiple lints.
282 for lint in lints.iter_mut().filter(|l| l.module == old_name) {
283 lint.module = new_name.into();
285 replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
286 replacements.as_slice()
287 } else if !lint.module.contains("::")
288 // Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs`
290 Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, old_name)),
291 Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, new_name)),
294 // Edit the module name in the lint list. Note there could be multiple lints, or none.
295 let renamed_mod = format!("{}::{}", lint.module, old_name);
296 for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) {
297 lint.module = format!("{}::{}", lint.module, new_name);
299 replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
300 replacements.as_slice()
302 replacements = [(&*old_name_upper, &*new_name_upper), ("", "")];
306 // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being
308 for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("renamed_lints.rs")) {
309 rewrite_file(file.path(), |s| replace_ident_like(s, replacements));
312 generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
313 println!("{} has been successfully renamed", old_name);
316 println!("note: `cargo uitest` still needs to be run to update the test results");
319 /// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there
320 /// were no replacements.
321 fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> {
322 fn is_ident_char(c: u8) -> bool {
323 matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')
326 let searcher = AhoCorasickBuilder::new()
328 .match_kind(aho_corasick::MatchKind::LeftmostLongest)
329 .build_with_size::<u16, _, _>(replacements.iter().map(|&(x, _)| x.as_bytes()))
332 let mut result = String::with_capacity(contents.len() + 1024);
334 let mut edited = false;
335 for m in searcher.find_iter(contents) {
336 let (old, new) = replacements[m.pattern()];
337 result.push_str(&contents[pos..m.start()]);
339 if !is_ident_char(contents.as_bytes().get(m.start().wrapping_sub(1)).copied().unwrap_or(0))
340 && !is_ident_char(contents.as_bytes().get(m.end()).copied().unwrap_or(0))
350 result.push_str(&contents[pos..]);
351 edited.then(|| result)
354 fn round_to_fifty(count: usize) -> usize {
358 fn process_file(path: impl AsRef<Path>, update_mode: UpdateMode, content: &str) {
359 if update_mode == UpdateMode::Check {
361 fs::read_to_string(&path).unwrap_or_else(|e| panic!("Cannot read from {}: {}", path.as_ref().display(), e));
362 if content != old_content {
366 fs::write(&path, content.as_bytes())
367 .unwrap_or_else(|e| panic!("Cannot write to {}: {}", path.as_ref().display(), e));
371 fn exit_with_failure() {
373 "Not all lints defined properly. \
374 Please run `cargo dev update_lints` to make sure all lints are defined properly."
376 std::process::exit(1);
379 /// Lint data parsed from the Clippy source code.
380 #[derive(Clone, PartialEq, Eq, Debug)]
390 fn new(name: &str, group: &str, desc: &str, module: &str) -> Self {
392 name: name.to_lowercase(),
394 desc: remove_line_splices(desc),
395 module: module.into(),
399 /// Returns all non-deprecated lints and non-internal lints
401 fn usable_lints(lints: &[Self]) -> Vec<Self> {
404 .filter(|l| !l.group.starts_with("internal"))
409 /// Returns all internal lints (not `internal_warn` lints)
411 fn internal_lints(lints: &[Self]) -> Vec<Self> {
412 lints.iter().filter(|l| l.group == "internal").cloned().collect()
415 /// Returns the lints in a `HashMap`, grouped by the different lint groups
417 fn by_lint_group(lints: impl Iterator<Item = Self>) -> HashMap<String, Vec<Self>> {
418 lints.map(|lint| (lint.group.to_string(), lint)).into_group_map()
422 #[derive(Clone, PartialEq, Eq, Debug)]
423 struct DeprecatedLint {
427 impl DeprecatedLint {
428 fn new(name: &str, reason: &str) -> Self {
430 name: name.to_lowercase(),
431 reason: remove_line_splices(reason),
441 fn new(old_name: &str, new_name: &str) -> Self {
443 old_name: remove_line_splices(old_name),
444 new_name: remove_line_splices(new_name),
449 /// Generates the code for registering a group
450 fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lint>) -> String {
451 let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect();
452 details.sort_unstable();
454 let mut output = GENERATED_FILE_COMMENT.to_string();
458 "store.register_group(true, \"clippy::{0}\", Some(\"clippy_{0}\"), vec![",
461 for (module, name) in details {
462 let _ = writeln!(output, " LintId::of({}::{}),", module, name);
464 output.push_str("])\n");
469 /// Generates the `register_removed` code
471 fn gen_deprecated(lints: &[DeprecatedLint]) -> String {
472 let mut output = GENERATED_FILE_COMMENT.to_string();
473 output.push_str("{\n");
478 " store.register_removed(\n",
479 " \"clippy::{}\",\n",
483 lint.name, lint.reason,
486 output.push_str("}\n");
491 /// Generates the code for registering lints
493 fn gen_register_lint_list<'a>(
494 internal_lints: impl Iterator<Item = &'a Lint>,
495 usable_lints: impl Iterator<Item = &'a Lint>,
497 let mut details: Vec<_> = internal_lints
498 .map(|l| (false, &l.module, l.name.to_uppercase()))
499 .chain(usable_lints.map(|l| (true, &l.module, l.name.to_uppercase())))
501 details.sort_unstable();
503 let mut output = GENERATED_FILE_COMMENT.to_string();
504 output.push_str("store.register_lints(&[\n");
506 for (is_public, module_name, lint_name) in details {
508 output.push_str(" #[cfg(feature = \"internal\")]\n");
510 let _ = writeln!(output, " {}::{},", module_name, lint_name);
512 output.push_str("])\n");
517 fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String {
518 let mut res: String = GENERATED_FILE_COMMENT.into();
520 writeln!(res, "#![warn(clippy::{})]", lint.name).unwrap();
522 res.push_str("\nfn main() {}\n");
526 fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String {
527 let mut seen_lints = HashSet::new();
528 let mut res: String = GENERATED_FILE_COMMENT.into();
529 res.push_str("// run-rustfix\n\n");
531 if seen_lints.insert(&lint.new_name) {
532 writeln!(res, "#![allow({})]", lint.new_name).unwrap();
537 if seen_lints.insert(&lint.old_name) {
538 writeln!(res, "#![warn({})]", lint.old_name).unwrap();
541 res.push_str("\nfn main() {}\n");
545 fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String {
546 const HEADER: &str = "\
547 // This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\
549 pub static RENAMED_LINTS: &[(&str, &str)] = &[\n";
551 let mut res = String::from(HEADER);
553 writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap();
555 res.push_str("];\n");
559 /// Gathers all lints defined in `clippy_lints/src`
560 fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>) {
561 let mut lints = Vec::with_capacity(1000);
562 let mut deprecated_lints = Vec::with_capacity(50);
563 let mut renamed_lints = Vec::with_capacity(50);
565 for (rel_path, file) in clippy_lints_src_files() {
566 let path = file.path();
568 fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
569 let module = rel_path
571 .map(|c| c.as_os_str().to_str().unwrap())
575 // If the lints are stored in mod.rs, we get the module name from
576 // the containing directory:
577 let module = if let Some(module) = module.strip_suffix("::mod.rs") {
580 module.strip_suffix(".rs").unwrap_or(&module)
584 "deprecated_lints" => parse_deprecated_contents(&contents, &mut deprecated_lints),
585 "renamed_lints" => parse_renamed_contents(&contents, &mut renamed_lints),
586 _ => parse_contents(&contents, module, &mut lints),
589 (lints, deprecated_lints, renamed_lints)
592 fn clippy_lints_src_files() -> impl Iterator<Item = (PathBuf, DirEntry)> {
593 let root_path = clippy_project_root().join("clippy_lints/src");
594 let iter = WalkDir::new(&root_path).into_iter();
595 iter.map(Result::unwrap)
596 .filter(|f| f.path().extension() == Some(OsStr::new("rs")))
597 .map(move |f| (f.path().strip_prefix(&root_path).unwrap().to_path_buf(), f))
600 macro_rules! match_tokens {
601 ($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => {
603 $($(let $capture =)? if let Some((TokenKind::$token $({$($fields)*})?, _x)) = $iter.next() {
608 #[allow(clippy::unused_unit)]
609 { ($($($capture,)?)*) }
614 /// Parse a source file looking for `declare_clippy_lint` macro invocations.
615 fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
616 let mut offset = 0usize;
617 let mut iter = tokenize(contents).map(|t| {
618 let range = offset..offset + t.len;
620 (t.kind, &contents[range])
623 while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_clippy_lint") {
626 .filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
628 match_tokens!(iter, Bang OpenBrace);
630 // #[clippy::version = "version"] pub
631 Some((TokenKind::Pound, _)) => {
632 match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident);
635 Some((TokenKind::Ident, _)) => (),
638 let (name, group, desc) = match_tokens!(
645 Literal{..}(desc) CloseBrace
647 lints.push(Lint::new(name, group, desc, module));
651 /// Parse a source file looking for `declare_deprecated_lint` macro invocations.
652 fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
653 let mut offset = 0usize;
654 let mut iter = tokenize(contents).map(|t| {
655 let range = offset..offset + t.len;
657 (t.kind, &contents[range])
659 while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_deprecated_lint") {
662 .filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
663 let (name, reason) = match_tokens!(
667 // #[clippy::version = "version"]
668 Pound OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket
670 Ident Ident(name) Comma
672 Literal{kind: LiteralKind::Str{..},..}(reason)
676 lints.push(DeprecatedLint::new(name, reason));
680 fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) {
681 for line in contents.lines() {
682 let mut offset = 0usize;
683 let mut iter = tokenize(line).map(|t| {
684 let range = offset..offset + t.len;
686 (t.kind, &line[range])
688 let (old_name, new_name) = match_tokens!(
691 Whitespace OpenParen Literal{kind: LiteralKind::Str{..},..}(old_name) Comma
693 Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma
695 lints.push(RenamedLint::new(old_name, new_name));
699 /// Removes the line splices and surrounding quotes from a string literal
700 fn remove_line_splices(s: &str) -> String {
706 .and_then(|s| s.strip_suffix('"'))
707 .unwrap_or_else(|| panic!("expected quoted string, found `{}`", s));
708 let mut res = String::with_capacity(s.len());
709 unescape::unescape_literal(s, unescape::Mode::Str, &mut |range, _| res.push_str(&s[range]));
713 /// Replaces a region in a file delimited by two lines matching regexes.
715 /// `path` is the relative path to the file on which you want to perform the replacement.
717 /// See `replace_region_in_text` for documentation of the other options.
721 /// Panics if the path could not read or then written
722 fn replace_region_in_file(
723 update_mode: UpdateMode,
727 write_replacement: impl FnMut(&mut String),
729 let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
730 let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) {
732 Err(delim) => panic!("Couldn't find `{}` in file `{}`", delim, path.display()),
736 UpdateMode::Check if contents != new_contents => exit_with_failure(),
737 UpdateMode::Check => (),
738 UpdateMode::Change => {
739 if let Err(e) = fs::write(path, new_contents.as_bytes()) {
740 panic!("Cannot write to `{}`: {}", path.display(), e);
746 /// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters
747 /// were found, or the missing delimiter if not.
748 fn replace_region_in_text<'a>(
752 mut write_replacement: impl FnMut(&mut String),
753 ) -> Result<String, &'a str> {
754 let (text_start, rest) = text.split_once(start).ok_or(start)?;
755 let (_, text_end) = rest.split_once(end).ok_or(end)?;
757 let mut res = String::with_capacity(text.len() + 4096);
758 res.push_str(text_start);
760 write_replacement(&mut res);
762 res.push_str(text_end);
767 fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
768 match fs::OpenOptions::new().create_new(true).write(true).open(new_name) {
769 Ok(file) => drop(file),
770 Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false,
771 Err(e) => panic_file(e, new_name, "create"),
773 match fs::rename(old_name, new_name) {
776 drop(fs::remove_file(new_name));
777 if e.kind() == io::ErrorKind::NotFound {
780 panic_file(e, old_name, "rename");
786 #[allow(clippy::needless_pass_by_value)]
787 fn panic_file(error: io::Error, name: &Path, action: &str) -> ! {
788 panic!("failed to {} file `{}`: {}", action, name.display(), error)
791 fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option<String>) {
792 let mut file = fs::OpenOptions::new()
796 .unwrap_or_else(|e| panic_file(e, path, "open"));
797 let mut buf = String::new();
798 file.read_to_string(&mut buf)
799 .unwrap_or_else(|e| panic_file(e, path, "read"));
800 if let Some(new_contents) = f(&buf) {
801 file.rewind().unwrap_or_else(|e| panic_file(e, path, "write"));
802 file.write_all(new_contents.as_bytes())
803 .unwrap_or_else(|e| panic_file(e, path, "write"));
804 file.set_len(new_contents.len() as u64)
805 .unwrap_or_else(|e| panic_file(e, path, "write"));
809 fn write_file(path: &Path, contents: &str) {
810 fs::write(path, contents).unwrap_or_else(|e| panic_file(e, path, "write"));
818 fn test_parse_contents() {
819 static CONTENTS: &str = r#"
820 declare_clippy_lint! {
821 #[clippy::version = "Hello Clippy!"]
828 declare_clippy_lint!{
829 #[clippy::version = "Test version"]
835 let mut result = Vec::new();
836 parse_contents(CONTENTS, "module_name", &mut result);
839 Lint::new("ptr_arg", "style", "\"really long text\"", "module_name"),
840 Lint::new("doc_markdown", "pedantic", "\"single line\"", "module_name"),
842 assert_eq!(expected, result);
846 fn test_parse_deprecated_contents() {
847 static DEPRECATED_CONTENTS: &str = r#"
849 declare_deprecated_lint! {
850 #[clippy::version = "I'm a version"]
851 pub SHOULD_ASSERT_EQ,
852 "`assert!()` will be more flexible with RFC 2011"
856 let mut result = Vec::new();
857 parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result);
859 let expected = vec![DeprecatedLint::new(
861 "\"`assert!()` will be more flexible with RFC 2011\"",
863 assert_eq!(expected, result);
867 fn test_usable_lints() {
869 Lint::new("should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name"),
870 Lint::new("should_assert_eq2", "internal", "\"abc\"", "module_name"),
871 Lint::new("should_assert_eq2", "internal_style", "\"abc\"", "module_name"),
873 let expected = vec![Lint::new(
879 assert_eq!(expected, Lint::usable_lints(&lints));
883 fn test_by_lint_group() {
885 Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
886 Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name"),
887 Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"),
889 let mut expected: HashMap<String, Vec<Lint>> = HashMap::new();
891 "group1".to_string(),
893 Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
894 Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"),
898 "group2".to_string(),
899 vec![Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name")],
901 assert_eq!(expected, Lint::by_lint_group(lints.into_iter()));
905 fn test_gen_deprecated() {
907 DeprecatedLint::new("should_assert_eq", "\"has been superseded by should_assert_eq2\""),
908 DeprecatedLint::new("another_deprecated", "\"will be removed\""),
911 let expected = GENERATED_FILE_COMMENT.to_string()
914 " store.register_removed(",
915 " \"clippy::should_assert_eq\",",
916 " \"has been superseded by should_assert_eq2\",",
918 " store.register_removed(",
919 " \"clippy::another_deprecated\",",
920 " \"will be removed\",",
927 assert_eq!(expected, gen_deprecated(&lints));
931 fn test_gen_lint_group_list() {
933 Lint::new("abc", "group1", "\"abc\"", "module_name"),
934 Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
935 Lint::new("internal", "internal_style", "\"abc\"", "module_name"),
937 let expected = GENERATED_FILE_COMMENT.to_string()
939 "store.register_group(true, \"clippy::group1\", Some(\"clippy_group1\"), vec![",
940 " LintId::of(module_name::ABC),",
941 " LintId::of(module_name::INTERNAL),",
942 " LintId::of(module_name::SHOULD_ASSERT_EQ),",
948 let result = gen_lint_group_list("group1", lints.iter());
950 assert_eq!(expected, result);