diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index 10c08dba50b9..4d5ceb8211f7 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -9,6 +9,7 @@ chrono = { version = "0.4.38", default-features = false, features = ["clock"] } clap = { version = "4.4", features = ["derive"] } indoc = "1.0" itertools = "0.12" +memchr = "2.7.5" opener = "0.7" walkdir = "2.3" diff --git a/clippy_dev/src/deprecate_lint.rs b/clippy_dev/src/deprecate_lint.rs index 3bdc5b277232..b845c033d94f 100644 --- a/clippy_dev/src/deprecate_lint.rs +++ b/clippy_dev/src/deprecate_lint.rs @@ -1,8 +1,9 @@ -use crate::update_lints::{DeprecatedLint, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints}; -use crate::utils::{UpdateMode, Version}; +use crate::parse::{ActiveLint, DeprecatedLint, LintKind, ParsedData}; +use crate::update_lints::generate_lint_files; +use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists}; +use core::mem; use std::ffi::OsStr; -use std::path::{Path, PathBuf}; -use std::{fs, io}; +use std::path::Path; /// Runs the `deprecate` command /// @@ -18,68 +19,42 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { panic!("`{name}` should not contain the `{prefix}` prefix"); } - let mut lints = find_lint_decls(); - let (mut deprecated_lints, renamed_lints) = read_deprecated_lints(); - - let Some(lint) = lints.iter().find(|l| l.name == name) else { + let mut data = ParsedData::collect(); + let Some(entry) = data.lints.get_mut(name) else { eprintln!("error: failed to find lint `{name}`"); return; }; - - let prefixed_name = String::from_iter(["clippy::", name]); - match deprecated_lints.binary_search_by(|x| x.name.cmp(&prefixed_name)) { - Ok(_) => { - println!("`{name}` is already deprecated"); - return; - }, - Err(idx) => deprecated_lints.insert( - idx, - DeprecatedLint { - name: prefixed_name, - reason: reason.into(), - version: clippy_version.rust_display().to_string(), - }, - ), - } - - let mod_path = { - let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module)); - if mod_path.is_dir() { - mod_path = mod_path.join("mod"); - } - - mod_path.set_extension("rs"); - mod_path + let LintKind::Active(lint) = mem::replace( + &mut entry.kind, + LintKind::Deprecated(DeprecatedLint { + reason: reason.into(), + version: clippy_version.rust_display().to_string(), + }), + ) else { + eprintln!("error: lint `{name}` is already deprecated"); + return; }; - if remove_lint_declaration(name, &mod_path, &mut lints).unwrap_or(false) { - generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); - println!("info: `{name}` has successfully been deprecated"); - println!("note: you must run `cargo uitest` to update the test results"); - } else { - eprintln!("error: lint not found"); - } + remove_lint_declaration(name, &lint, &data); + generate_lint_files(UpdateMode::Change, &data); + println!("info: `{name}` has successfully been deprecated"); + println!("note: you must run `cargo uitest` to update the test results"); } -fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io::Result { - fn remove_lint(name: &str, lints: &mut Vec) { - lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); - } - +fn remove_lint_declaration(name: &str, lint: &ActiveLint, data: &ParsedData) { fn remove_test_assets(name: &str) { let test_file_stem = format!("tests/ui/{name}"); let path = Path::new(&test_file_stem); // Some lints have their own directories, delete them if path.is_dir() { - let _ = fs::remove_dir_all(path); - return; + delete_dir_if_exists(path); + } else { + // Remove all related test files + delete_file_if_exists(&path.with_extension("rs")); + delete_file_if_exists(&path.with_extension("stderr")); + delete_file_if_exists(&path.with_extension("fixed")); } - - // Remove all related test files - let _ = fs::remove_file(path.with_extension("rs")); - let _ = fs::remove_file(path.with_extension("stderr")); - let _ = fs::remove_file(path.with_extension("fixed")); } fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) { @@ -108,54 +83,58 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io } } - if path.exists() - && let Some(lint) = lints.iter().find(|l| l.name == name) - { - if lint.module == name { - // The lint name is the same as the file, we can just delete the entire file - fs::remove_file(path)?; + let lint_file = &data.source_map.files[lint.decl_span.file]; + if data.lints.values().any(|l| { + let other_file = &data.source_map.files[l.name_span.file]; + other_file.krate == lint_file.krate && other_file.module.starts_with(&lint_file.module) + }) { + // Try to delete a sub-module that matches the lint's name + let removed_mod = if lint_file.path.file_name().map(OsStr::as_encoded_bytes) == Some(b"mod.rs") { + let mut path = lint_file.path.clone(); + path.set_file_name(name); + path.set_extension("rs"); + delete_file_if_exists(&path) } else { - // We can't delete the entire file, just remove the declaration - - if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) { - // Remove clippy_lints/src/some_mod/some_lint.rs - let mut lint_mod_path = path.to_path_buf(); - lint_mod_path.set_file_name(name); - lint_mod_path.set_extension("rs"); - - let _ = fs::remove_file(lint_mod_path); - } - - let mut content = - fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); - - eprintln!( - "warn: you will have to manually remove any code related to `{name}` from `{}`", - path.display() - ); - - assert!( - content[lint.declaration_range.clone()].contains(&name.to_uppercase()), - "error: `{}` does not contain lint `{}`'s declaration", - path.display(), - lint.name - ); - - // Remove lint declaration (declare_clippy_lint!) - content.replace_range(lint.declaration_range.clone(), ""); - - // Remove the module declaration (mod xyz;) - let mod_decl = format!("\nmod {name};"); - content = content.replacen(&mod_decl, "", 1); - - remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content); - fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy())); - } - - remove_test_assets(name); - remove_lint(name, lints); - return Ok(true); + false + }; + + FileUpdater::default().update_file(&lint_file.path, &mut |_, src, dst| { + let (a, b, c, d) = if removed_mod + && let mod_decl = format!("\nmod {name};") + && let Some(mod_start) = src.find(&mod_decl) + { + if mod_start < lint.decl_span.start as usize { + ( + mod_start, + mod_start + mod_decl.len(), + lint.decl_span.start as usize, + lint.decl_span.end as usize, + ) + } else { + ( + lint.decl_span.start as usize, + lint.decl_span.end as usize, + mod_start, + mod_start + mod_decl.len(), + ) + } + } else { + ( + lint.decl_span.start as usize, + lint.decl_span.end as usize, + src.len(), + src.len(), + ) + }; + dst.push_str(&src[..a]); + dst.push_str(&src[b..c]); + dst.push_str(&src[d..]); + remove_impl_lint_pass(&name.to_uppercase(), dst); + UpdateStatus::Changed + }); + } else { + // No other lint in the same module or a sub-module. + delete_file_if_exists(&lint_file.path); } - - Ok(false) + remove_test_assets(name); } diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index bd9e57c9f6da..016a57a12862 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -268,7 +268,7 @@ fn run_rustfmt(update_mode: UpdateMode) { .expect("invalid rustfmt path"); rustfmt_path.truncate(rustfmt_path.trim_end().len()); - let args: Vec<_> = walk_dir_no_dot_or_target() + let args: Vec<_> = walk_dir_no_dot_or_target(".") .filter_map(|e| { let e = expect_action(e, ErrAction::Read, "."); e.path() diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 3361443196ab..0d6ee9b92f1b 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,5 +1,6 @@ #![feature( rustc_private, + array_windows, exit_status_error, if_let_guard, let_chains, @@ -16,9 +17,11 @@ )] #![allow(clippy::missing_panics_doc)] -// The `rustc_driver` crate seems to be required in order to use the `rust_lexer` crate. -#[allow(unused_extern_crates)] +#[expect(unused_extern_crates, reason = "needed to lint to rustc crates")] extern crate rustc_driver; + +extern crate rustc_data_structures; +extern crate rustc_index; extern crate rustc_lexer; extern crate rustc_literal_escaper; @@ -34,3 +37,6 @@ pub mod setup; pub mod sync; pub mod update_lints; pub mod utils; + +mod parse; +mod source_map; diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 4121daa85e6d..b353520fb9fe 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,4 +1,5 @@ -use crate::utils::{RustSearcher, Token, Version}; +use crate::parse::{Capture, RustSearcher, Token}; +use crate::utils::Version; use clap::ValueEnum; use indoc::{formatdoc, writedoc}; use std::fmt::{self, Write as _}; @@ -522,7 +523,8 @@ fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) { let mut context = None; let mut decl_end = None; let mut searcher = RustSearcher::new(contents); - while let Some(name) = searcher.find_capture_token(CaptureIdent) { + let mut captures = [Capture::EMPTY]; + while let Some(name) = searcher.find_any_ident() { match name { "declare_clippy_lint" => { if searcher.match_tokens(&[Bang, OpenBrace], &mut []) && searcher.find_token(CloseBrace) { @@ -530,9 +532,8 @@ fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) { } }, "impl" => { - let mut capture = ""; - if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut [&mut capture]) { - match capture { + if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) { + match searcher.get_capture(captures[0]) { "LateLintPass" => context = Some("LateContext"), "EarlyLintPass" => context = Some("EarlyContext"), _ => {}, diff --git a/clippy_dev/src/parse.rs b/clippy_dev/src/parse.rs new file mode 100644 index 000000000000..b4a949331040 --- /dev/null +++ b/clippy_dev/src/parse.rs @@ -0,0 +1,833 @@ +use crate::source_map::{SourceFile, SourceMap, Span}; +use crate::utils::{ErrAction, expect_action, walk_dir_no_dot_or_target}; +use core::ops::Range; +use core::slice; +use rustc_data_structures::fx::FxHashMap; +use rustc_index::{IndexVec, newtype_index}; +use rustc_lexer::{self as lexer, FrontmatterAllowed}; +use std::collections::hash_map::{Entry, VacantEntry}; +use std::panic::Location; +use std::path::{self, Path}; +use std::{fs, process}; + +#[derive(Clone, Copy)] +pub enum Token<'a> { + /// Matches any number of comments / doc comments. + AnyComment, + AnyIdent, + At, + Ident(&'a str), + CaptureIdent, + LitStr, + CaptureLitStr, + Bang, + CloseBrace, + CloseBracket, + CloseParen, + /// This will consume the first colon even if the second doesn't exist. + DoubleColon, + Comma, + Eq, + FatArrow, + Lifetime, + Literal, + Lt, + Gt, + OpenBrace, + OpenBracket, + OpenParen, + OptLifetimeArgs, + Pound, + Semi, +} + +#[derive(Clone, Copy)] +pub struct Capture { + pub pos: u32, + pub len: u32, +} +impl Capture { + pub const EMPTY: Self = Self { pos: 0, len: 0 }; + pub fn to_index(self) -> Range { + self.pos as usize..(self.pos + self.len) as usize + } + + pub fn to_span(self, file: SourceFile) -> Span { + Span { + file, + start: self.pos, + end: self.pos + self.len, + } + } +} + +pub struct RustSearcher<'txt> { + text: &'txt str, + cursor: lexer::Cursor<'txt>, + pos: u32, + next_token: lexer::Token, +} +impl<'txt> RustSearcher<'txt> { + #[must_use] + #[expect(clippy::inconsistent_struct_constructor)] + pub fn new(text: &'txt str) -> Self { + let mut cursor = lexer::Cursor::new(text, FrontmatterAllowed::Yes); + Self { + text, + pos: 0, + next_token: cursor.advance_token(), + cursor, + } + } + + #[must_use] + pub fn get_capture(&self, capture: Capture) -> &'txt str { + &self.text[capture.to_index()] + } + + #[must_use] + pub fn peek_text(&self) -> &'txt str { + &self.text[self.pos as usize..(self.pos + self.next_token.len) as usize] + } + + #[must_use] + pub fn peek_span(&self, file: SourceFile) -> Span { + Span { + file, + start: self.pos, + end: self.pos + self.next_token.len, + } + } + + #[track_caller] + fn get_unexpected_err(&self, file: SourceFile) -> Error { + ErrorKind::UnexpectedToken { + token: self.peek_text().into(), + span: self.peek_span(file), + } + .into() + } + + #[must_use] + pub fn peek_len(&self) -> u32 { + self.next_token.len + } + + #[must_use] + pub fn peek(&self) -> lexer::TokenKind { + self.next_token.kind + } + + #[must_use] + pub fn pos(&self) -> u32 { + self.pos + } + + #[must_use] + pub fn at_end(&self) -> bool { + self.next_token.kind == lexer::TokenKind::Eof + } + + /// Steps to the next token, or `TokenKind::Eof` if there are no more tokens. + pub fn step(&mut self) { + // `next_token.len` is zero for the eof marker. + self.pos += self.next_token.len; + self.next_token = self.cursor.advance_token(); + } + + /// Consumes the next token if it matches the requested value and captures the value if + /// requested. Returns `true` if a token was matched. + #[expect(clippy::too_many_lines)] + fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool { + loop { + match (token, self.next_token.kind) { + (_, lexer::TokenKind::Whitespace) + | ( + Token::AnyComment, + lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. }, + ) => self.step(), + (Token::AnyComment, _) => return true, + (Token::At, lexer::TokenKind::At) + | (Token::AnyIdent, lexer::TokenKind::Ident) + | (Token::Bang, lexer::TokenKind::Bang) + | (Token::CloseBrace, lexer::TokenKind::CloseBrace) + | (Token::CloseBracket, lexer::TokenKind::CloseBracket) + | (Token::CloseParen, lexer::TokenKind::CloseParen) + | (Token::Comma, lexer::TokenKind::Comma) + | (Token::Eq, lexer::TokenKind::Eq) + | (Token::Lifetime, lexer::TokenKind::Lifetime { .. }) + | (Token::Lt, lexer::TokenKind::Lt) + | (Token::Gt, lexer::TokenKind::Gt) + | (Token::OpenBrace, lexer::TokenKind::OpenBrace) + | (Token::OpenBracket, lexer::TokenKind::OpenBracket) + | (Token::OpenParen, lexer::TokenKind::OpenParen) + | (Token::Pound, lexer::TokenKind::Pound) + | (Token::Semi, lexer::TokenKind::Semi) + | ( + Token::LitStr, + lexer::TokenKind::Literal { + kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. }, + .. + }, + ) + | ( + Token::Literal, + lexer::TokenKind::Literal { + kind: + lexer::LiteralKind::Int { .. } + | lexer::LiteralKind::Float { .. } + | lexer::LiteralKind::Byte { terminated: true } + | lexer::LiteralKind::ByteStr { terminated: true } + | lexer::LiteralKind::Char { terminated: true } + | lexer::LiteralKind::CStr { terminated: true } + | lexer::LiteralKind::Str { terminated: true } + | lexer::LiteralKind::RawByteStr { .. } + | lexer::LiteralKind::RawCStr { .. } + | lexer::LiteralKind::RawStr { .. }, + .. + }, + ) => { + self.step(); + return true; + }, + (Token::Literal, lexer::TokenKind::Ident) if matches!(self.peek_text(), "true" | "false") => { + self.step(); + return true; + }, + (Token::Ident(x), lexer::TokenKind::Ident) if x == self.peek_text() => { + self.step(); + return true; + }, + (Token::DoubleColon, lexer::TokenKind::Colon) => { + self.step(); + if matches!(self.next_token.kind, lexer::TokenKind::Colon) { + self.step(); + return true; + } + return false; + }, + (Token::FatArrow, lexer::TokenKind::Eq) => { + self.step(); + if matches!(self.next_token.kind, lexer::TokenKind::Gt) { + self.step(); + return true; + } + return false; + }, + (Token::OptLifetimeArgs, lexer::TokenKind::Lt) => { + self.step(); + while self.read_token(Token::Lifetime, captures) { + if !self.read_token(Token::Comma, captures) { + break; + } + } + return self.read_token(Token::Gt, captures); + }, + #[expect(clippy::match_same_arms)] + (Token::OptLifetimeArgs, _) => return true, + #[rustfmt::skip] + ( + Token::CaptureLitStr, + lexer::TokenKind::Literal { + kind: + lexer::LiteralKind::Str { terminated: true } + | lexer::LiteralKind::RawStr { n_hashes: Some(_) }, + .. + }, + ) + | (Token::CaptureIdent, lexer::TokenKind::Ident) => { + *captures.next().unwrap() = Capture { pos: self.pos, len: self.next_token.len }; + self.step(); + return true; + }, + _ => return false, + } + } + } + + #[must_use] + pub fn find_token(&mut self, token: Token<'_>) -> bool { + let mut captures = [].iter_mut(); + while !self.read_token(token, &mut captures) { + self.step(); + if self.at_end() { + return false; + } + } + true + } + + #[must_use] + pub fn find_any_ident(&mut self) -> Option<&'txt str> { + loop { + match self.next_token.kind { + lexer::TokenKind::Ident => { + let res = self.peek_text(); + self.step(); + return Some(res); + }, + lexer::TokenKind::Eof => return None, + _ => self.step(), + } + } + } + + #[must_use] + pub fn find_ident(&mut self, s: &str) -> bool { + while let Some(x) = self.find_any_ident() { + if x == s { + return true; + } + } + false + } + + #[must_use] + pub fn match_token(&mut self, token: Token<'_>) -> bool { + let mut captures = [].iter_mut(); + self.read_token(token, &mut captures) + } + + #[must_use] + pub fn match_tokens(&mut self, tokens: &[Token<'_>], captures: &mut [Capture]) -> bool { + let mut captures = captures.iter_mut(); + tokens.iter().all(|&t| self.read_token(t, &mut captures)) + } +} + +pub struct ActiveLint { + pub group: String, + pub decl_span: Span, +} + +pub struct DeprecatedLint { + pub reason: String, + pub version: String, +} + +pub struct RenamedLint { + pub new_name: String, + pub version: String, +} + +pub enum LintKind { + Active(ActiveLint), + Deprecated(DeprecatedLint), + Renamed(RenamedLint), +} + +pub struct Lint { + pub kind: LintKind, + pub name_span: Span, +} + +pub struct LintPassData { + pub name: String, + /// Span of the `impl_lint_pass` or `declare_lint_pass` macro call. + pub mac_span: Span, +} + +newtype_index! { + #[orderable] + pub struct LintPass {} +} + +pub struct LintRegistration { + pub name: String, + pub pass: LintPass, +} + +pub struct ParsedData { + pub source_map: SourceMap, + pub lints: FxHashMap, + pub lint_passes: IndexVec, + pub lint_registrations: Vec, + pub deprecated_span: Range, + pub renamed_span: Range, +} +impl ParsedData { + #[expect(clippy::default_trait_access)] + pub fn collect() -> Self { + // 2025-05: Initial capacities should fit everything without reallocating. + let mut parser = Parser { + source_map: SourceMap::with_capacity(8, 1000), + lints: FxHashMap::with_capacity_and_hasher(1000, Default::default()), + lint_passes: IndexVec::with_capacity(400), + lint_registrations: Vec::with_capacity(1000), + deprecated_span: 0..0, + renamed_span: 0..0, + errors: Vec::new(), + }; + parser.parse_src_files(); + parser.parse_deprecated_lints(); + + if !parser.errors.is_empty() { + for error in &parser.errors { + match &error.kind { + ErrorKind::DuplicateLint { name, span, prev_span } => { + eprint!( + "error: duplicate lint `{name}` found\n at: {}\n previous: {}\n", + span.display(&parser.source_map), + prev_span.display(&parser.source_map), + ); + }, + ErrorKind::NotLintName(span) => { + eprint!( + "error: invalid lint name found\n at: {}\n", + span.display(&parser.source_map), + ); + }, + ErrorKind::LintMissingPrefix(span) => { + eprint!( + "error: lint name missing `clippy::` prefix\n at: {}\n", + span.display(&parser.source_map), + ); + }, + ErrorKind::StrLit(span) => { + eprint!( + "error: invalid string literal\n at: {}\n", + span.display(&parser.source_map), + ); + }, + ErrorKind::StrLitEol(span) => { + eprint!( + "error: string literal contains a line ending\n at: {}\n", + span.display(&parser.source_map), + ); + }, + ErrorKind::UnexpectedToken { token, span } => { + eprint!( + "error: unexpected token `{token}`\n at: {}\n", + span.display(&parser.source_map), + ); + }, + } + eprintln!(" error-src: {}", error.loc); + } + process::exit(1); + } + + ParsedData { + source_map: parser.source_map, + lints: parser.lints, + lint_passes: parser.lint_passes, + lint_registrations: parser.lint_registrations, + deprecated_span: parser.deprecated_span, + renamed_span: parser.renamed_span, + } + } +} + +enum ErrorKind { + /// Multiple lint declarations with the same name. + DuplicateLint { + name: String, + span: Span, + prev_span: Span, + }, + /// A string literal is not a valid lint name. + NotLintName(Span), + /// A lint name is missing the `clippy::` prefix. + LintMissingPrefix(Span), + /// Error when parsing a string literal. + StrLit(Span), + // A string literal contains a line terminator. + StrLitEol(Span), + // A token not expected in the source was found. + UnexpectedToken { + token: String, + span: Span, + }, +} +struct Error { + kind: ErrorKind, + loc: &'static Location<'static>, +} +impl From for Error { + #[track_caller] + fn from(kind: ErrorKind) -> Self { + Self { + kind, + loc: Location::caller(), + } + } +} + +struct Parser { + source_map: SourceMap, + lints: FxHashMap, + lint_passes: IndexVec, + lint_registrations: Vec, + deprecated_span: Range, + renamed_span: Range, + errors: Vec, +} +impl Parser { + /// Parses all source files looking for lint declarations (`declare_clippy_lint! { .. }`). + fn parse_src_files(&mut self) { + 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 crate_path) = e.file_name().into_string() else { + continue; + }; + if crate_path.starts_with("clippy_lints") && crate_path != "clippy_lints_internal" { + let krate = self.source_map.add_new_crate(&crate_path); + crate_path.push(path::MAIN_SEPARATOR); + crate_path.push_str("src"); + for e in walk_dir_no_dot_or_target(&crate_path) { + let e = expect_action(e, ErrAction::Read, &crate_path); + if let Some(path) = e.path().to_str() + && let Some(path) = path.strip_suffix(".rs") + && let Some(path) = path.get(crate_path.len() + 1..) + { + let module = if path == "lib" { + String::new() + } else { + let path = if let Some(path) = path.strip_suffix("mod") + && let Some(path) = path.strip_suffix(path::MAIN_SEPARATOR) + { + path + } else { + path + }; + path.replace(path::MAIN_SEPARATOR, "::") + }; + let file = self.source_map.load_new_file(e.path(), krate, module); + self.parse_src_file(file); + } + } + } + } + } + + /// Parse a source file looking for `declare_clippy_lint` macro invocations. + fn parse_src_file(&mut self, file: SourceFile) { + #[allow(clippy::enum_glob_use)] + use Token::*; + #[rustfmt::skip] + static LINT_DECL_TOKENS: &[Token<'_>] = &[ + // { /// docs + OpenBrace, AnyComment, + // #[clippy::version = "version"] + Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket, + // pub NAME, GROUP, "desc" + Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, AnyComment, LitStr, + ]; + #[rustfmt::skip] + static LINT_DECL_EXTRA_TOKENS: &[Token<'_>] = &[ + // , @option = value + Comma, AnyComment, At, AnyIdent, Eq, Literal, + ]; + #[rustfmt::skip] + static LINT_PASS_TOKENS: &[Token<'_>] = &[ + // ( name <'lt> => [ + OpenParen, AnyComment, CaptureIdent, OptLifetimeArgs, FatArrow, OpenBracket, + ]; + + let mut searcher = RustSearcher::new(&self.source_map.files[file].contents); + let mut captures = [Capture::EMPTY; 2]; + while let Some(ident) = searcher.find_any_ident() { + #[expect(clippy::cast_possible_truncation)] + let start = searcher.pos - ident.len() as u32; + if searcher.match_token(Bang) { + match ident { + "declare_clippy_lint" => { + if !searcher.match_tokens(LINT_DECL_TOKENS, &mut captures) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + while searcher.match_tokens(LINT_DECL_EXTRA_TOKENS, &mut []) { + // nothing + } + if !searcher.match_token(CloseBrace) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + let name_span = captures[0].to_span(file); + let name = searcher.get_capture(captures[0]).to_ascii_lowercase(); + if let Some(e) = get_vacant_lint(name, name_span, &mut self.lints, &mut self.errors) { + e.insert(Lint { + kind: LintKind::Active(ActiveLint { + group: searcher.get_capture(captures[1]).into(), + decl_span: Span { + file, + start, + end: searcher.pos(), + }, + }), + name_span, + }); + } + }, + "impl_lint_pass" | "declare_lint_pass" => { + if !searcher.match_tokens(LINT_PASS_TOKENS, &mut captures) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + let pass = self.lint_passes.next_index(); + let pass_name = captures[0]; + while searcher.match_tokens(&[AnyComment, CaptureIdent], &mut captures) { + // Read a path expression. + while searcher.match_token(DoubleColon) { + // Overwrite the previous capture. The last segment is the lint name we want. + if !searcher.match_tokens(&[CaptureIdent], &mut captures) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + } + self.lint_registrations.push(LintRegistration { + name: searcher.get_capture(captures[0]).to_ascii_lowercase(), + pass, + }); + if !searcher.match_token(Comma) { + break; + } + } + if !searcher.match_tokens(&[CloseBracket, CloseParen], &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + self.lint_passes.push(LintPassData { + name: searcher.get_capture(pass_name).to_owned(), + mac_span: Span { + file, + start, + end: searcher.pos(), + }, + }); + }, + _ => {}, + } + } + } + } + + pub fn parse_deprecated_lints(&mut self) { + #[allow(clippy::enum_glob_use)] + use Token::*; + #[rustfmt::skip] + static DECL_TOKENS: &[Token<'_>] = &[ + // #[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: &[Token<'_>] = &[ + // !{ DEPRECATED(DEPRECATED_VERSION) = [ + Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket, + ]; + #[rustfmt::skip] + static RENAMED_TOKENS: &[Token<'_>] = &[ + // !{ RENAMED(RENAMED_VERSION) = [ + Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket, + ]; + #[rustfmt::skip] + static END_TOKENS: &[Token<'_>] = &[ + // ]} + CloseBracket, CloseBrace, + ]; + + let krate = self.source_map.add_crate("clippy_lints"); + let file = self.source_map.load_file( + Path::new("clippy_lints/src/deprecated_lints.rs"), + krate, + "deprecated_lints", + ); + let file_data = &self.source_map.files[file]; + + let mut captures = [Capture::EMPTY; 3]; + let mut searcher = RustSearcher::new(&file_data.contents); + // First instance is the macro definition. + assert!( + searcher.find_ident("declare_with_version"), + "error parsing `clippy_lints/src/deprecated_lints.rs`", + ); + + if !searcher.find_ident("declare_with_version") || !searcher.match_tokens(DEPRECATED_TOKENS, &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + let start = searcher.pos(); + let mut end = start; + while searcher.match_tokens(DECL_TOKENS, &mut captures) { + end = searcher.pos(); + let name_span = captures[1].to_span(file); + let (Some(version), Some(name), Some(reason)) = ( + parse_str_single_line(captures[0], searcher.text, file, &mut self.errors), + parse_clippy_lint_name(captures[1], searcher.text, file, &mut self.errors), + parse_str_single_line(captures[2], searcher.text, file, &mut self.errors), + ) else { + continue; + }; + if let Some(e) = get_vacant_lint(name, name_span, &mut self.lints, &mut self.errors) { + e.insert(Lint { + kind: LintKind::Deprecated(DeprecatedLint { reason, version }), + name_span, + }); + } + } + self.deprecated_span = start..end; + if !searcher.match_tokens(END_TOKENS, &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + + if !searcher.find_ident("declare_with_version") || !searcher.match_tokens(RENAMED_TOKENS, &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + let start = searcher.pos(); + let mut end = start; + while searcher.match_tokens(DECL_TOKENS, &mut captures) { + end = searcher.pos(); + let name_span = captures[1].to_span(file); + let (Some(version), Some(name), Some(new_name)) = ( + parse_str_single_line(captures[0], searcher.text, file, &mut self.errors), + parse_clippy_lint_name(captures[1], searcher.text, file, &mut self.errors), + parse_maybe_clippy_lint_name(captures[2], searcher.text, file, &mut self.errors), + ) else { + continue; + }; + if let Some(e) = get_vacant_lint(name, name_span, &mut self.lints, &mut self.errors) { + e.insert(Lint { + kind: LintKind::Renamed(RenamedLint { new_name, version }), + name_span, + }); + } + } + self.renamed_span = start..end; + if !searcher.match_tokens(END_TOKENS, &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + #[expect(clippy::needless_return)] + return; + } + } +} + +#[track_caller] +fn get_vacant_lint<'a>( + name: String, + name_span: Span, + lints: &'a mut FxHashMap, + errors: &mut Vec, +) -> Option> { + match lints.entry(name) { + Entry::Vacant(e) => Some(e), + Entry::Occupied(e) => { + errors.push( + ErrorKind::DuplicateLint { + name: e.key().clone(), + span: name_span, + prev_span: e.get().name_span, + } + .into(), + ); + None + }, + } +} + +#[track_caller] +fn parse_str_lit(capture: Capture, text: &str, file: SourceFile, errors: &mut Vec) -> Option { + let s = &text[capture.to_index()]; + let (s, is_raw) = if let Some(s) = s.strip_prefix("r") { + (s.trim_matches('#'), true) + } else { + (s, false) + }; + let Some(s) = s.strip_prefix('"').and_then(|s| s.strip_suffix('"')) else { + errors.push(ErrorKind::StrLit(capture.to_span(file)).into()); + return None; + }; + + let mut no_err = true; + let res = if is_raw { + #[expect(clippy::cast_possible_truncation)] + rustc_literal_escaper::check_raw_str(s, |sp, ch| { + if let Err(e) = ch + && e.is_fatal() + { + errors.push( + ErrorKind::StrLit(Span { + file, + start: capture.pos + sp.start as u32, + end: capture.pos + sp.end as u32, + }) + .into(), + ); + no_err = false; + } + }); + s.into() + } else { + let mut res = String::with_capacity(s.len()); + #[expect(clippy::cast_possible_truncation)] + rustc_literal_escaper::unescape_str(s, |sp, ch| match ch { + Ok(ch) => res.push(ch), + Err(e) if e.is_fatal() => { + errors.push( + ErrorKind::StrLit(Span { + file, + start: capture.pos + sp.start as u32, + end: capture.pos + sp.end as u32, + }) + .into(), + ); + no_err = false; + }, + _ => {}, + }); + res + }; + no_err.then_some(res) +} + +#[track_caller] +fn parse_str_single_line(capture: Capture, text: &str, file: SourceFile, errors: &mut Vec) -> Option { + let s = parse_str_lit(capture, text, file, errors)?; + if s.contains('\n') { + errors.push(ErrorKind::StrLitEol(capture.to_span(file)).into()); + None + } else { + Some(s) + } +} + +fn is_lint_name(s: &str) -> bool { + s.bytes() + .all(|c| matches!(c, b'_' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z')) +} + +#[track_caller] +fn parse_clippy_lint_name(capture: Capture, text: &str, file: SourceFile, errors: &mut Vec) -> Option { + let mut s = parse_str_lit(capture, text, file, errors)?; + let Some(name) = s.strip_prefix("clippy::") else { + errors.push(ErrorKind::LintMissingPrefix(capture.to_span(file)).into()); + return None; + }; + if !is_lint_name(name) { + errors.push(ErrorKind::NotLintName(capture.to_span(file)).into()); + return None; + } + s.drain(.."clippy::".len()); + Some(s) +} + +#[track_caller] +fn parse_maybe_clippy_lint_name( + capture: Capture, + text: &str, + file: SourceFile, + errors: &mut Vec, +) -> Option { + let s = parse_str_lit(capture, text, file, errors)?; + if !is_lint_name(s.strip_prefix("clippy::").unwrap_or(&s)) { + errors.push(ErrorKind::NotLintName(capture.to_span(file)).into()); + return None; + } + Some(s) +} diff --git a/clippy_dev/src/rename_lint.rs b/clippy_dev/src/rename_lint.rs index d62597428e21..8875de431ca3 100644 --- a/clippy_dev/src/rename_lint.rs +++ b/clippy_dev/src/rename_lint.rs @@ -1,9 +1,13 @@ -use crate::update_lints::{RenamedLint, find_lint_decls, generate_lint_files, read_deprecated_lints}; +use crate::parse::{Capture, Lint, LintKind, ParsedData, RenamedLint, RustSearcher, Token}; +use crate::update_lints::generate_lint_files; use crate::utils::{ - ErrAction, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, - delete_file_if_exists, expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, + ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists, + expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, }; +use core::mem; +use rustc_data_structures::fx::FxHashMap; use rustc_lexer::TokenKind; +use std::collections::hash_map::Entry; use std::ffi::OsString; use std::fs; use std::path::Path; @@ -33,67 +37,57 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } let mut updater = FileUpdater::default(); - let mut lints = find_lint_decls(); - let (deprecated_lints, mut renamed_lints) = read_deprecated_lints(); + let mut data = ParsedData::collect(); - let Ok(lint_idx) = lints.binary_search_by(|x| x.name.as_str().cmp(old_name)) else { - panic!("could not find lint `{old_name}`"); - }; - let lint = &lints[lint_idx]; - - let old_name_prefixed = String::from_iter(["clippy::", old_name]); + // Update any existing renames let new_name_prefixed = if uplift { new_name.to_owned() } else { String::from_iter(["clippy::", new_name]) }; - - for lint in &mut renamed_lints { - if lint.new_name == old_name_prefixed { + for lint in data.lints.values_mut() { + if let LintKind::Renamed(lint) = &mut lint.kind + && lint.new_name.strip_prefix("clippy::") == Some(old_name) + { lint.new_name.clone_from(&new_name_prefixed); } } - match renamed_lints.binary_search_by(|x| x.old_name.cmp(&old_name_prefixed)) { - Ok(_) => { - println!("`{old_name}` already has a rename registered"); - return; - }, - Err(idx) => { - renamed_lints.insert( - idx, - RenamedLint { - old_name: old_name_prefixed, - new_name: if uplift { - new_name.to_owned() - } else { - String::from_iter(["clippy::", new_name]) - }, - version: clippy_version.rust_display().to_string(), - }, - ); - }, - } - // Some tests are named `lint_name_suffix` which should also be renamed, - // but we can't do that if the renamed lint's name overlaps with another - // lint. e.g. renaming 'foo' to 'bar' when a lint 'foo_bar' also exists. - let change_prefixed_tests = lints.get(lint_idx + 1).is_none_or(|l| !l.name.starts_with(old_name)); + // Mark the lint as renamed + let Some(entry) = data.lints.get_mut(old_name) else { + eprintln!("error: failed to find lint `{old_name}`"); + return; + }; + let LintKind::Active(lint) = mem::replace( + &mut entry.kind, + LintKind::Renamed(RenamedLint { + new_name: new_name_prefixed, + version: clippy_version.rust_display().to_string(), + }), + ) else { + eprintln!("error: lint `{old_name}` is already deprecated"); + return; + }; + let lint_name_span = entry.name_span; let mut mod_edit = ModEdit::None; if uplift { - let is_unique_mod = lints[..lint_idx].iter().any(|l| l.module == lint.module) - || lints[lint_idx + 1..].iter().any(|l| l.module == lint.module); + let lint_file = &data.source_map.files[lint_name_span.file]; + let is_unique_mod = data + .lints + .values() + .any(|x| data.source_map.files[x.name_span.file].module == lint_file.module); if is_unique_mod { - if delete_file_if_exists(lint.path.as_ref()) { + if delete_file_if_exists(lint_file.path.as_ref()) { mod_edit = ModEdit::Delete; } } else { - updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus { - let mut start = &src[..lint.declaration_range.start]; + updater.update_file(&lint_file.path, &mut |_, src, dst| -> UpdateStatus { + let mut start = &src[..lint.decl_span.start as usize]; if start.ends_with("\n\n") { start = &start[..start.len() - 1]; } - let mut end = &src[lint.declaration_range.end..]; + let mut end = &src[lint.decl_span.end as usize..]; if end.starts_with("\n\n") { end = &end[1..]; } @@ -102,29 +96,31 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b UpdateStatus::Changed }); } - delete_test_files(old_name, change_prefixed_tests); - lints.remove(lint_idx); - } else if lints.binary_search_by(|x| x.name.as_str().cmp(new_name)).is_err() { - let lint = &mut lints[lint_idx]; - if lint.module.ends_with(old_name) - && lint + delete_test_files(old_name, &data.lints); + } else if let Entry::Vacant(entry) = data.lints.entry(new_name.to_owned()) { + let lint_file = &mut data.source_map.files[lint.decl_span.file]; + if lint_file.module.ends_with(old_name) + && lint_file .path .file_stem() .is_some_and(|x| x.as_encoded_bytes() == old_name.as_bytes()) { - let mut new_path = lint.path.with_file_name(new_name).into_os_string(); + let mut new_path = lint_file.path.with_file_name(new_name).into_os_string(); new_path.push(".rs"); - if try_rename_file(lint.path.as_ref(), new_path.as_ref()) { + if try_rename_file(lint_file.path.as_ref(), new_path.as_ref()) { + lint_file.path = new_path.into(); mod_edit = ModEdit::Rename; - } - let mod_len = lint.module.len(); - lint.module.truncate(mod_len - old_name.len()); - lint.module.push_str(new_name); + let mod_len = lint_file.module.len(); + lint_file.module.truncate(mod_len - old_name.len()); + lint_file.module.push_str(new_name); + } } - rename_test_files(old_name, new_name, change_prefixed_tests); - new_name.clone_into(&mut lints[lint_idx].name); - lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); + entry.insert(Lint { + kind: LintKind::Active(lint), + name_span: lint_name_span, + }); + rename_test_files(old_name, new_name, &data.lints); } else { println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); println!("Since `{new_name}` already exists the existing code has not been changed"); @@ -132,13 +128,13 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } let mut update_fn = file_update_fn(old_name, new_name, mod_edit); - for e in walk_dir_no_dot_or_target() { + for e in walk_dir_no_dot_or_target(".") { let e = expect_action(e, ErrAction::Read, "."); if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") { updater.update_file(e.path(), &mut update_fn); } } - generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + generate_lint_files(UpdateMode::Change, &data); if uplift { println!("Uplifted `clippy::{old_name}` as `{new_name}`"); @@ -163,47 +159,67 @@ enum ModEdit { Rename, } -fn collect_ui_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) { +fn is_lint_test(lint: &str, name: &str, lints: &FxHashMap) -> bool { + let is_num = |c: char| c.is_ascii_digit(); + let Some(suffix) = name.strip_prefix(lint) else { + return false; + }; + let suffix = suffix.trim_start_matches(is_num); + let Some(suffix) = suffix.strip_prefix('_') else { + return true; + }; + + // Some lint names are a prefix of other lint names. Make sure we don't mix test files + // between the two lints. + !(lints.contains_key(name.trim_end_matches(is_num)) + || suffix + .bytes() + .zip(suffix.len() - name.len()..) + .any(|(c, i)| c == b'_' && lints.contains_key(name[..i].trim_end_matches(is_num)))) +} + +fn collect_ui_test_names(lint: &str, lints: &FxHashMap, dst: &mut Vec<(String, bool)>) { for e in fs::read_dir("tests/ui").expect("error reading `tests/ui`") { let e = e.expect("error reading `tests/ui`"); - let name = e.file_name(); - if let Some((name_only, _)) = name.as_encoded_bytes().split_once(|&x| x == b'.') { - if name_only.starts_with(lint.as_bytes()) && (rename_prefixed || name_only.len() == lint.len()) { + if let Ok(name) = e.file_name().into_string() { + if e.file_type().is_ok_and(|ty| ty.is_dir()) { + if is_lint_test(lint, &name, lints) { + dst.push((name, false)); + } + } else if let Some((name_only, _)) = name.split_once('.') + && is_lint_test(lint, name_only, lints) + { dst.push((name, true)); } - } else if name.as_encoded_bytes().starts_with(lint.as_bytes()) && (rename_prefixed || name.len() == lint.len()) - { - dst.push((name, false)); } } } -fn collect_ui_toml_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) { - if rename_prefixed { - for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") { - let e = e.expect("error reading `tests/ui-toml`"); - let name = e.file_name(); - if name.as_encoded_bytes().starts_with(lint.as_bytes()) && e.file_type().is_ok_and(|ty| ty.is_dir()) { - dst.push((name, false)); - } +fn collect_ui_toml_test_names(lint: &str, lints: &FxHashMap, dst: &mut Vec<(String, bool)>) { + for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") { + let e = e.expect("error reading `tests/ui-toml`"); + if e.file_type().is_ok_and(|ty| ty.is_dir()) + && let Ok(name) = e.file_name().into_string() + && is_lint_test(lint, &name, lints) + { + dst.push((name, false)); } - } else { - dst.push((lint.into(), false)); } } /// Renames all test files for the given lint. /// /// If `rename_prefixed` is `true` this will also rename tests which have the lint name as a prefix. -fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) { +fn rename_test_files(old_name: &str, new_name: &str, lints: &FxHashMap) { let mut tests = Vec::new(); let mut old_buf = OsString::from("tests/ui/"); let mut new_buf = OsString::from("tests/ui/"); - collect_ui_test_names(old_name, rename_prefixed, &mut tests); + collect_ui_test_names(old_name, lints, &mut tests); for &(ref name, is_file) in &tests { old_buf.push(name); - new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); + new_buf.push(new_name); + new_buf.push(&name[old_name.len()..]); if is_file { try_rename_file(old_buf.as_ref(), new_buf.as_ref()); } else { @@ -218,21 +234,22 @@ fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) { new_buf.truncate("tests/ui".len()); old_buf.push("-toml/"); new_buf.push("-toml/"); - collect_ui_toml_test_names(old_name, rename_prefixed, &mut tests); + collect_ui_toml_test_names(old_name, lints, &mut tests); for (name, _) in &tests { old_buf.push(name); - new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); + new_buf.push(new_name); + new_buf.push(&name[old_name.len()..]); try_rename_dir(old_buf.as_ref(), new_buf.as_ref()); - old_buf.truncate("tests/ui/".len()); - new_buf.truncate("tests/ui/".len()); + old_buf.truncate("tests/ui-toml/".len()); + new_buf.truncate("tests/ui-toml/".len()); } } -fn delete_test_files(lint: &str, rename_prefixed: bool) { +fn delete_test_files(lint: &str, lints: &FxHashMap) { let mut tests = Vec::new(); let mut buf = OsString::from("tests/ui/"); - collect_ui_test_names(lint, rename_prefixed, &mut tests); + collect_ui_test_names(lint, lints, &mut tests); for &(ref name, is_file) in &tests { buf.push(name); if is_file { @@ -247,11 +264,11 @@ fn delete_test_files(lint: &str, rename_prefixed: bool) { buf.push("-toml/"); tests.clear(); - collect_ui_toml_test_names(lint, rename_prefixed, &mut tests); + collect_ui_toml_test_names(lint, lints, &mut tests); for (name, _) in &tests { buf.push(name); delete_dir_if_exists(buf.as_ref()); - buf.truncate("tests/ui/".len()); + buf.truncate("tests/ui-toml/".len()); } } @@ -286,7 +303,7 @@ fn file_update_fn<'a, 'b>( let mut copy_pos = 0u32; let mut changed = false; let mut searcher = RustSearcher::new(src); - let mut capture = ""; + let mut captures = [Capture::EMPTY]; loop { match searcher.peek() { TokenKind::Eof => break, @@ -295,12 +312,12 @@ fn file_update_fn<'a, 'b>( let text = searcher.peek_text(); searcher.step(); match text { - // clippy::line_name or clippy::lint-name + // clippy::lint_name "clippy" => { - if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name + if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut captures) + && searcher.get_capture(captures[0]) == old_name { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); dst.push_str(new_name); copy_pos = searcher.pos(); changed = true; @@ -309,12 +326,12 @@ fn file_update_fn<'a, 'b>( // mod lint_name "mod" => { if !matches!(mod_edit, ModEdit::None) - && searcher.match_tokens(&[Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name + && searcher.match_tokens(&[Token::CaptureIdent], &mut captures) + && searcher.get_capture(captures[0]) == old_name { match mod_edit { ModEdit::Rename => { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); dst.push_str(new_name); copy_pos = searcher.pos(); changed = true; @@ -377,10 +394,10 @@ fn file_update_fn<'a, 'b>( }, // ::lint_name TokenKind::Colon - if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name => + if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut captures) + && searcher.get_capture(captures[0]) == old_name => { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); dst.push_str(new_name); copy_pos = searcher.pos(); changed = true; diff --git a/clippy_dev/src/source_map.rs b/clippy_dev/src/source_map.rs new file mode 100644 index 000000000000..555014c77fc3 --- /dev/null +++ b/clippy_dev/src/source_map.rs @@ -0,0 +1,113 @@ +use crate::utils::File; +use core::fmt::{self, Display}; +use memchr::memchr_iter; +use rustc_index::{IndexVec, newtype_index}; +use std::path::{Path, PathBuf}; + +pub struct SourceData { + pub contents: String, + pub line_starts: Vec, + pub path: PathBuf, + pub module: String, + pub krate: Crate, +} +impl SourceData { + pub fn line_col(&self, pos: u32) -> (u32, u32) { + #[expect(clippy::cast_possible_truncation)] + let (line, offset) = match self.line_starts.binary_search(&pos) { + Ok(i) => (i as u32 + 1, self.line_starts[i]), + Err(i) => (i as u32, self.line_starts[i - 1]), + }; + let mut col = 1; + let mut remain = pos - offset; + let mut chars = self.contents[offset as usize..].chars(); + #[expect(clippy::cast_possible_truncation)] + while remain != 0 + && let Some(c) = chars.next() + { + col += 1; + remain = remain.saturating_sub(c.len_utf8() as u32); + } + (line, col) + } +} + +pub struct SourceMap { + pub crates: IndexVec>, + pub files: IndexVec, +} +impl SourceMap { + pub fn with_capacity(crates: usize, files: usize) -> Self { + Self { + crates: IndexVec::with_capacity(crates), + files: IndexVec::with_capacity(files), + } + } + + pub fn add_new_crate(&mut self, name: &str) -> Crate { + let res = self.crates.next_index(); + self.crates.push(name.into()); + res + } + + pub fn add_crate(&mut self, name: &str) -> Crate { + match self.crates.iter().position(|x| **x == *name) { + Some(x) => Crate::from_usize(x), + None => self.add_new_crate(name), + } + } + + pub fn load_new_file(&mut self, path: &Path, krate: Crate, module: String) -> SourceFile { + let mut contents = String::new(); + File::open_read(path).read_append_to_string(&mut contents); + + let res = self.files.next_index(); + let mut line_starts = Vec::with_capacity(16); + line_starts.push(0); + #[expect(clippy::cast_possible_truncation)] + line_starts.extend(memchr_iter(b'\n', contents.as_bytes()).map(|x| x as u32 + 1)); + self.files.push(SourceData { + contents, + line_starts, + path: path.into(), + module, + krate, + }); + res + } + + pub fn load_file(&mut self, path: &Path, krate: Crate, module: &str) -> SourceFile { + match self.files.iter().position(|x| x.krate == krate && x.module == module) { + Some(x) => SourceFile::from_usize(x), + None => self.load_new_file(path, krate, module.into()), + } + } +} + +newtype_index! { + pub struct SourceFile {} +} +newtype_index! { + #[orderable] + pub struct Crate {} +} + +#[derive(Clone, Copy)] +pub struct Span { + pub file: SourceFile, + pub start: u32, + pub end: u32, +} +impl Span { + pub fn display(self, source_map: &SourceMap) -> impl use<'_> + Display { + struct X<'a>(Span, &'a SourceMap); + impl Display for X<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let file = &self.1.files[self.0.file]; + let (line, col) = file.line_col(self.0.start); + write!(f, "{}:{line}:{col}", self.1.files[self.0.file].path.display()) + } + } + X(self, source_map) + } +} diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 5f6e874ffe25..146230882a60 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,13 +1,9 @@ -use crate::utils::{ - ErrAction, File, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, expect_action, update_text_region_fn, -}; +use crate::parse::{LintKind, ParsedData}; +use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; use itertools::Itertools; -use std::collections::HashSet; use std::fmt::Write; -use std::fs; -use std::ops::Range; -use std::path::{self, Path, PathBuf}; -use walkdir::{DirEntry, WalkDir}; +use std::path::Path; +use std::process; const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ // Use that command to update this file and do not edit by hand.\n\ @@ -25,91 +21,103 @@ const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.ht /// /// Panics if a file path could not read from or then written to pub fn update(update_mode: UpdateMode) { - let lints = find_lint_decls(); - let (deprecated, renamed) = read_deprecated_lints(); - generate_lint_files(update_mode, &lints, &deprecated, &renamed); + let mut data = ParsedData::collect(); + let mut is_missing = false; + data.lint_registrations.sort_by(|x, y| x.name.cmp(&y.name)); + for (name, lint) in &data.lints { + if matches!(lint.kind, LintKind::Active(_)) + && data.lint_registrations.binary_search_by(|x| x.name.cmp(name)).is_err() + { + is_missing = true; + eprint!( + "error: lint `{name}` is not registered in a lint pass\n declared here: {}\n", + lint.name_span.display(&data.source_map) + ); + } + } + if is_missing { + process::exit(1); + } + generate_lint_files(update_mode, &data); } #[expect(clippy::too_many_lines)] -pub fn generate_lint_files( - update_mode: UpdateMode, - lints: &[Lint], - deprecated: &[DeprecatedLint], - renamed: &[RenamedLint], -) { +pub fn generate_lint_files(update_mode: UpdateMode, data: &ParsedData) { let mut updater = FileUpdater::default(); + + let mut lints: Vec<_> = data.lints.iter().map(|(x, y)| (&**x, y)).collect(); + lints.sort_by_key(|&(x, _)| x); + updater.update_file_checked( "cargo dev update_lints", update_mode, - "README.md", - &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { - write!(dst, "{}", round_to_fifty(lints.len())).unwrap(); - }), + "CHANGELOG.md", + &mut update_text_region_fn( + "\n", + "", + |dst| { + for &(lint, _) in &lints { + writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); + } + }, + ), ); + + let mut active = Vec::with_capacity(data.lints.len()); + let mut deprecated = Vec::with_capacity(data.lints.len()); + let mut renamed = Vec::with_capacity(data.lints.len()); + for (name, lint) in lints { + match &lint.kind { + LintKind::Active(lint) => active.push((name, &data.source_map.files[lint.decl_span.file])), + LintKind::Deprecated(lint) => deprecated.push((name, lint)), + LintKind::Renamed(lint) => renamed.push((name, lint)), + } + } + active.sort_by(|(_, x), (_, y)| (x.krate, &*x.module).cmp(&(y.krate, &*y.module))); + + // Round the lint count down to avoid merge conflicts every time a new lint is added. + let lint_count = active.len() / 50 * 50; updater.update_file_checked( "cargo dev update_lints", update_mode, - "book/src/README.md", + "README.md", &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { - write!(dst, "{}", round_to_fifty(lints.len())).unwrap(); + write!(dst, "{lint_count}").unwrap(); }), ); updater.update_file_checked( "cargo dev update_lints", update_mode, - "CHANGELOG.md", - &mut update_text_region_fn( - "\n", - "", - |dst| { - for lint in lints - .iter() - .map(|l| &*l.name) - .chain(deprecated.iter().filter_map(|l| l.name.strip_prefix("clippy::"))) - .chain(renamed.iter().filter_map(|l| l.old_name.strip_prefix("clippy::"))) - .sorted() - { - writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); - } - }, - ), + "book/src/README.md", + &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { + write!(dst, "{lint_count}").unwrap(); + }), ); + updater.update_file_checked( "cargo dev update_lints", update_mode, "clippy_lints/src/deprecated_lints.rs", &mut |_, src, dst| { - let mut searcher = RustSearcher::new(src); - assert!( - searcher.find_token(Token::Ident("declare_with_version")) - && searcher.find_token(Token::Ident("declare_with_version")), - "error reading deprecated lints" - ); - dst.push_str(&src[..searcher.pos() as usize]); - dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); - for lint in deprecated { + dst.push_str(&src[..data.deprecated_span.start as usize]); + for &(name, lint) in &deprecated { write!( dst, - " #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n", - lint.version, lint.name, lint.reason, + "\n #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),", + lint.version, lint.reason, ) .unwrap(); } - dst.push_str( - "]}\n\n\ - #[rustfmt::skip]\n\ - declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ - ", - ); - for lint in renamed { + dst.push_str(&src[data.deprecated_span.end as usize..data.renamed_span.start as usize]); + for &(name, lint) in &renamed { write!( dst, - " #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n", - lint.version, lint.old_name, lint.new_name, + "\n #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),", + lint.version, lint.new_name, ) .unwrap(); } - dst.push_str("]}\n"); + dst.push_str(&src[data.renamed_span.end as usize..]); UpdateStatus::from_changed(src != dst) }, ); @@ -119,8 +127,8 @@ pub fn generate_lint_files( "tests/ui/deprecated.rs", &mut |_, src, dst| { dst.push_str(GENERATED_FILE_COMMENT); - for lint in deprecated { - writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.name, lint.name).unwrap(); + for &(name, _) in &deprecated { + writeln!(dst, "#![warn(clippy::{name})] //~ ERROR: lint `clippy::{name}`").unwrap(); } dst.push_str("\nfn main() {}\n"); UpdateStatus::from_changed(src != dst) @@ -131,47 +139,34 @@ pub fn generate_lint_files( update_mode, "tests/ui/rename.rs", &mut move |_, src, dst| { - let mut seen_lints = HashSet::new(); dst.push_str(GENERATED_FILE_COMMENT); dst.push_str("#![allow(clippy::duplicated_attributes)]\n"); - for lint in renamed { - if seen_lints.insert(&lint.new_name) { - writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); - } - } - seen_lints.clear(); - for lint in renamed { - if seen_lints.insert(&lint.old_name) { - writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap(); - } + for &(name, _) in &renamed { + writeln!(dst, "#![warn(clippy::{name})] //~ ERROR: lint `clippy::{name}`").unwrap(); } dst.push_str("\nfn main() {}\n"); UpdateStatus::from_changed(src != dst) }, ); - for (crate_name, lints) in lints.iter().into_group_map_by(|&l| { - let Some(path::Component::Normal(name)) = l.path.components().next() else { - // All paths should start with `{crate_name}/src` when parsed from `find_lint_decls` - panic!("internal error: can't read crate name from path `{}`", l.path.display()); - }; - name - }) { + + let mut active = &*active; + while let [(_, first_lint), ..] = active { + let (crate_lints, tail) = active.split_at(active.partition_point(|(_, x)| x.krate == first_lint.krate)); + let krate_name = &*data.source_map.crates[first_lint.krate]; + active = tail; + updater.update_file_checked( "cargo dev update_lints", update_mode, - Path::new(crate_name).join("src/lib.rs"), + Path::new(krate_name).join("src/lib.rs"), &mut update_text_region_fn( "// begin lints modules, do not remove this comment, it's used in `update_lints`\n", "// end lints modules, do not remove this comment, it's used in `update_lints`", |dst| { - for lint_mod in lints - .iter() - .filter(|l| !l.module.is_empty()) - .map(|l| l.module.split_once("::").map_or(&*l.module, |x| x.0)) - .sorted() - .dedup() - { - writeln!(dst, "mod {lint_mod};").unwrap(); + for (_, lint) in crate_lints.iter().dedup_by(|(_, x), (_, y)| x.module == y.module) { + if !lint.module.is_empty() { + writeln!(dst, "mod {};", lint.module).unwrap(); + } } }, ), @@ -179,15 +174,19 @@ pub fn generate_lint_files( updater.update_file_checked( "cargo dev update_lints", update_mode, - Path::new(crate_name).join("src/declared_lints.rs"), + Path::new(krate_name).join("src/declared_lints.rs"), &mut |_, src, dst| { dst.push_str(GENERATED_FILE_COMMENT); dst.push_str("pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[\n"); - for (module_path, lint_name) in lints.iter().map(|l| (&l.module, l.name.to_uppercase())).sorted() { - if module_path.is_empty() { - writeln!(dst, " crate::{lint_name}_INFO,").unwrap(); + let mut buf = String::new(); + for (name, lint) in crate_lints { + buf.clear(); + buf.push_str(name); + buf.make_ascii_uppercase(); + if lint.module.is_empty() { + writeln!(dst, " crate::{buf}_INFO,").unwrap(); } else { - writeln!(dst, " crate::{module_path}::{lint_name}_INFO,").unwrap(); + writeln!(dst, " crate::{}::{buf}_INFO,", lint.module).unwrap(); } } dst.push_str("];\n"); @@ -196,264 +195,3 @@ pub fn generate_lint_files( ); } } - -fn round_to_fifty(count: usize) -> usize { - count / 50 * 50 -} - -/// Lint data parsed from the Clippy source code. -#[derive(PartialEq, Eq, Debug)] -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 Token::*; - #[rustfmt::skip] - static DECL_TOKENS: &[Token<'_>] = &[ - // !{ /// 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 searcher = RustSearcher::new(contents); - while searcher.find_token(Ident("declare_clippy_lint")) { - let start = searcher.pos() as usize - "declare_clippy_lint".len(); - let (mut name, mut group) = ("", ""); - if searcher.match_tokens(DECL_TOKENS, &mut [&mut name, &mut group]) && searcher.find_token(CloseBrace) { - lints.push(Lint { - name: name.to_lowercase(), - group: group.into(), - module: module.into(), - path: path.into(), - declaration_range: start..searcher.pos() as usize, - }); - } - } -} - -#[must_use] -pub fn read_deprecated_lints() -> (Vec, Vec) { - #[allow(clippy::enum_glob_use)] - use Token::*; - #[rustfmt::skip] - static DECL_TOKENS: &[Token<'_>] = &[ - // #[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: &[Token<'_>] = &[ - // !{ DEPRECATED(DEPRECATED_VERSION) = [ - Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket, - ]; - #[rustfmt::skip] - static RENAMED_TOKENS: &[Token<'_>] = &[ - // !{ 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 searcher = RustSearcher::new(&contents); - - // First instance is the macro definition. - assert!( - searcher.find_token(Ident("declare_with_version")), - "error reading deprecated lints" - ); - - if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(DEPRECATED_TOKENS, &mut []) { - let mut version = ""; - let mut name = ""; - let mut reason = ""; - while searcher.match_tokens(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 searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(RENAMED_TOKENS, &mut []) { - let mut version = ""; - let mut old_name = ""; - let mut new_name = ""; - while searcher.match_tokens(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 = s.strip_prefix("r").unwrap_or(s).trim_matches('#'); - let s = s - .strip_prefix('"') - .and_then(|s| s.strip_suffix('"')) - .unwrap_or_else(|| panic!("expected quoted string, found `{s}`")); - 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 -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_clippy_lint_decls() { - static CONTENTS: &str = r#" - declare_clippy_lint! { - #[clippy::version = "Hello Clippy!"] - pub PTR_ARG, - style, - "really long \ - text" - } - - declare_clippy_lint!{ - #[clippy::version = "Test version"] - pub DOC_MARKDOWN, - pedantic, - "single line" - } - "#; - let mut result = Vec::new(); - parse_clippy_lint_decls("".as_ref(), CONTENTS, "module_name", &mut result); - for r in &mut result { - r.declaration_range = Range::default(); - } - - let expected = vec![ - Lint { - name: "ptr_arg".into(), - group: "style".into(), - module: "module_name".into(), - path: PathBuf::new(), - declaration_range: Range::default(), - }, - Lint { - name: "doc_markdown".into(), - group: "pedantic".into(), - module: "module_name".into(), - path: PathBuf::new(), - declaration_range: Range::default(), - }, - ]; - assert_eq!(expected, result); - } -} diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index 89962a110341..e8f91385320b 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -1,9 +1,7 @@ use core::fmt::{self, Display}; use core::num::NonZero; use core::ops::Range; -use core::slice; use core::str::FromStr; -use rustc_lexer::{self as lexer, FrontmatterAllowed}; use std::ffi::OsStr; use std::fs::{self, OpenOptions}; use std::io::{self, Read as _, Seek as _, SeekFrom, Write}; @@ -71,6 +69,12 @@ impl<'a> File<'a> { } } + /// Opens a file for reading. Panics on failure. + #[track_caller] + pub fn open_read(path: &'a (impl AsRef + ?Sized)) -> Self { + Self::open(path, OpenOptions::new().read(true)) + } + /// Opens a file if it exists, panicking on any other failure. #[track_caller] pub fn open_if_exists(path: &'a (impl AsRef + ?Sized), options: &mut OpenOptions) -> Option { @@ -82,15 +86,6 @@ impl<'a> File<'a> { } } - /// Opens and reads a file into a string, panicking of failure. - #[track_caller] - pub fn open_read_to_cleared_string<'dst>( - path: &'a (impl AsRef + ?Sized), - dst: &'dst mut String, - ) -> &'dst mut String { - Self::open(path, OpenOptions::new().read(true)).read_to_cleared_string(dst) - } - /// Read the entire contents of a file to the given buffer. #[track_caller] pub fn read_append_to_string<'dst>(&mut self, dst: &'dst mut String) -> &'dst mut String { @@ -426,181 +421,6 @@ pub fn update_text_region_fn( move |path, src, dst| update_text_region(path, start, end, src, dst, &mut insert) } -#[derive(Clone, Copy)] -pub enum Token<'a> { - /// Matches any number of comments / doc comments. - AnyComment, - Ident(&'a str), - CaptureIdent, - LitStr, - CaptureLitStr, - Bang, - CloseBrace, - CloseBracket, - CloseParen, - /// This will consume the first colon even if the second doesn't exist. - DoubleColon, - Comma, - Eq, - Lifetime, - Lt, - Gt, - OpenBrace, - OpenBracket, - OpenParen, - Pound, - Semi, - Slash, -} - -pub struct RustSearcher<'txt> { - text: &'txt str, - cursor: lexer::Cursor<'txt>, - pos: u32, - next_token: lexer::Token, -} -impl<'txt> RustSearcher<'txt> { - #[must_use] - #[expect(clippy::inconsistent_struct_constructor)] - pub fn new(text: &'txt str) -> Self { - let mut cursor = lexer::Cursor::new(text, FrontmatterAllowed::Yes); - Self { - text, - pos: 0, - next_token: cursor.advance_token(), - cursor, - } - } - - #[must_use] - pub fn peek_text(&self) -> &'txt str { - &self.text[self.pos as usize..(self.pos + self.next_token.len) as usize] - } - - #[must_use] - pub fn peek_len(&self) -> u32 { - self.next_token.len - } - - #[must_use] - pub fn peek(&self) -> lexer::TokenKind { - self.next_token.kind - } - - #[must_use] - pub fn pos(&self) -> u32 { - self.pos - } - - #[must_use] - pub fn at_end(&self) -> bool { - self.next_token.kind == lexer::TokenKind::Eof - } - - pub fn step(&mut self) { - // `next_len` is zero for the sentinel value and the eof marker. - self.pos += self.next_token.len; - self.next_token = self.cursor.advance_token(); - } - - /// Consumes the next token if it matches the requested value and captures the value if - /// requested. Returns true if a token was matched. - fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, &mut &'txt str>) -> bool { - loop { - match (token, self.next_token.kind) { - (_, lexer::TokenKind::Whitespace) - | ( - Token::AnyComment, - lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. }, - ) => self.step(), - (Token::AnyComment, _) => return true, - (Token::Bang, lexer::TokenKind::Bang) - | (Token::CloseBrace, lexer::TokenKind::CloseBrace) - | (Token::CloseBracket, lexer::TokenKind::CloseBracket) - | (Token::CloseParen, lexer::TokenKind::CloseParen) - | (Token::Comma, lexer::TokenKind::Comma) - | (Token::Eq, lexer::TokenKind::Eq) - | (Token::Lifetime, lexer::TokenKind::Lifetime { .. }) - | (Token::Lt, lexer::TokenKind::Lt) - | (Token::Gt, lexer::TokenKind::Gt) - | (Token::OpenBrace, lexer::TokenKind::OpenBrace) - | (Token::OpenBracket, lexer::TokenKind::OpenBracket) - | (Token::OpenParen, lexer::TokenKind::OpenParen) - | (Token::Pound, lexer::TokenKind::Pound) - | (Token::Semi, lexer::TokenKind::Semi) - | (Token::Slash, lexer::TokenKind::Slash) - | ( - Token::LitStr, - lexer::TokenKind::Literal { - kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. }, - .. - }, - ) => { - self.step(); - return true; - }, - (Token::Ident(x), lexer::TokenKind::Ident) if x == self.peek_text() => { - self.step(); - return true; - }, - (Token::DoubleColon, lexer::TokenKind::Colon) => { - self.step(); - if !self.at_end() && matches!(self.next_token.kind, lexer::TokenKind::Colon) { - self.step(); - return true; - } - return false; - }, - ( - Token::CaptureLitStr, - lexer::TokenKind::Literal { - kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. }, - .. - }, - ) - | (Token::CaptureIdent, lexer::TokenKind::Ident) => { - **captures.next().unwrap() = self.peek_text(); - self.step(); - return true; - }, - _ => return false, - } - } - } - - #[must_use] - pub fn find_token(&mut self, token: Token<'_>) -> bool { - let mut capture = [].iter_mut(); - while !self.read_token(token, &mut capture) { - self.step(); - if self.at_end() { - return false; - } - } - true - } - - #[must_use] - pub fn find_capture_token(&mut self, token: Token<'_>) -> Option<&'txt str> { - let mut res = ""; - let mut capture = &mut res; - let mut capture = slice::from_mut(&mut capture).iter_mut(); - while !self.read_token(token, &mut capture) { - self.step(); - if self.at_end() { - return None; - } - } - Some(res) - } - - #[must_use] - pub fn match_tokens(&mut self, tokens: &[Token<'_>], captures: &mut [&mut &'txt str]) -> bool { - let mut captures = captures.iter_mut(); - tokens.iter().all(|&t| self.read_token(t, &mut captures)) - } -} - #[expect(clippy::must_use_candidate)] pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { match OpenOptions::new().create_new(true).write(true).open(new_name) { @@ -756,8 +576,8 @@ pub fn delete_dir_if_exists(path: &Path) { } /// Walks all items excluding top-level dot files/directories and any target directories. -pub fn walk_dir_no_dot_or_target() -> impl Iterator> { - WalkDir::new(".").into_iter().filter_entry(|e| { +pub fn walk_dir_no_dot_or_target(p: impl AsRef) -> impl Iterator> { + WalkDir::new(p.as_ref()).into_iter().filter_entry(|e| { e.path() .file_name() .is_none_or(|x| x != "target" && x.as_encoded_bytes().first().copied() != Some(b'.')) diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c3f8e02b4c06..188f8f999165 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -84,8 +84,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::collection_is_never_read::COLLECTION_IS_NEVER_READ_INFO, crate::comparison_chain::COMPARISON_CHAIN_INFO, crate::copies::BRANCHES_SHARING_CODE_INFO, - crate::copies::IFS_SAME_COND_INFO, crate::copies::IF_SAME_THEN_ELSE_INFO, + crate::copies::IFS_SAME_COND_INFO, crate::copies::SAME_FUNCTIONS_IN_IF_CONDITION_INFO, crate::copy_iterator::COPY_ITERATOR_INFO, crate::crate_in_macro_def::CRATE_IN_MACRO_DEF_INFO, @@ -102,9 +102,9 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::dereference::NEEDLESS_BORROW_INFO, crate::dereference::REF_BINDING_TO_REFERENCE_INFO, crate::derivable_impls::DERIVABLE_IMPLS_INFO, - crate::derive::DERIVED_HASH_WITH_MANUAL_EQ_INFO, crate::derive::DERIVE_ORD_XOR_PARTIAL_ORD_INFO, crate::derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ_INFO, + crate::derive::DERIVED_HASH_WITH_MANUAL_EQ_INFO, crate::derive::EXPL_IMPL_CLONE_ON_COPY_INFO, crate::derive::UNSAFE_DERIVE_DESERIALIZE_INFO, crate::disallowed_macros::DISALLOWED_MACROS_INFO, @@ -333,8 +333,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::matches::MATCH_SAME_ARMS_INFO, crate::matches::MATCH_SINGLE_BINDING_INFO, crate::matches::MATCH_STR_CASE_MISMATCH_INFO, - crate::matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS_INFO, crate::matches::MATCH_WILD_ERR_ARM_INFO, + crate::matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS_INFO, crate::matches::NEEDLESS_MATCH_INFO, crate::matches::REDUNDANT_GUARDS_INFO, crate::matches::REDUNDANT_PATTERN_MATCHING_INFO, @@ -356,9 +356,9 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::CHARS_LAST_CMP_INFO, crate::methods::CHARS_NEXT_CMP_INFO, crate::methods::CLEAR_WITH_DRAIN_INFO, - crate::methods::CLONED_INSTEAD_OF_COPIED_INFO, crate::methods::CLONE_ON_COPY_INFO, crate::methods::CLONE_ON_REF_PTR_INFO, + crate::methods::CLONED_INSTEAD_OF_COPIED_INFO, crate::methods::COLLAPSIBLE_STR_REPLACE_INFO, crate::methods::CONST_IS_EMPTY_INFO, crate::methods::DOUBLE_ENDED_ITERATOR_LAST_INFO, @@ -386,7 +386,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::IO_OTHER_ERROR_INFO, crate::methods::IP_CONSTANT_INFO, crate::methods::IS_DIGIT_ASCII_RADIX_INFO, - crate::methods::ITERATOR_STEP_BY_ZERO_INFO, crate::methods::ITER_CLONED_COLLECT_INFO, crate::methods::ITER_COUNT_INFO, crate::methods::ITER_FILTER_IS_OK_INFO, @@ -402,9 +401,10 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::ITER_SKIP_NEXT_INFO, crate::methods::ITER_SKIP_ZERO_INFO, crate::methods::ITER_WITH_DRAIN_INFO, + crate::methods::ITERATOR_STEP_BY_ZERO_INFO, crate::methods::JOIN_ABSOLUTE_PATHS_INFO, - crate::methods::MANUAL_CONTAINS_INFO, crate::methods::MANUAL_C_STR_LITERALS_INFO, + crate::methods::MANUAL_CONTAINS_INFO, crate::methods::MANUAL_FILTER_MAP_INFO, crate::methods::MANUAL_FIND_MAP_INFO, crate::methods::MANUAL_INSPECT_INFO, @@ -433,8 +433,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::NEEDLESS_OPTION_TAKE_INFO, crate::methods::NEEDLESS_SPLITN_INFO, crate::methods::NEW_RET_NO_SELF_INFO, - crate::methods::NONSENSICAL_OPEN_OPTIONS_INFO, crate::methods::NO_EFFECT_REPLACE_INFO, + crate::methods::NONSENSICAL_OPEN_OPTIONS_INFO, crate::methods::OBFUSCATED_IF_ELSE_INFO, crate::methods::OK_EXPECT_INFO, crate::methods::OPTION_AS_REF_CLONED_INFO, @@ -446,8 +446,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO, crate::methods::PATH_ENDS_WITH_EXT_INFO, crate::methods::RANGE_ZIP_WITH_LEN_INFO, - crate::methods::READONLY_WRITE_LOCK_INFO, crate::methods::READ_LINE_WITHOUT_TRIM_INFO, + crate::methods::READONLY_WRITE_LOCK_INFO, crate::methods::REDUNDANT_AS_STR_INFO, crate::methods::REPEAT_ONCE_INFO, crate::methods::RESULT_FILTER_MAP_INFO, @@ -461,9 +461,9 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::SKIP_WHILE_NEXT_INFO, crate::methods::SLICED_STRING_AS_BYTES_INFO, crate::methods::STABLE_SORT_PRIMITIVE_INFO, + crate::methods::STR_SPLIT_AT_NEWLINE_INFO, crate::methods::STRING_EXTEND_CHARS_INFO, crate::methods::STRING_LIT_CHARS_ANY_INFO, - crate::methods::STR_SPLIT_AT_NEWLINE_INFO, crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, crate::methods::SUSPICIOUS_MAP_INFO, crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO, @@ -633,8 +633,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::ranges::RANGE_MINUS_ONE_INFO, crate::ranges::RANGE_PLUS_ONE_INFO, crate::ranges::REVERSED_EMPTY_RANGES_INFO, - crate::raw_strings::NEEDLESS_RAW_STRINGS_INFO, crate::raw_strings::NEEDLESS_RAW_STRING_HASHES_INFO, + crate::raw_strings::NEEDLESS_RAW_STRINGS_INFO, crate::rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT_INFO, crate::read_zero_byte_vec::READ_ZERO_BYTE_VEC_INFO, crate::redundant_async_block::REDUNDANT_ASYNC_BLOCK_INFO, @@ -685,13 +685,13 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::std_instead_of_core::STD_INSTEAD_OF_CORE_INFO, crate::string_patterns::MANUAL_PATTERN_CHAR_COMPARISON_INFO, crate::string_patterns::SINGLE_CHAR_PATTERN_INFO, + crate::strings::STR_TO_STRING_INFO, crate::strings::STRING_ADD_INFO, crate::strings::STRING_ADD_ASSIGN_INFO, crate::strings::STRING_FROM_UTF8_AS_BYTES_INFO, crate::strings::STRING_LIT_AS_BYTES_INFO, crate::strings::STRING_SLICE_INFO, crate::strings::STRING_TO_STRING_INFO, - crate::strings::STR_TO_STRING_INFO, crate::strings::TRIM_SPLIT_WHITESPACE_INFO, crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO, crate::suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS_INFO, @@ -712,7 +712,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::transmute::CROSSPOINTER_TRANSMUTE_INFO, crate::transmute::EAGER_TRANSMUTE_INFO, crate::transmute::MISSING_TRANSMUTE_ANNOTATIONS_INFO, - crate::transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS_INFO, crate::transmute::TRANSMUTE_BYTES_TO_STR_INFO, crate::transmute::TRANSMUTE_INT_TO_BOOL_INFO, crate::transmute::TRANSMUTE_INT_TO_NON_ZERO_INFO, @@ -720,6 +719,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::transmute::TRANSMUTE_PTR_TO_PTR_INFO, crate::transmute::TRANSMUTE_PTR_TO_REF_INFO, crate::transmute::TRANSMUTE_UNDEFINED_REPR_INFO, + crate::transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS_INFO, crate::transmute::TRANSMUTING_NULL_INFO, crate::transmute::UNSOUND_COLLECTION_TRANSMUTE_INFO, crate::transmute::USELESS_TRANSMUTE_INFO, @@ -776,19 +776,19 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::vec::USELESS_VEC_INFO, crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO, crate::visibility::NEEDLESS_PUB_SELF_INFO, - crate::visibility::PUB_WITHOUT_SHORTHAND_INFO, crate::visibility::PUB_WITH_SHORTHAND_INFO, + crate::visibility::PUB_WITHOUT_SHORTHAND_INFO, crate::wildcard_imports::ENUM_GLOB_USE_INFO, crate::wildcard_imports::WILDCARD_IMPORTS_INFO, - crate::write::PRINTLN_EMPTY_STRING_INFO, crate::write::PRINT_LITERAL_INFO, crate::write::PRINT_STDERR_INFO, crate::write::PRINT_STDOUT_INFO, crate::write::PRINT_WITH_NEWLINE_INFO, + crate::write::PRINTLN_EMPTY_STRING_INFO, crate::write::USE_DEBUG_INFO, - crate::write::WRITELN_EMPTY_STRING_INFO, crate::write::WRITE_LITERAL_INFO, crate::write::WRITE_WITH_NEWLINE_INFO, + crate::write::WRITELN_EMPTY_STRING_INFO, crate::zero_div_zero::ZERO_DIVIDED_BY_ZERO_INFO, crate::zero_repeat_side_effects::ZERO_REPEAT_SIDE_EFFECTS_INFO, crate::zero_sized_map_values::ZERO_SIZED_MAP_VALUES_INFO, diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 49397938ca73..66f240f6f71f 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -1,5 +1,3 @@ -#![allow(clippy::lint_without_lint_pass)] - use clippy_config::Conf; use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then}; diff --git a/clippy_lints_internal/src/lib.rs b/clippy_lints_internal/src/lib.rs index 43cde86504f5..c19fba229aaa 100644 --- a/clippy_lints_internal/src/lib.rs +++ b/clippy_lints_internal/src/lib.rs @@ -50,7 +50,6 @@ static LINTS: &[&Lint] = &[ derive_deserialize_allowing_unknown::DERIVE_DESERIALIZE_ALLOWING_UNKNOWN, lint_without_lint_pass::DEFAULT_LINT, lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE, - lint_without_lint_pass::LINT_WITHOUT_LINT_PASS, lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE, msrv_attr_impl::MISSING_MSRV_ATTR_IMPL, outer_expn_data_pass::OUTER_EXPN_EXPN_DATA, @@ -69,7 +68,7 @@ pub fn register_lints(store: &mut LintStore) { store.register_late_pass(|_| Box::new(collapsible_calls::CollapsibleCalls)); store.register_late_pass(|_| Box::new(derive_deserialize_allowing_unknown::DeriveDeserializeAllowingUnknown)); store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::::default()); + store.register_late_pass(|_| Box::new(lint_without_lint_pass::LintWithoutLintPass)); store.register_late_pass(|_| Box::new(unnecessary_def_path::UnnecessaryDefPath)); store.register_late_pass(|_| Box::new(outer_expn_data_pass::OuterExpnDataPass)); store.register_late_pass(|_| Box::new(msrv_attr_impl::MsrvAttrImpl)); diff --git a/clippy_lints_internal/src/lint_without_lint_pass.rs b/clippy_lints_internal/src/lint_without_lint_pass.rs index 45a866030b2d..3b782c28e92a 100644 --- a/clippy_lints_internal/src/lint_without_lint_pass.rs +++ b/clippy_lints_internal/src/lint_without_lint_pass.rs @@ -1,49 +1,14 @@ use crate::internal_paths; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; -use clippy_utils::is_lint_allowed; -use clippy_utils::macros::root_macro_call_first_node; use rustc_ast::ast::LitKind; -use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::hir_id::CRATE_HIR_ID; -use rustc_hir::intravisit::Visitor; -use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind}; +use rustc_hir::{ExprKind, Item, MutTy, Mutability, TyKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::hir::nested_filter; -use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Spanned; +use rustc_span::sym; use rustc_span::symbol::Symbol; -use rustc_span::{Span, sym}; - -declare_tool_lint! { - /// ### What it does - /// Ensures every lint is associated to a `LintPass`. - /// - /// ### Why is this bad? - /// The compiler only knows lints via a `LintPass`. Without - /// putting a lint to a `LintPass::lint_vec()`'s return, the compiler will not - /// know the name of the lint. - /// - /// ### Known problems - /// Only checks for lints associated using the `declare_lint_pass!` and - /// `impl_lint_pass!` macros. - /// - /// ### Example - /// ```rust,ignore - /// declare_lint! { pub LINT_1, ... } - /// declare_lint! { pub LINT_2, ... } - /// declare_lint! { pub FORGOTTEN_LINT, ... } - /// // ... - /// declare_lint_pass!(Pass => [LINT_1, LINT_2]); - /// // missing FORGOTTEN_LINT - /// ``` - pub clippy::LINT_WITHOUT_LINT_PASS, - Warn, - "declaring a lint without associating it in a LintPass", - report_in_external_macro: true - -} declare_tool_lint! { /// ### What it does @@ -90,109 +55,47 @@ declare_tool_lint! { report_in_external_macro: true } -#[derive(Clone, Debug, Default)] -pub struct LintWithoutLintPass { - declared_lints: FxIndexMap, - registered_lints: FxIndexSet, -} - -impl_lint_pass!(LintWithoutLintPass => [ +declare_lint_pass!(LintWithoutLintPass => [ DEFAULT_LINT, - LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE, ]); impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let hir::ItemKind::Static(Mutability::Not, ident, ty, body_id) = item.kind { - if is_lint_ref_type(cx, ty) { - check_invalid_clippy_version_attribute(cx, item); + if let hir::ItemKind::Static(Mutability::Not, ident, ty, body_id) = item.kind + && is_lint_ref_type(cx, ty) + { + check_invalid_clippy_version_attribute(cx, item); - let expr = &cx.tcx.hir_body(body_id).value; - let fields = if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind - && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind - { - struct_fields - } else { - return; - }; + let expr = &cx.tcx.hir_body(body_id).value; + let fields = if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind + && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind + { + struct_fields + } else { + return; + }; - let field = fields - .iter() - .find(|f| f.ident.as_str() == "desc") - .expect("lints must have a description field"); + let field = fields + .iter() + .find(|f| f.ident.as_str() == "desc") + .expect("lints must have a description field"); - if let ExprKind::Lit(Spanned { - node: LitKind::Str(sym, _), - .. - }) = field.expr.kind - { - let sym_str = sym.as_str(); - if sym_str == "default lint description" { - span_lint( - cx, - DEFAULT_LINT, - item.span, - format!("the lint `{}` has the default lint description", ident.name), - ); - } - self.declared_lints.insert(ident.name, item.span); - } - } - } else if let Some(macro_call) = root_macro_call_first_node(cx, item) { - if !matches!( - cx.tcx.item_name(macro_call.def_id).as_str(), - "impl_lint_pass" | "declare_lint_pass" - ) { - return; - } - if let hir::ItemKind::Impl(hir::Impl { - of_trait: None, - items: impl_item_refs, + if let ExprKind::Lit(Spanned { + node: LitKind::Str(sym, _), .. - }) = item.kind + }) = field.expr.kind { - let mut collector = LintCollector { - output: &mut self.registered_lints, - cx, - }; - let body = cx.tcx.hir_body_owned_by( - impl_item_refs - .iter() - .find(|iiref| iiref.ident.as_str() == "lint_vec") - .expect("LintPass needs to implement lint_vec") - .id - .owner_id - .def_id, - ); - collector.visit_expr(body.value); - } - } - } - - fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { - if is_lint_allowed(cx, LINT_WITHOUT_LINT_PASS, CRATE_HIR_ID) { - return; - } - - for (lint_name, &lint_span) in &self.declared_lints { - // When using the `declare_tool_lint!` macro, the original `lint_span`'s - // file points to "". - // `compiletest-rs` thinks that's an error in a different file and - // just ignores it. This causes the test in compile-fail/lint_pass - // not able to capture the error. - // Therefore, we need to climb the macro expansion tree and find the - // actual span that invoked `declare_tool_lint!`: - let lint_span = lint_span.ctxt().outer_expn_data().call_site; - - if !self.registered_lints.contains(lint_name) { - span_lint( - cx, - LINT_WITHOUT_LINT_PASS, - lint_span, - format!("the lint `{lint_name}` is not added to any `LintPass`"), - ); + let sym_str = sym.as_str(); + if sym_str == "default lint description" { + span_lint( + cx, + DEFAULT_LINT, + item.span, + format!("the lint `{}` has the default lint description", ident.name), + ); + } } } } @@ -261,22 +164,3 @@ pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item< } }) } - -struct LintCollector<'a, 'tcx> { - output: &'a mut FxIndexSet, - cx: &'a LateContext<'tcx>, -} - -impl<'tcx> Visitor<'tcx> for LintCollector<'_, 'tcx> { - type NestedFilter = nested_filter::All; - - fn visit_path(&mut self, path: &Path<'_>, _: HirId) { - if path.segments.len() == 1 { - self.output.insert(path.segments[0].ident.name); - } - } - - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.cx.tcx - } -} diff --git a/tests/ui-internal/check_formulation.rs b/tests/ui-internal/check_formulation.rs index bcbb0d783198..092404cfcda8 100644 --- a/tests/ui-internal/check_formulation.rs +++ b/tests/ui-internal/check_formulation.rs @@ -1,5 +1,4 @@ #![deny(clippy::almost_standard_lint_formulation)] -#![allow(clippy::lint_without_lint_pass)] #![feature(rustc_private)] #[macro_use] diff --git a/tests/ui-internal/check_formulation.stderr b/tests/ui-internal/check_formulation.stderr index 9aeb9e1f2d49..f498f04c5fe0 100644 --- a/tests/ui-internal/check_formulation.stderr +++ b/tests/ui-internal/check_formulation.stderr @@ -1,5 +1,5 @@ error: non-standard lint formulation - --> tests/ui-internal/check_formulation.rs:24:5 + --> tests/ui-internal/check_formulation.rs:23:5 | LL | /// Check for lint formulations that are correct | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -12,7 +12,7 @@ LL | #![deny(clippy::almost_standard_lint_formulation)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-standard lint formulation - --> tests/ui-internal/check_formulation.rs:35:5 + --> tests/ui-internal/check_formulation.rs:34:5 | LL | /// Detects uses of incorrect formulations | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui-internal/lint_without_lint_pass.rs b/tests/ui-internal/lint_without_lint_pass.rs deleted file mode 100644 index 6b649132aca3..000000000000 --- a/tests/ui-internal/lint_without_lint_pass.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![deny(clippy::lint_without_lint_pass)] -#![allow(clippy::missing_clippy_version_attribute)] -#![feature(rustc_private)] - -#[macro_use] -extern crate rustc_middle; -#[macro_use] -extern crate rustc_session; -extern crate rustc_lint; -use rustc_lint::{LintPass, LintVec}; - -declare_tool_lint! { -//~^ lint_without_lint_pass - pub clippy::TEST_LINT, - Warn, - "", - report_in_external_macro: true -} - -declare_tool_lint! { - pub clippy::TEST_LINT_REGISTERED, - Warn, - "", - report_in_external_macro: true -} - -declare_tool_lint! { - pub clippy::TEST_LINT_REGISTERED_ONLY_IMPL, - Warn, - "", - report_in_external_macro: true -} - -pub struct Pass; -impl LintPass for Pass { - fn name(&self) -> &'static str { - "TEST_LINT" - } - fn get_lints(&self) -> LintVec { - vec![TEST_LINT] - } -} - -declare_lint_pass!(Pass2 => [TEST_LINT_REGISTERED]); - -pub struct Pass3; -impl_lint_pass!(Pass3 => [TEST_LINT_REGISTERED_ONLY_IMPL]); - -fn main() {} diff --git a/tests/ui-internal/lint_without_lint_pass.stderr b/tests/ui-internal/lint_without_lint_pass.stderr deleted file mode 100644 index 3798293f4c11..000000000000 --- a/tests/ui-internal/lint_without_lint_pass.stderr +++ /dev/null @@ -1,21 +0,0 @@ -error: the lint `TEST_LINT` is not added to any `LintPass` - --> tests/ui-internal/lint_without_lint_pass.rs:12:1 - | -LL | / declare_tool_lint! { -LL | | -LL | | pub clippy::TEST_LINT, -LL | | Warn, -LL | | "", -LL | | report_in_external_macro: true -LL | | } - | |_^ - | -note: the lint level is defined here - --> tests/ui-internal/lint_without_lint_pass.rs:1:9 - | -LL | #![deny(clippy::lint_without_lint_pass)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: aborting due to 1 previous error - diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index ff81c6426027..07f999e74c19 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -3,67 +3,6 @@ // Manual edits will be overwritten. #![allow(clippy::duplicated_attributes)] -#![allow(clippy::almost_complete_range)] -#![allow(clippy::disallowed_names)] -#![allow(clippy::blocks_in_conditions)] -#![allow(clippy::box_collection)] -#![allow(invalid_reference_casting)] -#![allow(suspicious_double_ref_op)] -#![allow(invalid_nan_comparisons)] -#![allow(clippy::redundant_static_lifetimes)] -#![allow(clippy::cognitive_complexity)] -#![allow(clippy::derived_hash_with_manual_eq)] -#![allow(clippy::disallowed_methods)] -#![allow(clippy::disallowed_types)] -#![allow(double_negations)] -#![allow(drop_bounds)] -#![allow(dropping_copy_types)] -#![allow(dropping_references)] -#![allow(clippy::mixed_read_write_in_expression)] -#![allow(clippy::manual_filter_map)] -#![allow(clippy::manual_find_map)] -#![allow(unpredictable_function_pointer_comparisons)] -#![allow(useless_ptr_null_checks)] -#![allow(for_loops_over_fallibles)] -#![allow(forgetting_copy_types)] -#![allow(forgetting_references)] -#![allow(clippy::useless_conversion)] -#![allow(clippy::redundant_pattern_matching)] -#![allow(clippy::match_result_ok)] -#![allow(clippy::non_canonical_clone_impl)] -#![allow(clippy::non_canonical_partial_ord_impl)] -#![allow(clippy::arithmetic_side_effects)] -#![allow(array_into_iter)] -#![allow(invalid_atomic_ordering)] -#![allow(invalid_null_arguments)] -#![allow(invalid_value)] -#![allow(invalid_from_utf8_unchecked)] -#![allow(let_underscore_drop)] -#![allow(clippy::overly_complex_bool_expr)] -#![allow(unexpected_cfgs)] -#![allow(enum_intrinsics_non_enums)] -#![allow(clippy::new_without_default)] -#![allow(clippy::bind_instead_of_map)] -#![allow(clippy::expect_used)] -#![allow(clippy::map_unwrap_or)] -#![allow(clippy::unwrap_used)] -#![allow(clippy::panicking_overflow_checks)] -#![allow(non_fmt_panics)] -#![allow(named_arguments_used_positionally)] -#![allow(clippy::needless_borrow)] -#![allow(clippy::reversed_empty_ranges)] -#![allow(clippy::single_char_add_str)] -#![allow(clippy::module_name_repetitions)] -#![allow(dangling_pointers_from_temporaries)] -#![allow(clippy::missing_const_for_thread_local)] -#![allow(clippy::recursive_format_impl)] -#![allow(unnecessary_transmutes)] -#![allow(undropped_manually_drops)] -#![allow(unknown_lints)] -#![allow(unused_labels)] -#![allow(clippy::unwrap_or_default)] -#![allow(ambiguous_wide_pointer_comparisons)] -#![allow(clippy::invisible_characters)] #![warn(clippy::almost_complete_range)] //~ ERROR: lint `clippy::almost_complete_letter_range` #![warn(clippy::disallowed_names)] //~ ERROR: lint `clippy::blacklisted_name` #![warn(clippy::blocks_in_conditions)] //~ ERROR: lint `clippy::block_in_if_condition_expr` diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index b5d5d07e639a..721bf4734e54 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -3,67 +3,6 @@ // Manual edits will be overwritten. #![allow(clippy::duplicated_attributes)] -#![allow(clippy::almost_complete_range)] -#![allow(clippy::disallowed_names)] -#![allow(clippy::blocks_in_conditions)] -#![allow(clippy::box_collection)] -#![allow(invalid_reference_casting)] -#![allow(suspicious_double_ref_op)] -#![allow(invalid_nan_comparisons)] -#![allow(clippy::redundant_static_lifetimes)] -#![allow(clippy::cognitive_complexity)] -#![allow(clippy::derived_hash_with_manual_eq)] -#![allow(clippy::disallowed_methods)] -#![allow(clippy::disallowed_types)] -#![allow(double_negations)] -#![allow(drop_bounds)] -#![allow(dropping_copy_types)] -#![allow(dropping_references)] -#![allow(clippy::mixed_read_write_in_expression)] -#![allow(clippy::manual_filter_map)] -#![allow(clippy::manual_find_map)] -#![allow(unpredictable_function_pointer_comparisons)] -#![allow(useless_ptr_null_checks)] -#![allow(for_loops_over_fallibles)] -#![allow(forgetting_copy_types)] -#![allow(forgetting_references)] -#![allow(clippy::useless_conversion)] -#![allow(clippy::redundant_pattern_matching)] -#![allow(clippy::match_result_ok)] -#![allow(clippy::non_canonical_clone_impl)] -#![allow(clippy::non_canonical_partial_ord_impl)] -#![allow(clippy::arithmetic_side_effects)] -#![allow(array_into_iter)] -#![allow(invalid_atomic_ordering)] -#![allow(invalid_null_arguments)] -#![allow(invalid_value)] -#![allow(invalid_from_utf8_unchecked)] -#![allow(let_underscore_drop)] -#![allow(clippy::overly_complex_bool_expr)] -#![allow(unexpected_cfgs)] -#![allow(enum_intrinsics_non_enums)] -#![allow(clippy::new_without_default)] -#![allow(clippy::bind_instead_of_map)] -#![allow(clippy::expect_used)] -#![allow(clippy::map_unwrap_or)] -#![allow(clippy::unwrap_used)] -#![allow(clippy::panicking_overflow_checks)] -#![allow(non_fmt_panics)] -#![allow(named_arguments_used_positionally)] -#![allow(clippy::needless_borrow)] -#![allow(clippy::reversed_empty_ranges)] -#![allow(clippy::single_char_add_str)] -#![allow(clippy::module_name_repetitions)] -#![allow(dangling_pointers_from_temporaries)] -#![allow(clippy::missing_const_for_thread_local)] -#![allow(clippy::recursive_format_impl)] -#![allow(unnecessary_transmutes)] -#![allow(undropped_manually_drops)] -#![allow(unknown_lints)] -#![allow(unused_labels)] -#![allow(clippy::unwrap_or_default)] -#![allow(ambiguous_wide_pointer_comparisons)] -#![allow(clippy::invisible_characters)] #![warn(clippy::almost_complete_letter_range)] //~ ERROR: lint `clippy::almost_complete_letter_range` #![warn(clippy::blacklisted_name)] //~ ERROR: lint `clippy::blacklisted_name` #![warn(clippy::block_in_if_condition_expr)] //~ ERROR: lint `clippy::block_in_if_condition_expr` diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index 2487dfc8eba4..579a76db2cf6 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,5 +1,5 @@ error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` - --> tests/ui/rename.rs:67:9 + --> tests/ui/rename.rs:6:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` @@ -8,433 +8,433 @@ LL | #![warn(clippy::almost_complete_letter_range)] = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` - --> tests/ui/rename.rs:68:9 + --> tests/ui/rename.rs:7:9 | LL | #![warn(clippy::blacklisted_name)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_names` error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:69:9 + --> tests/ui/rename.rs:8:9 | LL | #![warn(clippy::block_in_if_condition_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:70:9 + --> tests/ui/rename.rs:9:9 | LL | #![warn(clippy::block_in_if_condition_stmt)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::blocks_in_if_conditions` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:71:9 + --> tests/ui/rename.rs:10:9 | LL | #![warn(clippy::blocks_in_if_conditions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::box_vec` has been renamed to `clippy::box_collection` - --> tests/ui/rename.rs:72:9 + --> tests/ui/rename.rs:11:9 | LL | #![warn(clippy::box_vec)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection` error: lint `clippy::cast_ref_to_mut` has been renamed to `invalid_reference_casting` - --> tests/ui/rename.rs:73:9 + --> tests/ui/rename.rs:12:9 | LL | #![warn(clippy::cast_ref_to_mut)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_reference_casting` error: lint `clippy::clone_double_ref` has been renamed to `suspicious_double_ref_op` - --> tests/ui/rename.rs:74:9 + --> tests/ui/rename.rs:13:9 | LL | #![warn(clippy::clone_double_ref)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `suspicious_double_ref_op` error: lint `clippy::cmp_nan` has been renamed to `invalid_nan_comparisons` - --> tests/ui/rename.rs:75:9 + --> tests/ui/rename.rs:14:9 | LL | #![warn(clippy::cmp_nan)] | ^^^^^^^^^^^^^^^ help: use the new name: `invalid_nan_comparisons` error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` - --> tests/ui/rename.rs:76:9 + --> tests/ui/rename.rs:15:9 | LL | #![warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> tests/ui/rename.rs:77:9 + --> tests/ui/rename.rs:16:9 | LL | #![warn(clippy::cyclomatic_complexity)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` error: lint `clippy::derive_hash_xor_eq` has been renamed to `clippy::derived_hash_with_manual_eq` - --> tests/ui/rename.rs:78:9 + --> tests/ui/rename.rs:17:9 | LL | #![warn(clippy::derive_hash_xor_eq)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::derived_hash_with_manual_eq` error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods` - --> tests/ui/rename.rs:79:9 + --> tests/ui/rename.rs:18:9 | LL | #![warn(clippy::disallowed_method)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods` error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types` - --> tests/ui/rename.rs:80:9 + --> tests/ui/rename.rs:19:9 | LL | #![warn(clippy::disallowed_type)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types` error: lint `clippy::double_neg` has been renamed to `double_negations` - --> tests/ui/rename.rs:81:9 + --> tests/ui/rename.rs:20:9 | LL | #![warn(clippy::double_neg)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `double_negations` error: lint `clippy::drop_bounds` has been renamed to `drop_bounds` - --> tests/ui/rename.rs:82:9 + --> tests/ui/rename.rs:21:9 | LL | #![warn(clippy::drop_bounds)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds` error: lint `clippy::drop_copy` has been renamed to `dropping_copy_types` - --> tests/ui/rename.rs:83:9 + --> tests/ui/rename.rs:22:9 | LL | #![warn(clippy::drop_copy)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `dropping_copy_types` error: lint `clippy::drop_ref` has been renamed to `dropping_references` - --> tests/ui/rename.rs:84:9 + --> tests/ui/rename.rs:23:9 | LL | #![warn(clippy::drop_ref)] | ^^^^^^^^^^^^^^^^ help: use the new name: `dropping_references` error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression` - --> tests/ui/rename.rs:85:9 + --> tests/ui/rename.rs:24:9 | LL | #![warn(clippy::eval_order_dependence)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression` error: lint `clippy::filter_map` has been renamed to `clippy::manual_filter_map` - --> tests/ui/rename.rs:86:9 + --> tests/ui/rename.rs:25:9 | LL | #![warn(clippy::filter_map)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::manual_filter_map` error: lint `clippy::find_map` has been renamed to `clippy::manual_find_map` - --> tests/ui/rename.rs:87:9 + --> tests/ui/rename.rs:26:9 | LL | #![warn(clippy::find_map)] | ^^^^^^^^^^^^^^^^ help: use the new name: `clippy::manual_find_map` error: lint `clippy::fn_address_comparisons` has been renamed to `unpredictable_function_pointer_comparisons` - --> tests/ui/rename.rs:88:9 + --> tests/ui/rename.rs:27:9 | LL | #![warn(clippy::fn_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unpredictable_function_pointer_comparisons` error: lint `clippy::fn_null_check` has been renamed to `useless_ptr_null_checks` - --> tests/ui/rename.rs:89:9 + --> tests/ui/rename.rs:28:9 | LL | #![warn(clippy::fn_null_check)] | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `useless_ptr_null_checks` error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:90:9 + --> tests/ui/rename.rs:29:9 | LL | #![warn(clippy::for_loop_over_option)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:91:9 + --> tests/ui/rename.rs:30:9 | LL | #![warn(clippy::for_loop_over_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:92:9 + --> tests/ui/rename.rs:31:9 | LL | #![warn(clippy::for_loops_over_fallibles)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::forget_copy` has been renamed to `forgetting_copy_types` - --> tests/ui/rename.rs:93:9 + --> tests/ui/rename.rs:32:9 | LL | #![warn(clippy::forget_copy)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_copy_types` error: lint `clippy::forget_ref` has been renamed to `forgetting_references` - --> tests/ui/rename.rs:94:9 + --> tests/ui/rename.rs:33:9 | LL | #![warn(clippy::forget_ref)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_references` error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion` - --> tests/ui/rename.rs:95:9 + --> tests/ui/rename.rs:34:9 | LL | #![warn(clippy::identity_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion` error: lint `clippy::if_let_redundant_pattern_matching` has been renamed to `clippy::redundant_pattern_matching` - --> tests/ui/rename.rs:96:9 + --> tests/ui/rename.rs:35:9 | LL | #![warn(clippy::if_let_redundant_pattern_matching)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_pattern_matching` error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok` - --> tests/ui/rename.rs:97:9 + --> tests/ui/rename.rs:36:9 | LL | #![warn(clippy::if_let_some_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok` error: lint `clippy::incorrect_clone_impl_on_copy_type` has been renamed to `clippy::non_canonical_clone_impl` - --> tests/ui/rename.rs:98:9 + --> tests/ui/rename.rs:37:9 | LL | #![warn(clippy::incorrect_clone_impl_on_copy_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_clone_impl` error: lint `clippy::incorrect_partial_ord_impl_on_ord_type` has been renamed to `clippy::non_canonical_partial_ord_impl` - --> tests/ui/rename.rs:99:9 + --> tests/ui/rename.rs:38:9 | LL | #![warn(clippy::incorrect_partial_ord_impl_on_ord_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_partial_ord_impl` error: lint `clippy::integer_arithmetic` has been renamed to `clippy::arithmetic_side_effects` - --> tests/ui/rename.rs:100:9 + --> tests/ui/rename.rs:39:9 | LL | #![warn(clippy::integer_arithmetic)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::arithmetic_side_effects` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` - --> tests/ui/rename.rs:101:9 + --> tests/ui/rename.rs:40:9 | LL | #![warn(clippy::into_iter_on_array)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter` error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering` - --> tests/ui/rename.rs:102:9 + --> tests/ui/rename.rs:41:9 | LL | #![warn(clippy::invalid_atomic_ordering)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering` error: lint `clippy::invalid_null_ptr_usage` has been renamed to `invalid_null_arguments` - --> tests/ui/rename.rs:103:9 + --> tests/ui/rename.rs:42:9 | LL | #![warn(clippy::invalid_null_ptr_usage)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_null_arguments` error: lint `clippy::invalid_ref` has been renamed to `invalid_value` - --> tests/ui/rename.rs:104:9 + --> tests/ui/rename.rs:43:9 | LL | #![warn(clippy::invalid_ref)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value` error: lint `clippy::invalid_utf8_in_unchecked` has been renamed to `invalid_from_utf8_unchecked` - --> tests/ui/rename.rs:105:9 + --> tests/ui/rename.rs:44:9 | LL | #![warn(clippy::invalid_utf8_in_unchecked)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_from_utf8_unchecked` error: lint `clippy::let_underscore_drop` has been renamed to `let_underscore_drop` - --> tests/ui/rename.rs:106:9 + --> tests/ui/rename.rs:45:9 | LL | #![warn(clippy::let_underscore_drop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `let_underscore_drop` error: lint `clippy::logic_bug` has been renamed to `clippy::overly_complex_bool_expr` - --> tests/ui/rename.rs:107:9 + --> tests/ui/rename.rs:46:9 | LL | #![warn(clippy::logic_bug)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::overly_complex_bool_expr` error: lint `clippy::maybe_misused_cfg` has been renamed to `unexpected_cfgs` - --> tests/ui/rename.rs:108:9 + --> tests/ui/rename.rs:47:9 | LL | #![warn(clippy::maybe_misused_cfg)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unexpected_cfgs` error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums` - --> tests/ui/rename.rs:109:9 + --> tests/ui/rename.rs:48:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` error: lint `clippy::mismatched_target_os` has been renamed to `unexpected_cfgs` - --> tests/ui/rename.rs:110:9 + --> tests/ui/rename.rs:49:9 | LL | #![warn(clippy::mismatched_target_os)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unexpected_cfgs` error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` - --> tests/ui/rename.rs:111:9 + --> tests/ui/rename.rs:50:9 | LL | #![warn(clippy::new_without_default_derive)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map` - --> tests/ui/rename.rs:112:9 + --> tests/ui/rename.rs:51:9 | LL | #![warn(clippy::option_and_then_some)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map` error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:113:9 + --> tests/ui/rename.rs:52:9 | LL | #![warn(clippy::option_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:114:9 + --> tests/ui/rename.rs:53:9 | LL | #![warn(clippy::option_map_unwrap_or)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:115:9 + --> tests/ui/rename.rs:54:9 | LL | #![warn(clippy::option_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:116:9 + --> tests/ui/rename.rs:55:9 | LL | #![warn(clippy::option_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::overflow_check_conditional` has been renamed to `clippy::panicking_overflow_checks` - --> tests/ui/rename.rs:117:9 + --> tests/ui/rename.rs:56:9 | LL | #![warn(clippy::overflow_check_conditional)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::panicking_overflow_checks` error: lint `clippy::panic_params` has been renamed to `non_fmt_panics` - --> tests/ui/rename.rs:118:9 + --> tests/ui/rename.rs:57:9 | LL | #![warn(clippy::panic_params)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics` error: lint `clippy::positional_named_format_parameters` has been renamed to `named_arguments_used_positionally` - --> tests/ui/rename.rs:119:9 + --> tests/ui/rename.rs:58:9 | LL | #![warn(clippy::positional_named_format_parameters)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `named_arguments_used_positionally` error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow` - --> tests/ui/rename.rs:120:9 + --> tests/ui/rename.rs:59:9 | LL | #![warn(clippy::ref_in_deref)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow` error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:121:9 + --> tests/ui/rename.rs:60:9 | LL | #![warn(clippy::result_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:122:9 + --> tests/ui/rename.rs:61:9 | LL | #![warn(clippy::result_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:123:9 + --> tests/ui/rename.rs:62:9 | LL | #![warn(clippy::result_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::reverse_range_loop` has been renamed to `clippy::reversed_empty_ranges` - --> tests/ui/rename.rs:124:9 + --> tests/ui/rename.rs:63:9 | LL | #![warn(clippy::reverse_range_loop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::reversed_empty_ranges` error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str` - --> tests/ui/rename.rs:125:9 + --> tests/ui/rename.rs:64:9 | LL | #![warn(clippy::single_char_push_str)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str` error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` - --> tests/ui/rename.rs:126:9 + --> tests/ui/rename.rs:65:9 | LL | #![warn(clippy::stutter)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `dangling_pointers_from_temporaries` - --> tests/ui/rename.rs:127:9 + --> tests/ui/rename.rs:66:9 | LL | #![warn(clippy::temporary_cstring_as_ptr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `dangling_pointers_from_temporaries` error: lint `clippy::thread_local_initializer_can_be_made_const` has been renamed to `clippy::missing_const_for_thread_local` - --> tests/ui/rename.rs:128:9 + --> tests/ui/rename.rs:67:9 | LL | #![warn(clippy::thread_local_initializer_can_be_made_const)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::missing_const_for_thread_local` error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl` - --> tests/ui/rename.rs:129:9 + --> tests/ui/rename.rs:68:9 | LL | #![warn(clippy::to_string_in_display)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl` error: lint `clippy::transmute_float_to_int` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:130:9 + --> tests/ui/rename.rs:69:9 | LL | #![warn(clippy::transmute_float_to_int)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_int_to_char` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:131:9 + --> tests/ui/rename.rs:70:9 | LL | #![warn(clippy::transmute_int_to_char)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_int_to_float` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:132:9 + --> tests/ui/rename.rs:71:9 | LL | #![warn(clippy::transmute_int_to_float)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_num_to_bytes` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:133:9 + --> tests/ui/rename.rs:72:9 | LL | #![warn(clippy::transmute_num_to_bytes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::undropped_manually_drops` has been renamed to `undropped_manually_drops` - --> tests/ui/rename.rs:134:9 + --> tests/ui/rename.rs:73:9 | LL | #![warn(clippy::undropped_manually_drops)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `undropped_manually_drops` error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints` - --> tests/ui/rename.rs:135:9 + --> tests/ui/rename.rs:74:9 | LL | #![warn(clippy::unknown_clippy_lints)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints` error: lint `clippy::unused_label` has been renamed to `unused_labels` - --> tests/ui/rename.rs:136:9 + --> tests/ui/rename.rs:75:9 | LL | #![warn(clippy::unused_label)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels` error: lint `clippy::unwrap_or_else_default` has been renamed to `clippy::unwrap_or_default` - --> tests/ui/rename.rs:137:9 + --> tests/ui/rename.rs:76:9 | LL | #![warn(clippy::unwrap_or_else_default)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_or_default` error: lint `clippy::vtable_address_comparisons` has been renamed to `ambiguous_wide_pointer_comparisons` - --> tests/ui/rename.rs:138:9 + --> tests/ui/rename.rs:77:9 | LL | #![warn(clippy::vtable_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `ambiguous_wide_pointer_comparisons` error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters` - --> tests/ui/rename.rs:139:9 + --> tests/ui/rename.rs:78:9 | LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters`