pub mod cursor; use self::cursor::Cursor; use crate::utils::{ErrAction, File, expect_action}; use core::ops::Range; use std::fs; use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; pub struct Lint { pub name: String, pub group: String, pub module: String, pub path: PathBuf, pub declaration_range: Range, } pub struct DeprecatedLint { pub name: String, pub reason: String, pub version: String, } pub struct RenamedLint { pub old_name: String, pub new_name: String, pub version: String, } /// Finds all lint declarations (`declare_clippy_lint!`) #[must_use] pub fn find_lint_decls() -> Vec { let mut lints = Vec::with_capacity(1000); let mut contents = String::new(); for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") { let e = expect_action(e, ErrAction::Read, "."); if !expect_action(e.file_type(), ErrAction::Read, ".").is_dir() { continue; } let Ok(mut name) = e.file_name().into_string() else { continue; }; if name.starts_with("clippy_lints") && name != "clippy_lints_internal" { name.push_str("/src"); for (file, module) in read_src_with_module(name.as_ref()) { parse_clippy_lint_decls( file.path(), File::open_read_to_cleared_string(file.path(), &mut contents), &module, &mut lints, ); } } } lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); lints } /// Reads the source files from the given root directory fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator { WalkDir::new(src_root).into_iter().filter_map(move |e| { let e = expect_action(e, ErrAction::Read, src_root); let path = e.path().as_os_str().as_encoded_bytes(); if let Some(path) = path.strip_suffix(b".rs") && let Some(path) = path.get(src_root.as_os_str().len() + 1..) { if path == b"lib" { Some((e, String::new())) } else { let path = if let Some(path) = path.strip_suffix(b"mod") && let Some(path) = path.strip_suffix(b"/").or_else(|| path.strip_suffix(b"\\")) { path } else { path }; if let Ok(path) = str::from_utf8(path) { let path = path.replace(['/', '\\'], "::"); Some((e, path)) } else { None } } } else { None } }) } /// Parse a source file looking for `declare_clippy_lint` macro invocations. fn parse_clippy_lint_decls(path: &Path, contents: &str, module: &str, lints: &mut Vec) { #[allow(clippy::enum_glob_use)] use cursor::Pat::*; #[rustfmt::skip] static DECL_TOKENS: &[cursor::Pat<'_>] = &[ // !{ /// docs Bang, OpenBrace, AnyComment, // #[clippy::version = "version"] Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket, // pub NAME, GROUP, Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, ]; let mut cursor = Cursor::new(contents); while cursor.find_pat(Ident("declare_clippy_lint")) { let start = cursor.pos() as usize - "declare_clippy_lint".len(); let (mut name, mut group) = ("", ""); if cursor.match_all(DECL_TOKENS, &mut [&mut name, &mut group]) && cursor.find_pat(CloseBrace) { lints.push(Lint { name: name.to_lowercase(), group: group.into(), module: module.into(), path: path.into(), declaration_range: start..cursor.pos() as usize, }); } } } #[must_use] pub fn read_deprecated_lints() -> (Vec, Vec) { #[allow(clippy::enum_glob_use)] use cursor::Pat::*; #[rustfmt::skip] static DECL_TOKENS: &[cursor::Pat<'_>] = &[ // #[clippy::version = "version"] Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket, // ("first", "second"), OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma, ]; #[rustfmt::skip] static DEPRECATED_TOKENS: &[cursor::Pat<'_>] = &[ // !{ DEPRECATED(DEPRECATED_VERSION) = [ Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket, ]; #[rustfmt::skip] static RENAMED_TOKENS: &[cursor::Pat<'_>] = &[ // !{ RENAMED(RENAMED_VERSION) = [ Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket, ]; let path = "clippy_lints/src/deprecated_lints.rs"; let mut deprecated = Vec::with_capacity(30); let mut renamed = Vec::with_capacity(80); let mut contents = String::new(); File::open_read_to_cleared_string(path, &mut contents); let mut cursor = Cursor::new(&contents); // First instance is the macro definition. assert!( cursor.find_pat(Ident("declare_with_version")), "error reading deprecated lints" ); if cursor.find_pat(Ident("declare_with_version")) && cursor.match_all(DEPRECATED_TOKENS, &mut []) { let mut version = ""; let mut name = ""; let mut reason = ""; while cursor.match_all(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) { deprecated.push(DeprecatedLint { name: parse_str_single_line(path.as_ref(), name), reason: parse_str_single_line(path.as_ref(), reason), version: parse_str_single_line(path.as_ref(), version), }); } } else { panic!("error reading deprecated lints"); } if cursor.find_pat(Ident("declare_with_version")) && cursor.match_all(RENAMED_TOKENS, &mut []) { let mut version = ""; let mut old_name = ""; let mut new_name = ""; while cursor.match_all(DECL_TOKENS, &mut [&mut version, &mut old_name, &mut new_name]) { renamed.push(RenamedLint { old_name: parse_str_single_line(path.as_ref(), old_name), new_name: parse_str_single_line(path.as_ref(), new_name), version: parse_str_single_line(path.as_ref(), version), }); } } else { panic!("error reading renamed lints"); } deprecated.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(&rhs.old_name)); (deprecated, renamed) } /// Removes the line splices and surrounding quotes from a string literal fn parse_str_lit(s: &str) -> String { let (s, is_raw) = if let Some(s) = s.strip_prefix("r") { (s.trim_matches('#'), true) } else { (s, false) }; let s = s .strip_prefix('"') .and_then(|s| s.strip_suffix('"')) .unwrap_or_else(|| panic!("expected quoted string, found `{s}`")); if is_raw { s.into() } else { let mut res = String::with_capacity(s.len()); rustc_literal_escaper::unescape_str(s, &mut |_, ch| { if let Ok(ch) = ch { res.push(ch); } }); res } } fn parse_str_single_line(path: &Path, s: &str) -> String { let value = parse_str_lit(s); assert!( !value.contains('\n'), "error parsing `{}`: `{s}` should be a single line string", path.display(), ); value }