From e96b9262304b5ddf75ffa9cb92a8745a8028c861 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 31 May 2025 10:26:18 -0400 Subject: [PATCH 01/10] `clippy_dev`: Move parsing code to it's own module. --- clippy_dev/src/deprecate_lint.rs | 3 +- clippy_dev/src/lib.rs | 2 + clippy_dev/src/new_lint.rs | 3 +- clippy_dev/src/parse.rs | 399 +++++++++++++++++++++++++++++++ clippy_dev/src/rename_lint.rs | 7 +- clippy_dev/src/update_lints.rs | 267 +-------------------- clippy_dev/src/utils.rs | 177 -------------- 7 files changed, 412 insertions(+), 446 deletions(-) create mode 100644 clippy_dev/src/parse.rs diff --git a/clippy_dev/src/deprecate_lint.rs b/clippy_dev/src/deprecate_lint.rs index 3bdc5b277232..9f6d1984eb57 100644 --- a/clippy_dev/src/deprecate_lint.rs +++ b/clippy_dev/src/deprecate_lint.rs @@ -1,4 +1,5 @@ -use crate::update_lints::{DeprecatedLint, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints}; +use crate::parse::{DeprecatedLint, Lint, find_lint_decls, read_deprecated_lints}; +use crate::update_lints::generate_lint_files; use crate::utils::{UpdateMode, Version}; use std::ffi::OsStr; use std::path::{Path, PathBuf}; diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 3361443196ab..578753735c8e 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -34,3 +34,5 @@ pub mod setup; pub mod sync; pub mod update_lints; pub mod utils; + +mod parse; diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 4121daa85e6d..d73802de1706 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::{RustSearcher, Token}; +use crate::utils::Version; use clap::ValueEnum; use indoc::{formatdoc, writedoc}; use std::fmt::{self, Write as _}; diff --git a/clippy_dev/src/parse.rs b/clippy_dev/src/parse.rs new file mode 100644 index 000000000000..2b2e5fcf9131 --- /dev/null +++ b/clippy_dev/src/parse.rs @@ -0,0 +1,399 @@ +use crate::utils::{ErrAction, File, expect_action}; +use core::ops::Range; +use core::slice; +use rustc_lexer::{self as lexer, FrontmatterAllowed}; +use std::fs; +use std::path::{Path, PathBuf}; +use walkdir::{DirEntry, WalkDir}; + +#[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, +} + +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_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. + 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::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; + }, + #[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() = 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)) + } +} + +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, is_raw) = if let Some(s) = s.strip_prefix("r") { + (s.trim_matches('#'), true) + } else { + (s, false) + }; + let s = s + .strip_prefix('"') + .and_then(|s| s.strip_suffix('"')) + .unwrap_or_else(|| panic!("expected quoted string, found `{s}`")); + + if is_raw { + s.into() + } else { + let mut res = String::with_capacity(s.len()); + rustc_literal_escaper::unescape_str(s, &mut |_, ch| { + if let Ok(ch) = ch { + res.push(ch); + } + }); + res + } +} + +fn parse_str_single_line(path: &Path, s: &str) -> String { + let value = parse_str_lit(s); + assert!( + !value.contains('\n'), + "error parsing `{}`: `{s}` should be a single line string", + path.display(), + ); + value +} diff --git a/clippy_dev/src/rename_lint.rs b/clippy_dev/src/rename_lint.rs index d62597428e21..a46c03a61fee 100644 --- a/clippy_dev/src/rename_lint.rs +++ b/clippy_dev/src/rename_lint.rs @@ -1,7 +1,8 @@ -use crate::update_lints::{RenamedLint, find_lint_decls, generate_lint_files, read_deprecated_lints}; +use crate::parse::{RenamedLint, RustSearcher, Token, find_lint_decls, read_deprecated_lints}; +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 rustc_lexer::TokenKind; use std::ffi::OsString; diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 5f6e874ffe25..87f81ecd6a46 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::{DeprecatedLint, Lint, RenamedLint, RustSearcher, Token, find_lint_decls, read_deprecated_lints}; +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::{self, Path}; 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\ @@ -200,260 +196,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..fc1a07a14314 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}; @@ -426,181 +424,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) { From 2ffe004daa7a4aad54648d422fdec222ef9d2949 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 6 Jun 2025 13:28:46 -0400 Subject: [PATCH 02/10] `clippy_dev` parsing changes: * Keep results together. * Keep lints in a map to detect duplicate names. * Store the crate a lint comes from. --- clippy_dev/src/deprecate_lint.rs | 175 ++++++-------- clippy_dev/src/fmt.rs | 2 +- clippy_dev/src/lib.rs | 6 +- clippy_dev/src/parse.rs | 364 ++++++++++++++++------------- clippy_dev/src/rename_lint.rs | 170 +++++++------- clippy_dev/src/update_lints.rs | 168 ++++++------- clippy_dev/src/utils.rs | 4 +- clippy_lints/src/declared_lints.rs | 30 +-- tests/ui/rename.fixed | 61 ----- tests/ui/rename.rs | 61 ----- tests/ui/rename.stderr | 146 ++++++------ 11 files changed, 544 insertions(+), 643 deletions(-) diff --git a/clippy_dev/src/deprecate_lint.rs b/clippy_dev/src/deprecate_lint.rs index 9f6d1984eb57..c1fed600014d 100644 --- a/clippy_dev/src/deprecate_lint.rs +++ b/clippy_dev/src/deprecate_lint.rs @@ -1,9 +1,10 @@ -use crate::parse::{DeprecatedLint, Lint, find_lint_decls, read_deprecated_lints}; +use crate::parse::{ActiveLint, DeprecatedLint, Lint, LintList}; use crate::update_lints::generate_lint_files; -use crate::utils::{UpdateMode, Version}; +use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists}; +use core::mem; +use rustc_data_structures::fx::FxHashMap; use std::ffi::OsStr; -use std::path::{Path, PathBuf}; -use std::{fs, io}; +use std::path::Path; /// Runs the `deprecate` command /// @@ -14,73 +15,48 @@ use std::{fs, io}; /// # Panics /// /// If a file path could not read from or written to +#[expect(clippy::similar_names)] pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { if let Some((prefix, _)) = name.split_once("::") { 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 list = LintList::collect(); + let Some(entry) = list.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 Lint::Active(lint) = mem::replace( + entry, + Lint::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, &list.lints); + generate_lint_files(UpdateMode::Change, &list); + 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, lints: &FxHashMap) { 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) { @@ -109,54 +85,55 @@ 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)?; + if lints.values().any(|l| { + if let Lint::Active(l) = l { + l.krate == lint.krate && l.module.starts_with(&lint.module) } 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())); + false } - - remove_test_assets(name); - remove_lint(name, lints); - return Ok(true); + }) { + // Try to delete a sub-module that matches the lint's name + let removed_mod = if lint.path.file_name().map(OsStr::as_encoded_bytes) == Some(b"mod.rs") { + let mut path = lint.path.clone(); + path.set_file_name(name); + path.set_extension("rs"); + delete_file_if_exists(&path) + } else { + false + }; + + FileUpdater::default().update_file(&lint.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.span.start as usize { + ( + mod_start, + mod_start + mod_decl.len(), + lint.span.start as usize, + lint.span.end as usize, + ) + } else { + ( + lint.span.start as usize, + lint.span.end as usize, + mod_start, + mod_start + mod_decl.len(), + ) + } + } else { + (lint.span.start as usize, lint.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.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 578753735c8e..ee3ab204b10f 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,10 @@ )] #![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_lexer; extern crate rustc_literal_escaper; diff --git a/clippy_dev/src/parse.rs b/clippy_dev/src/parse.rs index 2b2e5fcf9131..07be3cb30b89 100644 --- a/clippy_dev/src/parse.rs +++ b/clippy_dev/src/parse.rs @@ -1,10 +1,10 @@ -use crate::utils::{ErrAction, File, expect_action}; +use crate::utils::{ErrAction, File, expect_action, walk_dir_no_dot_or_target}; use core::ops::Range; use core::slice; +use rustc_data_structures::fx::FxHashMap; use rustc_lexer::{self as lexer, FrontmatterAllowed}; use std::fs; -use std::path::{Path, PathBuf}; -use walkdir::{DirEntry, WalkDir}; +use std::path::{self, Path, PathBuf}; #[derive(Clone, Copy)] pub enum Token<'a> { @@ -76,6 +76,7 @@ impl<'txt> RustSearcher<'txt> { 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; @@ -83,7 +84,7 @@ impl<'txt> RustSearcher<'txt> { } /// Consumes the next token if it matches the requested value and captures the value if - /// requested. Returns true if a token was matched. + /// 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) { @@ -123,7 +124,7 @@ impl<'txt> RustSearcher<'txt> { }, (Token::DoubleColon, lexer::TokenKind::Colon) => { self.step(); - if !self.at_end() && matches!(self.next_token.kind, lexer::TokenKind::Colon) { + if matches!(self.next_token.kind, lexer::TokenKind::Colon) { self.step(); return true; } @@ -182,188 +183,220 @@ impl<'txt> RustSearcher<'txt> { } } -pub struct Lint { - pub name: String, - pub group: String, +pub struct ActiveLint { + pub krate: String, pub module: String, + pub group: String, pub path: PathBuf, - pub declaration_range: Range, + pub span: 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; +pub enum Lint { + Active(ActiveLint), + Deprecated(DeprecatedLint), + Renamed(RenamedLint), +} + +pub struct LintList { + pub lints: FxHashMap, + pub deprecated_span: Range, + pub renamed_span: Range, +} +impl LintList { + #[expect(clippy::default_trait_access)] + pub fn collect() -> Self { + // 2025-05: Initial capacities should fit everything without reallocating. + let mut parser = Parser { + lints: FxHashMap::with_capacity_and_hasher(1000, Default::default()), + deprecated_span: 0..0, + renamed_span: 0..0, + contents: String::with_capacity(1024 * 128 * 3), }; - 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, - ); - } + parser.parse_src_files(); + parser.parse_deprecated_lints(); + + LintList { + lints: parser.lints, + deprecated_span: parser.deprecated_span, + renamed_span: parser.renamed_span, } } - 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 +struct Parser { + lints: FxHashMap, + deprecated_span: Range, + renamed_span: Range, + contents: String, +} +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" { + crate_path.push_str("/src"); + let krate = &crate_path[..crate_path.len() - 4]; + 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, "::") + }; + self.parse_src_file(e.path(), krate, &module); + } } } - } else { - None } - }) -} + } + + /// Parse a source file looking for `declare_clippy_lint` macro invocations. + fn parse_src_file(&mut self, path: &Path, krate: &str, module: &str) { + #[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, + ]; -/// 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, - }); + File::open_read_to_cleared_string(path, &mut self.contents); + let mut searcher = RustSearcher::new(&self.contents); + #[expect(clippy::cast_possible_truncation)] + while searcher.find_token(Ident("declare_clippy_lint")) { + let start = searcher.pos() - "declare_clippy_lint".len() as u32; + let (mut name, mut group) = ("", ""); + if searcher.match_tokens(DECL_TOKENS, &mut [&mut name, &mut group]) && searcher.find_token(CloseBrace) { + self.lints.insert( + name.to_ascii_lowercase(), + Lint::Active(ActiveLint { + krate: krate.into(), + module: module.into(), + group: group.into(), + path: path.into(), + span: start..searcher.pos(), + }), + ); + } } } -} -#[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" - ); + 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, + ]; - 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), - }); + let path = Path::new("clippy_lints/src/deprecated_lints.rs"); + File::open_read_to_cleared_string(path, &mut self.contents); + + let mut searcher = RustSearcher::new(&self.contents); + // First instance is the macro definition. + assert!( + searcher.find_token(Ident("declare_with_version")), + "error parsing `clippy_lints/src/deprecated_lints.rs`" + ); + + if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(DEPRECATED_TOKENS, &mut []) { + let start = searcher.pos(); + let mut end = start; + let mut version = ""; + let mut name = ""; + let mut reason = ""; + while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) { + self.lints.insert( + parse_clippy_lint_name(path, name), + Lint::Deprecated(DeprecatedLint { + reason: parse_str_single_line(path, reason), + version: parse_str_single_line(path, version), + }), + ); + end = searcher.pos(); + } + self.deprecated_span = start..end; + } else { + panic!("error reading deprecated lints"); } - } 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), - }); + if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(RENAMED_TOKENS, &mut []) { + let start = searcher.pos(); + let mut end = start; + 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]) { + self.lints.insert( + parse_clippy_lint_name(path, old_name), + Lint::Renamed(RenamedLint { + new_name: parse_maybe_clippy_lint_name(path, new_name), + version: parse_str_single_line(path, version), + }), + ); + end = searcher.pos(); + } + self.renamed_span = start..end; + } else { + panic!("error reading renamed lints"); } - } 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) +fn assert_lint_name(path: &Path, s: &str) { + assert!( + s.bytes() + .all(|c| matches!(c, b'_' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z')), + "error parsing `{}`: `{s}` is not a valid lint name", + path.display(), + ); } -/// Removes the line splices and surrounding quotes from a string literal fn parse_str_lit(s: &str) -> String { let (s, is_raw) = if let Some(s) = s.strip_prefix("r") { (s.trim_matches('#'), true) @@ -389,11 +422,30 @@ fn parse_str_lit(s: &str) -> String { } fn parse_str_single_line(path: &Path, s: &str) -> String { - let value = parse_str_lit(s); + let s = parse_str_lit(s); assert!( - !value.contains('\n'), + !s.contains('\n'), "error parsing `{}`: `{s}` should be a single line string", path.display(), ); - value + s +} + +fn parse_clippy_lint_name(path: &Path, s: &str) -> String { + let mut s = parse_str_lit(s); + let Some(name) = s.strip_prefix("clippy::") else { + panic!( + "error parsing `{}`: `{s}` needs to have the `clippy::` prefix", + path.display() + ); + }; + assert_lint_name(path, name); + s.drain(.."clippy::".len()); + s +} + +fn parse_maybe_clippy_lint_name(path: &Path, s: &str) -> String { + let s = parse_str_lit(s); + assert_lint_name(path, s.strip_prefix("clippy::").unwrap_or(&s)); + s } diff --git a/clippy_dev/src/rename_lint.rs b/clippy_dev/src/rename_lint.rs index a46c03a61fee..b770d18d8cf5 100644 --- a/clippy_dev/src/rename_lint.rs +++ b/clippy_dev/src/rename_lint.rs @@ -1,9 +1,11 @@ -use crate::parse::{RenamedLint, RustSearcher, Token, find_lint_decls, read_deprecated_lints}; +use crate::parse::{Lint, LintList, RenamedLint, RustSearcher, Token}; use crate::update_lints::generate_lint_files; use crate::utils::{ 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, StdEntry}; use rustc_lexer::TokenKind; use std::ffi::OsString; use std::fs; @@ -24,7 +26,7 @@ use std::path::Path; /// * If either lint name has a prefix /// * If `old_name` doesn't name an existing lint. /// * If `old_name` names a deprecated or renamed lint. -#[expect(clippy::too_many_lines)] +#[expect(clippy::too_many_lines, clippy::similar_names)] pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: bool) { if let Some((prefix, _)) = old_name.split_once("::") { panic!("`{old_name}` should not contain the `{prefix}` prefix"); @@ -34,67 +36,58 @@ 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 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 mut list = LintList::collect(); - 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 list.lints.values_mut() { + if let Lint::Renamed(lint) = lint + && 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(old_entry) = list.lints.get_mut(old_name) else { + eprintln!("error: failed to find lint `{old_name}`"); + return; + }; + let Lint::Active(mut lint) = mem::replace( + old_entry, + Lint::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 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 is_unique_mod = list.lints.values().any(|x| { + if let Lint::Active(x) = x { + x.module == lint.module + } else { + false + } + }); if is_unique_mod { if delete_file_if_exists(lint.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]; + let mut start = &src[..lint.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.span.end as usize..]; if end.starts_with("\n\n") { end = &end[1..]; } @@ -103,10 +96,8 @@ 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]; + delete_test_files(old_name, &list.lints); + } else if let StdEntry::Vacant(entry) = list.lints.entry(new_name.to_owned()) { if lint.module.ends_with(old_name) && lint .path @@ -116,6 +107,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b let mut new_path = lint.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()) { + lint.path = new_path.into(); mod_edit = ModEdit::Rename; } @@ -123,9 +115,8 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b lint.module.truncate(mod_len - old_name.len()); lint.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::Active(lint)); + rename_test_files(old_name, new_name, &list.lints); } else { println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); println!("Since `{new_name}` already exists the existing code has not been changed"); @@ -133,13 +124,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, &list); if uplift { println!("Uplifted `clippy::{old_name}` as `{new_name}`"); @@ -164,47 +155,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 { @@ -219,21 +230,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 { @@ -248,11 +260,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()); } } @@ -296,7 +308,7 @@ 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 diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 87f81ecd6a46..639f6ccbd009 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,9 +1,8 @@ -use crate::parse::{DeprecatedLint, Lint, RenamedLint, RustSearcher, Token, find_lint_decls, read_deprecated_lints}; +use crate::parse::{Lint, LintList}; use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; use itertools::Itertools; -use std::collections::HashSet; use std::fmt::Write; -use std::path::{self, Path}; +use std::path::Path; 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\ @@ -21,91 +20,86 @@ 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); + generate_lint_files(update_mode, &LintList::collect()); } -#[expect(clippy::too_many_lines)] -pub fn generate_lint_files( - update_mode: UpdateMode, - lints: &[Lint], - deprecated: &[DeprecatedLint], - renamed: &[RenamedLint], -) { +#[expect(clippy::too_many_lines, clippy::similar_names)] +pub fn generate_lint_files(update_mode: UpdateMode, list: &LintList) { let mut updater = FileUpdater::default(); + + let mut lints: Vec<_> = list.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(list.lints.len()); + let mut deprecated = Vec::with_capacity(list.lints.len()); + let mut renamed = Vec::with_capacity(list.lints.len()); + for (name, lint) in lints { + match lint { + Lint::Active(lint) => active.push((name, lint)), + Lint::Deprecated(lint) => deprecated.push((name, lint)), + Lint::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[..list.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[list.deprecated_span.end as usize..list.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[list.renamed_span.end as usize..]); UpdateStatus::from_changed(src != dst) }, ); @@ -115,8 +109,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) @@ -127,47 +121,33 @@ 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)); + active = tail; + updater.update_file_checked( "cargo dev update_lints", update_mode, - Path::new(crate_name).join("src/lib.rs"), + Path::new(&first_lint.krate).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(); + } } }, ), @@ -175,15 +155,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(&first_lint.krate).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"); @@ -192,7 +176,3 @@ pub fn generate_lint_files( ); } } - -fn round_to_fifty(count: usize) -> usize { - count / 50 * 50 -} diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index fc1a07a14314..8ff9f2a775e4 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -579,8 +579,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/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` From 107ab93958100e937d14ec1134105ec67fe08a05 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 9 Jul 2025 11:15:51 -0400 Subject: [PATCH 03/10] `clippy_dev`: Keep a source map when parsing files. --- clippy_dev/Cargo.toml | 1 + clippy_dev/src/deprecate_lint.rs | 28 ++++---- clippy_dev/src/lib.rs | 2 + clippy_dev/src/parse.rs | 67 ++++++++++-------- clippy_dev/src/rename_lint.rs | 48 ++++++------- clippy_dev/src/source_map.rs | 113 +++++++++++++++++++++++++++++++ clippy_dev/src/update_lints.rs | 31 +++++---- clippy_dev/src/utils.rs | 15 ++-- 8 files changed, 214 insertions(+), 91 deletions(-) create mode 100644 clippy_dev/src/source_map.rs 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 c1fed600014d..8a19717737ee 100644 --- a/clippy_dev/src/deprecate_lint.rs +++ b/clippy_dev/src/deprecate_lint.rs @@ -1,8 +1,7 @@ -use crate::parse::{ActiveLint, DeprecatedLint, Lint, LintList}; +use crate::parse::{ActiveLint, DeprecatedLint, Lint, 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 rustc_data_structures::fx::FxHashMap; use std::ffi::OsStr; use std::path::Path; @@ -15,14 +14,13 @@ use std::path::Path; /// # Panics /// /// If a file path could not read from or written to -#[expect(clippy::similar_names)] pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { if let Some((prefix, _)) = name.split_once("::") { panic!("`{name}` should not contain the `{prefix}` prefix"); } - let mut list = LintList::collect(); - let Some(entry) = list.lints.get_mut(name) else { + let mut data = ParsedData::collect(); + let Some(entry) = data.lints.get_mut(name) else { eprintln!("error: failed to find lint `{name}`"); return; }; @@ -37,13 +35,13 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { return; }; - remove_lint_declaration(name, &lint, &list.lints); - generate_lint_files(UpdateMode::Change, &list); + 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, lint: &ActiveLint, lints: &FxHashMap) { +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); @@ -85,16 +83,18 @@ fn remove_lint_declaration(name: &str, lint: &ActiveLint, lints: &FxHashMap { @@ -184,11 +185,8 @@ impl<'txt> RustSearcher<'txt> { } pub struct ActiveLint { - pub krate: String, - pub module: String, pub group: String, - pub path: PathBuf, - pub span: Range, + pub span: Span, } pub struct DeprecatedLint { @@ -207,25 +205,27 @@ pub enum Lint { Renamed(RenamedLint), } -pub struct LintList { +pub struct ParsedData { + pub source_map: SourceMap, pub lints: FxHashMap, pub deprecated_span: Range, pub renamed_span: Range, } -impl LintList { +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()), deprecated_span: 0..0, renamed_span: 0..0, - contents: String::with_capacity(1024 * 128 * 3), }; parser.parse_src_files(); parser.parse_deprecated_lints(); - LintList { + ParsedData { + source_map: parser.source_map, lints: parser.lints, deprecated_span: parser.deprecated_span, renamed_span: parser.renamed_span, @@ -234,10 +234,10 @@ impl LintList { } struct Parser { + source_map: SourceMap, lints: FxHashMap, deprecated_span: Range, renamed_span: Range, - contents: String, } impl Parser { /// Parses all source files looking for lint declarations (`declare_clippy_lint! { .. }`). @@ -251,8 +251,9 @@ impl Parser { continue; }; if crate_path.starts_with("clippy_lints") && crate_path != "clippy_lints_internal" { - crate_path.push_str("/src"); - let krate = &crate_path[..crate_path.len() - 4]; + 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() @@ -271,7 +272,8 @@ impl Parser { }; path.replace(path::MAIN_SEPARATOR, "::") }; - self.parse_src_file(e.path(), krate, &module); + let file = self.source_map.load_new_file(e.path(), krate, module); + self.parse_src_file(file); } } } @@ -279,7 +281,7 @@ impl Parser { } /// Parse a source file looking for `declare_clippy_lint` macro invocations. - fn parse_src_file(&mut self, path: &Path, krate: &str, module: &str) { + fn parse_src_file(&mut self, file: SourceFile) { #[allow(clippy::enum_glob_use)] use Token::*; #[rustfmt::skip] @@ -292,8 +294,7 @@ impl Parser { Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, ]; - File::open_read_to_cleared_string(path, &mut self.contents); - let mut searcher = RustSearcher::new(&self.contents); + let mut searcher = RustSearcher::new(&self.source_map.files[file].contents); #[expect(clippy::cast_possible_truncation)] while searcher.find_token(Ident("declare_clippy_lint")) { let start = searcher.pos() - "declare_clippy_lint".len() as u32; @@ -302,11 +303,12 @@ impl Parser { self.lints.insert( name.to_ascii_lowercase(), Lint::Active(ActiveLint { - krate: krate.into(), - module: module.into(), group: group.into(), - path: path.into(), - span: start..searcher.pos(), + span: Span { + file, + start, + end: searcher.pos(), + }, }), ); } @@ -334,10 +336,15 @@ impl Parser { Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket, ]; - let path = Path::new("clippy_lints/src/deprecated_lints.rs"); - File::open_read_to_cleared_string(path, &mut self.contents); + 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 = &self.source_map.files[file]; - let mut searcher = RustSearcher::new(&self.contents); + let mut searcher = RustSearcher::new(&file.contents); // First instance is the macro definition. assert!( searcher.find_token(Ident("declare_with_version")), @@ -352,10 +359,10 @@ impl Parser { let mut reason = ""; while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) { self.lints.insert( - parse_clippy_lint_name(path, name), + parse_clippy_lint_name(&file.path, name), Lint::Deprecated(DeprecatedLint { - reason: parse_str_single_line(path, reason), - version: parse_str_single_line(path, version), + reason: parse_str_single_line(&file.path, reason), + version: parse_str_single_line(&file.path, version), }), ); end = searcher.pos(); @@ -373,10 +380,10 @@ impl Parser { let mut new_name = ""; while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut old_name, &mut new_name]) { self.lints.insert( - parse_clippy_lint_name(path, old_name), + parse_clippy_lint_name(&file.path, old_name), Lint::Renamed(RenamedLint { - new_name: parse_maybe_clippy_lint_name(path, new_name), - version: parse_str_single_line(path, version), + new_name: parse_maybe_clippy_lint_name(&file.path, new_name), + version: parse_str_single_line(&file.path, version), }), ); end = searcher.pos(); diff --git a/clippy_dev/src/rename_lint.rs b/clippy_dev/src/rename_lint.rs index b770d18d8cf5..41afa56c670b 100644 --- a/clippy_dev/src/rename_lint.rs +++ b/clippy_dev/src/rename_lint.rs @@ -1,4 +1,4 @@ -use crate::parse::{Lint, LintList, RenamedLint, RustSearcher, Token}; +use crate::parse::{Lint, ParsedData, RenamedLint, RustSearcher, Token}; use crate::update_lints::generate_lint_files; use crate::utils::{ ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists, @@ -26,7 +26,7 @@ use std::path::Path; /// * If either lint name has a prefix /// * If `old_name` doesn't name an existing lint. /// * If `old_name` names a deprecated or renamed lint. -#[expect(clippy::too_many_lines, clippy::similar_names)] +#[expect(clippy::too_many_lines)] pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: bool) { if let Some((prefix, _)) = old_name.split_once("::") { panic!("`{old_name}` should not contain the `{prefix}` prefix"); @@ -36,7 +36,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } let mut updater = FileUpdater::default(); - let mut list = LintList::collect(); + let mut data = ParsedData::collect(); // Update any existing renames let new_name_prefixed = if uplift { @@ -44,7 +44,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } else { String::from_iter(["clippy::", new_name]) }; - for lint in list.lints.values_mut() { + for lint in data.lints.values_mut() { if let Lint::Renamed(lint) = lint && lint.new_name.strip_prefix("clippy::") == Some(old_name) { @@ -53,11 +53,11 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } // Mark the lint as renamed - let Some(old_entry) = list.lints.get_mut(old_name) else { + let Some(old_entry) = data.lints.get_mut(old_name) else { eprintln!("error: failed to find lint `{old_name}`"); return; }; - let Lint::Active(mut lint) = mem::replace( + let Lint::Active(lint) = mem::replace( old_entry, Lint::Renamed(RenamedLint { new_name: new_name_prefixed, @@ -70,19 +70,20 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b let mut mod_edit = ModEdit::None; if uplift { - let is_unique_mod = list.lints.values().any(|x| { + let lint_file = &data.source_map.files[lint.span.file]; + let is_unique_mod = data.lints.values().any(|x| { if let Lint::Active(x) = x { - x.module == lint.module + data.source_map.files[x.span.file].module == lint_file.module } else { false } }); 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 { + updater.update_file(&lint_file.path, &mut |_, src, dst| -> UpdateStatus { let mut start = &src[..lint.span.start as usize]; if start.ends_with("\n\n") { start = &start[..start.len() - 1]; @@ -96,27 +97,28 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b UpdateStatus::Changed }); } - delete_test_files(old_name, &list.lints); - } else if let StdEntry::Vacant(entry) = list.lints.entry(new_name.to_owned()) { - if lint.module.ends_with(old_name) - && lint + delete_test_files(old_name, &data.lints); + } else if let StdEntry::Vacant(entry) = data.lints.entry(new_name.to_owned()) { + let lint_file = &mut data.source_map.files[lint.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()) { - lint.path = new_path.into(); + 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); + } } entry.insert(Lint::Active(lint)); - rename_test_files(old_name, new_name, &list.lints); + 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"); @@ -130,7 +132,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b updater.update_file(e.path(), &mut update_fn); } } - generate_lint_files(UpdateMode::Change, &list); + generate_lint_files(UpdateMode::Change, &data); if uplift { println!("Uplifted `clippy::{old_name}` as `{new_name}`"); 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 639f6ccbd009..cf5c986984f5 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,4 +1,4 @@ -use crate::parse::{Lint, LintList}; +use crate::parse::{Lint, ParsedData}; use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; use itertools::Itertools; use std::fmt::Write; @@ -20,14 +20,14 @@ 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) { - generate_lint_files(update_mode, &LintList::collect()); + generate_lint_files(update_mode, &ParsedData::collect()); } -#[expect(clippy::too_many_lines, clippy::similar_names)] -pub fn generate_lint_files(update_mode: UpdateMode, list: &LintList) { +#[expect(clippy::too_many_lines)] +pub fn generate_lint_files(update_mode: UpdateMode, data: &ParsedData) { let mut updater = FileUpdater::default(); - let mut lints: Vec<_> = list.lints.iter().map(|(x, y)| (&**x, y)).collect(); + let mut lints: Vec<_> = data.lints.iter().map(|(x, y)| (&**x, y)).collect(); lints.sort_by_key(|&(x, _)| x); updater.update_file_checked( @@ -45,17 +45,17 @@ pub fn generate_lint_files(update_mode: UpdateMode, list: &LintList) { ), ); - let mut active = Vec::with_capacity(list.lints.len()); - let mut deprecated = Vec::with_capacity(list.lints.len()); - let mut renamed = Vec::with_capacity(list.lints.len()); + 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 { - Lint::Active(lint) => active.push((name, lint)), + Lint::Active(lint) => active.push((name, &data.source_map.files[lint.span.file])), Lint::Deprecated(lint) => deprecated.push((name, lint)), Lint::Renamed(lint) => renamed.push((name, lint)), } } - active.sort_by(|(_, x), (_, y)| (&*x.krate, &*x.module).cmp(&(&*y.krate, &*y.module))); + 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; @@ -81,7 +81,7 @@ pub fn generate_lint_files(update_mode: UpdateMode, list: &LintList) { update_mode, "clippy_lints/src/deprecated_lints.rs", &mut |_, src, dst| { - dst.push_str(&src[..list.deprecated_span.start as usize]); + dst.push_str(&src[..data.deprecated_span.start as usize]); for &(name, lint) in &deprecated { write!( dst, @@ -90,7 +90,7 @@ pub fn generate_lint_files(update_mode: UpdateMode, list: &LintList) { ) .unwrap(); } - dst.push_str(&src[list.deprecated_span.end as usize..list.renamed_span.start as usize]); + dst.push_str(&src[data.deprecated_span.end as usize..data.renamed_span.start as usize]); for &(name, lint) in &renamed { write!( dst, @@ -99,7 +99,7 @@ pub fn generate_lint_files(update_mode: UpdateMode, list: &LintList) { ) .unwrap(); } - dst.push_str(&src[list.renamed_span.end as usize..]); + dst.push_str(&src[data.renamed_span.end as usize..]); UpdateStatus::from_changed(src != dst) }, ); @@ -134,12 +134,13 @@ pub fn generate_lint_files(update_mode: UpdateMode, list: &LintList) { 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(&first_lint.krate).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`", @@ -155,7 +156,7 @@ pub fn generate_lint_files(update_mode: UpdateMode, list: &LintList) { updater.update_file_checked( "cargo dev update_lints", update_mode, - Path::new(&first_lint.krate).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"); diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index 8ff9f2a775e4..e8f91385320b 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -69,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 { @@ -80,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 { From 7bc7fe5e48fb856168feed56905d5eb40e55ad07 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 11 Jul 2025 05:34:36 -0400 Subject: [PATCH 04/10] `clippy_dev`: print parse errors with location info. --- clippy_dev/src/deprecate_lint.rs | 35 +-- clippy_dev/src/new_lint.rs | 8 +- clippy_dev/src/parse.rs | 481 ++++++++++++++++++++++++------- clippy_dev/src/rename_lint.rs | 62 ++-- clippy_dev/src/update_lints.rs | 10 +- 5 files changed, 434 insertions(+), 162 deletions(-) diff --git a/clippy_dev/src/deprecate_lint.rs b/clippy_dev/src/deprecate_lint.rs index 8a19717737ee..b845c033d94f 100644 --- a/clippy_dev/src/deprecate_lint.rs +++ b/clippy_dev/src/deprecate_lint.rs @@ -1,4 +1,4 @@ -use crate::parse::{ActiveLint, DeprecatedLint, Lint, ParsedData}; +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; @@ -24,9 +24,9 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { eprintln!("error: failed to find lint `{name}`"); return; }; - let Lint::Active(lint) = mem::replace( - entry, - Lint::Deprecated(DeprecatedLint { + let LintKind::Active(lint) = mem::replace( + &mut entry.kind, + LintKind::Deprecated(DeprecatedLint { reason: reason.into(), version: clippy_version.rust_display().to_string(), }), @@ -83,14 +83,10 @@ fn remove_lint_declaration(name: &str, lint: &ActiveLint, data: &ParsedData) { } } - let lint_file = &data.source_map.files[lint.span.file]; + let lint_file = &data.source_map.files[lint.decl_span.file]; if data.lints.values().any(|l| { - if let Lint::Active(l) = l { - let other_file = &data.source_map.files[l.span.file]; - other_file.krate == lint_file.krate && other_file.module.starts_with(&lint_file.module) - } else { - false - } + 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") { @@ -107,23 +103,28 @@ fn remove_lint_declaration(name: &str, lint: &ActiveLint, data: &ParsedData) { && let mod_decl = format!("\nmod {name};") && let Some(mod_start) = src.find(&mod_decl) { - if mod_start < lint.span.start as usize { + if mod_start < lint.decl_span.start as usize { ( mod_start, mod_start + mod_decl.len(), - lint.span.start as usize, - lint.span.end as usize, + lint.decl_span.start as usize, + lint.decl_span.end as usize, ) } else { ( - lint.span.start as usize, - lint.span.end as usize, + lint.decl_span.start as usize, + lint.decl_span.end as usize, mod_start, mod_start + mod_decl.len(), ) } } else { - (lint.span.start as usize, lint.span.end as usize, src.len(), src.len()) + ( + 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]); diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index d73802de1706..3d27dc438987 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,4 +1,4 @@ -use crate::parse::{RustSearcher, Token}; +use crate::parse::{Capture, RustSearcher, Token}; use crate::utils::Version; use clap::ValueEnum; use indoc::{formatdoc, writedoc}; @@ -523,6 +523,7 @@ 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); + let mut captures = [Capture::EMPTY]; while let Some(name) = searcher.find_capture_token(CaptureIdent) { match name { "declare_clippy_lint" => { @@ -531,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 index d7c316460e89..703a0922709a 100644 --- a/clippy_dev/src/parse.rs +++ b/clippy_dev/src/parse.rs @@ -4,13 +4,17 @@ use core::ops::Range; use core::slice; use rustc_data_structures::fx::FxHashMap; use rustc_lexer::{self as lexer, FrontmatterAllowed}; -use std::fs; +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, @@ -24,6 +28,7 @@ pub enum Token<'a> { Comma, Eq, Lifetime, + Literal, Lt, Gt, OpenBrace, @@ -33,6 +38,26 @@ pub enum Token<'a> { 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>, @@ -52,11 +77,34 @@ impl<'txt> RustSearcher<'txt> { } } + #[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 @@ -86,7 +134,7 @@ impl<'txt> RustSearcher<'txt> { /// 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 { + fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool { loop { match (token, self.next_token.kind) { (_, lexer::TokenKind::Whitespace) @@ -95,7 +143,9 @@ impl<'txt> RustSearcher<'txt> { lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. }, ) => self.step(), (Token::AnyComment, _) => return true, - (Token::Bang, lexer::TokenKind::Bang) + (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) @@ -115,10 +165,31 @@ impl<'txt> RustSearcher<'txt> { 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; @@ -142,7 +213,7 @@ impl<'txt> RustSearcher<'txt> { }, ) | (Token::CaptureIdent, lexer::TokenKind::Ident) => { - **captures.next().unwrap() = self.peek_text(); + *captures.next().unwrap() = Capture { pos: self.pos, len: self.next_token.len }; self.step(); return true; }, @@ -153,8 +224,8 @@ impl<'txt> RustSearcher<'txt> { #[must_use] pub fn find_token(&mut self, token: Token<'_>) -> bool { - let mut capture = [].iter_mut(); - while !self.read_token(token, &mut capture) { + let mut captures = [].iter_mut(); + while !self.read_token(token, &mut captures) { self.step(); if self.at_end() { return false; @@ -165,20 +236,25 @@ impl<'txt> RustSearcher<'txt> { #[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) { + let mut capture = Capture::EMPTY; + let mut captures = slice::from_mut(&mut capture).iter_mut(); + while !self.read_token(token, &mut captures) { self.step(); if self.at_end() { return None; } } - Some(res) + Some(&self.text[capture.to_index()]) } #[must_use] - pub fn match_tokens(&mut self, tokens: &[Token<'_>], captures: &mut [&mut &'txt str]) -> bool { + 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)) } @@ -186,7 +262,7 @@ impl<'txt> RustSearcher<'txt> { pub struct ActiveLint { pub group: String, - pub span: Span, + pub decl_span: Span, } pub struct DeprecatedLint { @@ -199,12 +275,17 @@ pub struct RenamedLint { pub version: String, } -pub enum Lint { +pub enum LintKind { Active(ActiveLint), Deprecated(DeprecatedLint), Renamed(RenamedLint), } +pub struct Lint { + pub kind: LintKind, + pub name_span: Span, +} + pub struct ParsedData { pub source_map: SourceMap, pub lints: FxHashMap, @@ -220,10 +301,57 @@ impl ParsedData { lints: FxHashMap::with_capacity_and_hasher(1000, Default::default()), 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, @@ -233,11 +361,47 @@ impl ParsedData { } } +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, deprecated_span: Range, renamed_span: Range, + errors: Vec, } impl Parser { /// Parses all source files looking for lint declarations (`declare_clippy_lint! { .. }`). @@ -286,31 +450,51 @@ impl Parser { use Token::*; #[rustfmt::skip] static DECL_TOKENS: &[Token<'_>] = &[ - // !{ /// docs - Bang, OpenBrace, AnyComment, + // { /// docs + 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, + // pub NAME, GROUP, "desc" + Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, AnyComment, LitStr, + ]; + #[rustfmt::skip] + static EXTRA_TOKENS: &[Token<'_>] = &[ + // , @option = value + Comma, AnyComment, At, AnyIdent, Eq, Literal, ]; let mut searcher = RustSearcher::new(&self.source_map.files[file].contents); + let mut captures = [Capture::EMPTY; 2]; #[expect(clippy::cast_possible_truncation)] while searcher.find_token(Ident("declare_clippy_lint")) { let start = searcher.pos() - "declare_clippy_lint".len() as u32; - let (mut name, mut group) = ("", ""); - if searcher.match_tokens(DECL_TOKENS, &mut [&mut name, &mut group]) && searcher.find_token(CloseBrace) { - self.lints.insert( - name.to_ascii_lowercase(), - Lint::Active(ActiveLint { - group: group.into(), - span: Span { - file, - start, - end: searcher.pos(), - }, - }), - ); + if searcher.match_token(Bang) { + if !searcher.match_tokens(DECL_TOKENS, &mut captures) { + 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, + }); + } + while searcher.match_tokens(EXTRA_TOKENS, &mut []) { + // nothing + } + if !searcher.match_token(CloseBrace) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } } } } @@ -335,6 +519,11 @@ impl Parser { // !{ 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( @@ -342,117 +531,197 @@ impl Parser { krate, "deprecated_lints", ); - let file = &self.source_map.files[file]; + let file_data = &self.source_map.files[file]; - let mut searcher = RustSearcher::new(&file.contents); + let mut captures = [Capture::EMPTY; 3]; + let mut searcher = RustSearcher::new(&file_data.contents); // First instance is the macro definition. assert!( searcher.find_token(Ident("declare_with_version")), "error parsing `clippy_lints/src/deprecated_lints.rs`" ); - if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(DEPRECATED_TOKENS, &mut []) { - let start = searcher.pos(); - let mut end = start; - let mut version = ""; - let mut name = ""; - let mut reason = ""; - while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) { - self.lints.insert( - parse_clippy_lint_name(&file.path, name), - Lint::Deprecated(DeprecatedLint { - reason: parse_str_single_line(&file.path, reason), - version: parse_str_single_line(&file.path, version), - }), - ); - end = searcher.pos(); + if !searcher.find_token(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; - } else { - panic!("error reading deprecated lints"); + } + self.deprecated_span = start..end; + if !searcher.match_tokens(END_TOKENS, &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + return; } - if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(RENAMED_TOKENS, &mut []) { - let start = searcher.pos(); - let mut end = start; - 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]) { - self.lints.insert( - parse_clippy_lint_name(&file.path, old_name), - Lint::Renamed(RenamedLint { - new_name: parse_maybe_clippy_lint_name(&file.path, new_name), - version: parse_str_single_line(&file.path, version), - }), - ); - end = searcher.pos(); + if !searcher.find_token(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; - } else { - panic!("error reading renamed lints"); + } + 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; } } } -fn assert_lint_name(path: &Path, s: &str) { - assert!( - s.bytes() - .all(|c| matches!(c, b'_' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z')), - "error parsing `{}`: `{s}` is not a valid lint name", - path.display(), - ); +#[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 + }, + } } -fn parse_str_lit(s: &str) -> String { +#[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 s = s - .strip_prefix('"') - .and_then(|s| s.strip_suffix('"')) - .unwrap_or_else(|| panic!("expected quoted string, found `{s}`")); + let Some(s) = s.strip_prefix('"').and_then(|s| s.strip_suffix('"')) else { + errors.push(ErrorKind::StrLit(capture.to_span(file)).into()); + return None; + }; - if is_raw { + 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()); - rustc_literal_escaper::unescape_str(s, &mut |_, ch| { - if let Ok(ch) = ch { - res.push(ch); - } + #[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 parse_str_single_line(path: &Path, s: &str) -> String { - let s = parse_str_lit(s); - assert!( - !s.contains('\n'), - "error parsing `{}`: `{s}` should be a single line string", - path.display(), - ); - 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')) } -fn parse_clippy_lint_name(path: &Path, s: &str) -> String { - let mut s = parse_str_lit(s); +#[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 { - panic!( - "error parsing `{}`: `{s}` needs to have the `clippy::` prefix", - path.display() - ); + errors.push(ErrorKind::LintMissingPrefix(capture.to_span(file)).into()); + return None; }; - assert_lint_name(path, name); + if !is_lint_name(name) { + errors.push(ErrorKind::NotLintName(capture.to_span(file)).into()); + return None; + } s.drain(.."clippy::".len()); - s + Some(s) } -fn parse_maybe_clippy_lint_name(path: &Path, s: &str) -> String { - let s = parse_str_lit(s); - assert_lint_name(path, s.strip_prefix("clippy::").unwrap_or(&s)); - 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 41afa56c670b..8875de431ca3 100644 --- a/clippy_dev/src/rename_lint.rs +++ b/clippy_dev/src/rename_lint.rs @@ -1,12 +1,13 @@ -use crate::parse::{Lint, ParsedData, RenamedLint, RustSearcher, Token}; +use crate::parse::{Capture, Lint, LintKind, ParsedData, RenamedLint, RustSearcher, Token}; use crate::update_lints::generate_lint_files; use crate::utils::{ 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, StdEntry}; +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; @@ -45,7 +46,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b String::from_iter(["clippy::", new_name]) }; for lint in data.lints.values_mut() { - if let Lint::Renamed(lint) = lint + 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); @@ -53,13 +54,13 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } // Mark the lint as renamed - let Some(old_entry) = data.lints.get_mut(old_name) else { + let Some(entry) = data.lints.get_mut(old_name) else { eprintln!("error: failed to find lint `{old_name}`"); return; }; - let Lint::Active(lint) = mem::replace( - old_entry, - Lint::Renamed(RenamedLint { + let LintKind::Active(lint) = mem::replace( + &mut entry.kind, + LintKind::Renamed(RenamedLint { new_name: new_name_prefixed, version: clippy_version.rust_display().to_string(), }), @@ -67,28 +68,26 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b 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 lint_file = &data.source_map.files[lint.span.file]; - let is_unique_mod = data.lints.values().any(|x| { - if let Lint::Active(x) = x { - data.source_map.files[x.span.file].module == lint_file.module - } else { - false - } - }); + 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_file.path.as_ref()) { mod_edit = ModEdit::Delete; } } else { updater.update_file(&lint_file.path, &mut |_, src, dst| -> UpdateStatus { - let mut start = &src[..lint.span.start as usize]; + 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.span.end as usize..]; + let mut end = &src[lint.decl_span.end as usize..]; if end.starts_with("\n\n") { end = &end[1..]; } @@ -98,8 +97,8 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b }); } delete_test_files(old_name, &data.lints); - } else if let StdEntry::Vacant(entry) = data.lints.entry(new_name.to_owned()) { - let lint_file = &mut data.source_map.files[lint.span.file]; + } 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 @@ -117,7 +116,10 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b lint_file.module.push_str(new_name); } } - entry.insert(Lint::Active(lint)); + 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}`"); @@ -301,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, @@ -312,10 +314,10 @@ fn file_update_fn<'a, 'b>( match text { // 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; @@ -324,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; @@ -392,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/update_lints.rs b/clippy_dev/src/update_lints.rs index cf5c986984f5..83e2ff4bc952 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,4 +1,4 @@ -use crate::parse::{Lint, ParsedData}; +use crate::parse::{LintKind, ParsedData}; use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; use itertools::Itertools; use std::fmt::Write; @@ -49,10 +49,10 @@ pub fn generate_lint_files(update_mode: UpdateMode, data: &ParsedData) { 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 { - Lint::Active(lint) => active.push((name, &data.source_map.files[lint.span.file])), - Lint::Deprecated(lint) => deprecated.push((name, lint)), - Lint::Renamed(lint) => renamed.push((name, lint)), + 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))); From ab1f092b6f89a4e18ac6a4bc846f05e25e940cd3 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 12 Jul 2025 01:55:27 -0400 Subject: [PATCH 05/10] `clippy_dev`: parse lint pass macro calls --- clippy_dev/src/new_lint.rs | 2 +- clippy_dev/src/parse.rs | 182 +++++++++++++++++++++++++++++-------- 2 files changed, 145 insertions(+), 39 deletions(-) diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 3d27dc438987..b353520fb9fe 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -524,7 +524,7 @@ fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) { let mut decl_end = None; let mut searcher = RustSearcher::new(contents); let mut captures = [Capture::EMPTY]; - while let Some(name) = searcher.find_capture_token(CaptureIdent) { + while let Some(name) = searcher.find_any_ident() { match name { "declare_clippy_lint" => { if searcher.match_tokens(&[Bang, OpenBrace], &mut []) && searcher.find_token(CloseBrace) { diff --git a/clippy_dev/src/parse.rs b/clippy_dev/src/parse.rs index 703a0922709a..b4a949331040 100644 --- a/clippy_dev/src/parse.rs +++ b/clippy_dev/src/parse.rs @@ -3,6 +3,7 @@ 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; @@ -27,6 +28,7 @@ pub enum Token<'a> { DoubleColon, Comma, Eq, + FatArrow, Lifetime, Literal, Lt, @@ -34,6 +36,7 @@ pub enum Token<'a> { OpenBrace, OpenBracket, OpenParen, + OptLifetimeArgs, Pound, Semi, } @@ -134,6 +137,7 @@ impl<'txt> RustSearcher<'txt> { /// 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) { @@ -202,6 +206,25 @@ impl<'txt> RustSearcher<'txt> { } 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, @@ -235,16 +258,28 @@ impl<'txt> RustSearcher<'txt> { } #[must_use] - pub fn find_capture_token(&mut self, token: Token<'_>) -> Option<&'txt str> { - let mut capture = Capture::EMPTY; - let mut captures = slice::from_mut(&mut capture).iter_mut(); - while !self.read_token(token, &mut captures) { - self.step(); - if self.at_end() { - return None; + 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(), } } - Some(&self.text[capture.to_index()]) + } + + #[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] @@ -286,9 +321,27 @@ pub struct Lint { 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, } @@ -299,6 +352,8 @@ impl ParsedData { 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(), @@ -355,6 +410,8 @@ impl ParsedData { 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, } @@ -399,6 +456,8 @@ impl From for Error { struct Parser { source_map: SourceMap, lints: FxHashMap, + lint_passes: IndexVec, + lint_registrations: Vec, deprecated_span: Range, renamed_span: Range, errors: Vec, @@ -449,7 +508,7 @@ impl Parser { #[allow(clippy::enum_glob_use)] use Token::*; #[rustfmt::skip] - static DECL_TOKENS: &[Token<'_>] = &[ + static LINT_DECL_TOKENS: &[Token<'_>] = &[ // { /// docs OpenBrace, AnyComment, // #[clippy::version = "version"] @@ -458,42 +517,89 @@ impl Parser { Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, AnyComment, LitStr, ]; #[rustfmt::skip] - static EXTRA_TOKENS: &[Token<'_>] = &[ + 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]; - #[expect(clippy::cast_possible_truncation)] - while searcher.find_token(Ident("declare_clippy_lint")) { - let start = searcher.pos() - "declare_clippy_lint".len() as u32; + 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) { - if !searcher.match_tokens(DECL_TOKENS, &mut captures) { - 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 { + 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(), }, - }), - name_span, - }); - } - while searcher.match_tokens(EXTRA_TOKENS, &mut []) { - // nothing - } - if !searcher.match_token(CloseBrace) { - self.errors.push(searcher.get_unexpected_err(file)); - return; + }); + }, + _ => {}, } } } @@ -537,11 +643,11 @@ impl Parser { let mut searcher = RustSearcher::new(&file_data.contents); // First instance is the macro definition. assert!( - searcher.find_token(Ident("declare_with_version")), - "error parsing `clippy_lints/src/deprecated_lints.rs`" + searcher.find_ident("declare_with_version"), + "error parsing `clippy_lints/src/deprecated_lints.rs`", ); - if !searcher.find_token(Ident("declare_with_version")) || !searcher.match_tokens(DEPRECATED_TOKENS, &mut []) { + if !searcher.find_ident("declare_with_version") || !searcher.match_tokens(DEPRECATED_TOKENS, &mut []) { self.errors.push(searcher.get_unexpected_err(file)); return; } @@ -570,7 +676,7 @@ impl Parser { return; } - if !searcher.find_token(Ident("declare_with_version")) || !searcher.match_tokens(RENAMED_TOKENS, &mut []) { + if !searcher.find_ident("declare_with_version") || !searcher.match_tokens(RENAMED_TOKENS, &mut []) { self.errors.push(searcher.get_unexpected_err(file)); return; } From ca06aa2aad22c8a3e2223285d0d8a56116655023 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 13 Jul 2025 13:56:07 -0400 Subject: [PATCH 06/10] `clippy_dev`: check that all lints are registered with a lint pass --- clippy_dev/src/update_lints.rs | 20 +- clippy_lints/src/doc/mod.rs | 2 - clippy_lints_internal/src/lib.rs | 3 +- .../src/lint_without_lint_pass.rs | 180 ++++-------------- tests/ui-internal/check_formulation.rs | 1 - tests/ui-internal/check_formulation.stderr | 4 +- tests/ui-internal/lint_without_lint_pass.rs | 49 ----- .../ui-internal/lint_without_lint_pass.stderr | 21 -- 8 files changed, 54 insertions(+), 226 deletions(-) delete mode 100644 tests/ui-internal/lint_without_lint_pass.rs delete mode 100644 tests/ui-internal/lint_without_lint_pass.stderr diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 83e2ff4bc952..146230882a60 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -3,6 +3,7 @@ use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn} use itertools::Itertools; use std::fmt::Write; 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\ @@ -20,7 +21,24 @@ 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) { - generate_lint_files(update_mode, &ParsedData::collect()); + 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)] 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 - From eb65729a00d8725bea3adc031e4a2b206a3c9501 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Jul 2025 11:50:47 -0400 Subject: [PATCH 07/10] prereqs --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/ineffective_open_options.rs | 98 ++++++------ clippy_lints/src/loops/mod.rs | 11 +- .../src/loops/unused_enumerate_index.rs | 92 +++++++++--- .../src/methods/iter_overeager_cloned.rs | 5 +- clippy_lints/src/methods/mod.rs | 62 ++++---- .../src/methods/unused_enumerate_index.rs | 139 ------------------ clippy_lints/src/useless_conversion.rs | 53 +++---- tests/ui/iter_overeager_cloned.fixed | 12 +- tests/ui/iter_overeager_cloned.rs | 12 +- tests/ui/iter_overeager_cloned.stderr | 4 +- tests/ui/unused_enumerate_index.stderr | 32 ++-- 13 files changed, 227 insertions(+), 295 deletions(-) delete mode 100644 clippy_lints/src/methods/unused_enumerate_index.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a92fbdc767bd..8d770ad2a338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6304,6 +6304,7 @@ Released 2018-09-13 [`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names [`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names [`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards +[`redundant_iter_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_iter_cloned [`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals [`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern [`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 188f8f999165..dec2da9c07fa 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -449,6 +449,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::READ_LINE_WITHOUT_TRIM_INFO, crate::methods::READONLY_WRITE_LOCK_INFO, crate::methods::REDUNDANT_AS_STR_INFO, + crate::methods::REDUNDANT_ITER_CLONED_INFO, crate::methods::REPEAT_ONCE_INFO, crate::methods::RESULT_FILTER_MAP_INFO, crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, diff --git a/clippy_lints/src/ineffective_open_options.rs b/clippy_lints/src/ineffective_open_options.rs index 7a751514b647..a159f6157183 100644 --- a/clippy_lints/src/ineffective_open_options.rs +++ b/clippy_lints/src/ineffective_open_options.rs @@ -1,13 +1,12 @@ -use crate::methods::method_call; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{peel_blocks, sym}; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{peel_blocks, peel_hir_expr_while, sym}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; use rustc_session::declare_lint_pass; -use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -43,53 +42,58 @@ declare_clippy_lint! { declare_lint_pass!(IneffectiveOpenOptions => [INEFFECTIVE_OPEN_OPTIONS]); -fn index_if_arg_is_boolean(args: &[Expr<'_>], call_span: Span) -> Option { - if let [arg] = args - && let ExprKind::Lit(lit) = peel_blocks(arg).kind - && lit.node == LitKind::Bool(true) - { - // The `.` is not included in the span so we cheat a little bit to include it as well. - Some(call_span.with_lo(call_span.lo() - BytePos(1))) - } else { - None - } -} - impl<'tcx> LateLintPass<'tcx> for IneffectiveOpenOptions { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some((sym::open, mut receiver, [_arg], _, _)) = method_call(expr) else { - return; - }; - let receiver_ty = cx.typeck_results().expr_ty(receiver); - match receiver_ty.peel_refs().kind() { - ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::FsOpenOptions, adt.did()) => {}, - _ => return, - } - - let mut append = None; - let mut write = None; + if let ExprKind::MethodCall(name, recv, [_], _) = expr.kind + && name.ident.name == sym::open + && !expr.span.from_expansion() + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::FsOpenOptions) + { + let mut append = false; + let mut write = None; + peel_hir_expr_while(recv, |e| { + if let ExprKind::MethodCall(name, recv, args, call_span) = e.kind + && !e.span.from_expansion() + { + if let [arg] = args + && let ExprKind::Lit(lit) = peel_blocks(arg).kind + && matches!(lit.node, LitKind::Bool(true)) + && !arg.span.from_expansion() + && !lit.span.from_expansion() + { + match name.ident.name { + sym::append => append = true, + sym::write + if let Some(range) = call_span.map_range(cx, |_, text, range| { + if text.get(..range.start)?.ends_with('.') { + Some(range.start - 1..range.end) + } else { + None + } + }) => + { + write = Some(call_span.with_lo(range.start)); + }, + _ => {}, + } + } + Some(recv) + } else { + None + } + }); - while let Some((name, recv, args, _, span)) = method_call(receiver) { - if name == sym::append { - append = index_if_arg_is_boolean(args, span); - } else if name == sym::write { - write = index_if_arg_is_boolean(args, span); + if append && let Some(write_span) = write { + span_lint_and_sugg( + cx, + INEFFECTIVE_OPEN_OPTIONS, + write_span, + "unnecessary use of `.write(true)` because there is `.append(true)`", + "remove `.write(true)`", + String::new(), + Applicability::MachineApplicable, + ); } - receiver = recv; - } - - if let Some(write_span) = write - && append.is_some() - { - span_lint_and_sugg( - cx, - INEFFECTIVE_OPEN_OPTIONS, - write_span, - "unnecessary use of `.write(true)` because there is `.append(true)`", - "remove `.write(true)`", - String::new(), - Applicability::MachineApplicable, - ); } } } diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 01c36b8cb12f..5b46922c5ab3 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -876,6 +876,15 @@ impl<'tcx> LateLintPass<'tcx> for Loops { missing_spin_loop::check(cx, condition, body); manual_while_let_some::check(cx, condition, body, span); } + + if let ExprKind::MethodCall(path, recv, [arg], _) = expr.kind + && matches!( + path.ident.name, + sym::all | sym::any | sym::filter_map | sym::find_map | sym::flat_map | sym::for_each | sym::map + ) + { + unused_enumerate_index::check_method(cx, expr, recv, arg); + } } } @@ -904,7 +913,7 @@ impl Loops { same_item_push::check(cx, pat, arg, body, expr, self.msrv); manual_flatten::check(cx, pat, arg, body, span, self.msrv); manual_find::check(cx, pat, arg, body, span, expr); - unused_enumerate_index::check(cx, pat, arg, body); + unused_enumerate_index::check_loop(cx, arg, pat, None, body); char_indices_as_byte_indices::check(cx, pat, arg, body); } diff --git a/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs index 51e21aa9734e..d4cbde53afb3 100644 --- a/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,40 +1,84 @@ use super::UNUSED_ENUMERATE_INDEX; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet; -use clippy_utils::{pat_is_wild, sugg}; +use clippy_utils::source::{SpanRangeExt, walk_span_to_context}; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{expr_or_init, is_lint_allowed, is_trait_method, pat_is_wild}; use rustc_errors::Applicability; -use rustc_hir::def::DefKind; -use rustc_hir::{Expr, ExprKind, Pat, PatKind}; +use rustc_hir::{Expr, ExprKind, Pat, PatKind, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty; -use rustc_span::sym; +use rustc_span::{Span, SyntaxContext, sym}; -/// Checks for the `UNUSED_ENUMERATE_INDEX` lint. -/// -/// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { - if let PatKind::Tuple([index, elem], _) = pat.kind - && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind - && let ty = cx.typeck_results().expr_ty(arg) - && pat_is_wild(cx, &index.kind, body) - && let ty::Adt(base, _) = *ty.kind() - && cx.tcx.is_diagnostic_item(sym::Enumerate, base.did()) - && let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) - && cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id) +pub(super) fn check_method<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'tcx>, + arg: &'tcx Expr<'tcx>, +) { + if let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir_body(closure.body) + && let [param] = body.params + && is_trait_method(cx, e, sym::Iterator) + && let [input] = closure.fn_decl.inputs + && !arg.span.from_expansion() + && !input.span.from_expansion() + && !recv.span.from_expansion() + && !param.span.from_expansion() { + let ty_spans = if let TyKind::Tup([_, inner]) = input.kind { + let Some(inner) = walk_span_to_context(inner.span, SyntaxContext::root()) else { + return; + }; + Some((input.span, inner)) + } else { + None + }; + check_loop(cx, recv, param.pat, ty_spans, body.value); + } +} + +pub(super) fn check_loop<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + pat: &'tcx Pat<'tcx>, + ty_spans: Option<(Span, Span)>, + body: &'tcx Expr<'tcx>, +) { + if let PatKind::Tuple([idx_pat, inner_pat], _) = pat.kind + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e), sym::Enumerate) + && pat_is_wild(cx, &idx_pat.kind, body) + && let enumerate_call = expr_or_init(cx, e) + && let ExprKind::MethodCall(_, _, [], enumerate_span) = enumerate_call.kind + && let Some(enumerate_id) = cx.typeck_results().type_dependent_def_id(enumerate_call.hir_id) + && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_id) + && !is_lint_allowed(cx, UNUSED_ENUMERATE_INDEX, enumerate_call.hir_id) + && !enumerate_call.span.from_expansion() + && !pat.span.from_expansion() + && !idx_pat.span.from_expansion() + && !inner_pat.span.from_expansion() + && let Some(enumerate_range) = enumerate_span.map_range(cx, |_, text, range| { + text.get(..range.start)? + .ends_with('.') + .then_some(range.start - 1..range.end) + }) + { + let enumerate_span = Span::new(enumerate_range.start, enumerate_range.end, SyntaxContext::root(), None); span_lint_and_then( cx, UNUSED_ENUMERATE_INDEX, - arg.span, + enumerate_span, "you seem to use `.enumerate()` and immediately discard the index", |diag| { - let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter"); + let mut spans = Vec::with_capacity(5); + spans.push((enumerate_span, String::new())); + spans.push((pat.span.with_hi(inner_pat.span.lo()), String::new())); + spans.push((pat.span.with_lo(inner_pat.span.hi()), String::new())); + if let Some((outer, inner)) = ty_spans { + spans.push((outer.with_hi(inner.lo()), String::new())); + spans.push((outer.with_lo(inner.hi()), String::new())); + } diag.multipart_suggestion( "remove the `.enumerate()` call", - vec![ - (pat.span, snippet(cx, elem.span, "..").into_owned()), - (arg.span, base_iter.to_string()), - ], + spans, Applicability::MachineApplicable, ); }, diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index f5fe4316eb0d..d43b22c2bd51 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -10,8 +10,7 @@ use rustc_middle::mir::{FakeReadCause, Mutability}; use rustc_middle::ty::{self, BorrowKind}; use rustc_span::{Symbol, sym}; -use super::ITER_OVEREAGER_CLONED; -use crate::redundant_clone::REDUNDANT_CLONE; +use super::{ITER_OVEREAGER_CLONED, REDUNDANT_ITER_CLONED}; #[derive(Clone, Copy)] pub(super) enum Op<'a> { @@ -96,7 +95,7 @@ pub(super) fn check<'tcx>( } let (lint, msg, trailing_clone) = match op { - Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_CLONE, "unneeded cloning of iterator items", ""), + Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_ITER_CLONED, "unneeded cloning of iterator items", ""), Op::LaterCloned | Op::FixClosure(_, _) => ( ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index f2dabdd34387..4190a71d672c 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -134,7 +134,6 @@ mod unnecessary_min_or_max; mod unnecessary_result_map_or_else; mod unnecessary_sort_by; mod unnecessary_to_owned; -mod unused_enumerate_index; mod unwrap_expect_used; mod useless_asref; mod useless_nonzero_new_unchecked; @@ -4565,6 +4564,31 @@ declare_clippy_lint! { "hardcoded localhost IP address" } +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `Iterator::cloned` where the original value could be used + /// instead. + /// + /// ### Why is this bad? + /// It is not always possible for the compiler to eliminate useless allocations and + /// deallocations generated by redundant `clone()`s. + /// + /// ### Example + /// ```no_run + /// let x = vec![String::new()]; + /// let _ = x.iter().cloned().map(|x| x.len()); + /// ``` + /// Use instead: + /// ```no_run + /// let x = vec![String::new()]; + /// let _ = x.iter().map(|x| x.len()); + /// ``` + #[clippy::version = "1.90.0"] + pub REDUNDANT_ITER_CLONED, + perf, + "detects redundant calls to `Iterator::cloned`" +} + #[expect(clippy::struct_excessive_bools)] pub struct Methods { avoid_breaking_exported_api: bool, @@ -4744,6 +4768,7 @@ impl_lint_pass!(Methods => [ IO_OTHER_ERROR, SWAP_WITH_TEMPORARY, IP_CONSTANT, + REDUNDANT_ITER_CLONED, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4956,7 +4981,6 @@ impl Methods { zst_offset::check(cx, expr, recv); }, (sym::all, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, true); match method_call(recv) { Some((sym::cloned, recv2, [], _, _)) => { @@ -4986,7 +5010,6 @@ impl Methods { } }, (sym::any, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, false); match method_call(recv) { Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( @@ -5133,17 +5156,14 @@ impl Methods { } }, (sym::filter_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); unnecessary_filter_map::check(cx, expr, arg, name); filter_map_bool_then::check(cx, expr, arg, call_span); filter_map_identity::check(cx, expr, arg, span); }, (sym::find_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); unnecessary_filter_map::check(cx, expr, arg, name); }, (sym::flat_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); flat_map_identity::check(cx, expr, arg, span); flat_map_option::check(cx, expr, arg, span); }, @@ -5165,20 +5185,17 @@ impl Methods { manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv); unnecessary_fold::check(cx, expr, init, acc, span); }, - (sym::for_each, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - match method_call(recv) { - Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, - ), - _ => {}, - } + (sym::for_each, [arg]) => match method_call(recv) { + Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ), + _ => {}, }, (sym::get, [arg]) => { get_first::check(cx, expr, recv, arg); @@ -5239,7 +5256,6 @@ impl Methods { }, (name @ (sym::map | sym::map_err), [m_arg]) => { if name == sym::map { - unused_enumerate_index::check(cx, expr, recv, m_arg); map_clone::check(cx, expr, recv, m_arg, self.msrv); map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span); manual_is_variant_and::check_map(cx, expr); @@ -5275,10 +5291,6 @@ impl Methods { } map_identity::check(cx, expr, recv, m_arg, name, span); manual_inspect::check(cx, expr, m_arg, name, span, self.msrv); - crate::useless_conversion::check_function_application(cx, expr, recv, m_arg); - }, - (sym::map_break | sym::map_continue, [m_arg]) => { - crate::useless_conversion::check_function_application(cx, expr, recv, m_arg); }, (sym::map_or, [def, map]) => { option_map_or_none::check(cx, expr, recv, def, map); diff --git a/clippy_lints/src/methods/unused_enumerate_index.rs b/clippy_lints/src/methods/unused_enumerate_index.rs deleted file mode 100644 index af466fe091c2..000000000000 --- a/clippy_lints/src/methods/unused_enumerate_index.rs +++ /dev/null @@ -1,139 +0,0 @@ -use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::{expr_or_init, is_trait_method, pat_is_wild}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind}; -use rustc_lint::LateContext; -use rustc_middle::ty::AdtDef; -use rustc_span::{Span, sym}; - -use crate::loops::UNUSED_ENUMERATE_INDEX; - -/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops. -/// -/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is -/// checked: -/// ```ignore -/// for (_, x) in some_iter.enumerate() { -/// // Index is ignored -/// } -/// ``` -/// -/// This `check` function checks for chained method calls constructs where we can detect that the -/// index is unused. Currently, this checks only for the following patterns: -/// ```ignore -/// some_iter.enumerate().map_function(|(_, x)| ..) -/// let x = some_iter.enumerate(); -/// x.map_function(|(_, x)| ..) -/// ``` -/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or -/// `map`. -/// -/// # Preconditions -/// This function must be called not on the `enumerate` call expression itself, but on any of the -/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and -/// that the method call is one of the `std::iter::Iterator` trait. -/// -/// * `call_expr`: The map function call expression -/// * `recv`: The receiver of the call -/// * `closure_arg`: The argument to the map function call containing the closure/function to apply -pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { - let recv_ty = cx.typeck_results().expr_ty(recv); - if let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did) - // If we call a method on a `std::iter::Enumerate` instance - && cx.tcx.is_diagnostic_item(sym::Enumerate, recv_ty_defid) - // If we are calling a method of the `Iterator` trait - && is_trait_method(cx, call_expr, sym::Iterator) - // And the map argument is a closure - && let ExprKind::Closure(closure) = closure_arg.kind - && let closure_body = cx.tcx.hir_body(closure.body) - // And that closure has one argument ... - && let [closure_param] = closure_body.params - // .. which is a tuple of 2 elements - && let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind - // And that the first element (the index) is either `_` or unused in the body - && pat_is_wild(cx, &index.kind, closure_body) - // Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the - // first example below, `expr_or_init` would return `recv`. - // ``` - // iter.enumerate().map(|(_, x)| x) - // ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate` - // - // let binding = iter.enumerate(); - // ^^^^^^^^^^^^^^^^ `recv_init_expr` - // binding.map(|(_, x)| x) - // ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate` - // ``` - && let recv_init_expr = expr_or_init(cx, recv) - // Make sure the initializer is a method call. It may be that the `Enumerate` comes from something - // that we cannot control. - // This would for instance happen with: - // ``` - // external_lib::some_function_returning_enumerate().map(|(_, x)| x) - // ``` - && let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind - && let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id) - // Make sure the method call is `std::iter::Iterator::enumerate`. - && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_defid) - { - // Check if the tuple type was explicit. It may be the type system _needs_ the type of the element - // that would be explicitly in the closure. - let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) { - // We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`. - // Fallback to `..` if we fail getting either snippet. - Some(ty_span) => elem - .span - .get_source_text(cx) - .and_then(|binding_name| { - ty_span - .get_source_text(cx) - .map(|ty_name| format!("{binding_name}: {ty_name}")) - }) - .unwrap_or_else(|| "..".to_string()), - // Otherwise, we have no explicit type. We can replace with the binding name of the element. - None => snippet(cx, elem.span, "..").into_owned(), - }; - - // Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we - // can get from the `MethodCall`. - span_lint_hir_and_then( - cx, - UNUSED_ENUMERATE_INDEX, - recv_init_expr.hir_id, - enumerate_span, - "you seem to use `.enumerate()` and immediately discard the index", - |diag| { - diag.multipart_suggestion( - "remove the `.enumerate()` call", - vec![ - (closure_param.span, new_closure_param), - ( - enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()), - String::new(), - ), - ], - Applicability::MachineApplicable, - ); - }, - ); - } -} - -/// Find the span of the explicit type of the element. -/// -/// # Returns -/// If the tuple argument: -/// * Has no explicit type, returns `None` -/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None` -/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for -/// the element type. -fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option { - if let [tuple_ty] = fn_decl.inputs - && let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind - && !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer(())) - { - Some(elem_ty.span) - } else { - None - } -} diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 380ddea4e1e8..e5b20c0e0a13 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -176,6 +176,33 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } }, + ExprKind::MethodCall(path, recv, [arg], _) => { + if matches!( + path.ident.name, + sym::map | sym::map_err | sym::map_break | sym::map_continue + ) && has_eligible_receiver(cx, recv, e) + && (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From)) + && let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind() + && let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice() + && same_type_and_consts(from_ty, to_ty) + { + span_lint_and_then( + cx, + USELESS_CONVERSION, + e.span.with_lo(recv.span.hi()), + format!("useless conversion to the same type: `{from_ty}`"), + |diag| { + diag.suggest_remove_item( + cx, + e.span.with_lo(recv.span.hi()), + "consider removing", + Applicability::MachineApplicable, + ); + }, + ); + } + }, + ExprKind::MethodCall(name, recv, [], _) => { if is_trait_method(cx, e, sym::Into) && name.ident.name == sym::into { let a = cx.typeck_results().expr_ty(e); @@ -412,32 +439,6 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } } -/// Check if `arg` is a `Into::into` or `From::from` applied to `receiver` to give `expr`, through a -/// higher-order mapping function. -pub fn check_function_application(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { - if has_eligible_receiver(cx, recv, expr) - && (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From)) - && let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind() - && let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice() - && same_type_and_consts(from_ty, to_ty) - { - span_lint_and_then( - cx, - USELESS_CONVERSION, - expr.span.with_lo(recv.span.hi()), - format!("useless conversion to the same type: `{from_ty}`"), - |diag| { - diag.suggest_remove_item( - cx, - expr.span.with_lo(recv.span.hi()), - "consider removing", - Applicability::MachineApplicable, - ); - }, - ); - } -} - fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) -> bool { if is_inherent_method_call(cx, expr) { matches!( diff --git a/tests/ui/iter_overeager_cloned.fixed b/tests/ui/iter_overeager_cloned.fixed index b0e548f17909..4171f19469a4 100644 --- a/tests/ui/iter_overeager_cloned.fixed +++ b/tests/ui/iter_overeager_cloned.fixed @@ -1,4 +1,4 @@ -#![warn(clippy::iter_overeager_cloned, clippy::redundant_clone, clippy::filter_next)] +#![warn(clippy::iter_overeager_cloned, clippy::redundant_iter_cloned, clippy::filter_next)] #![allow( dead_code, clippy::let_unit_value, @@ -16,7 +16,7 @@ fn main() { //~^ iter_overeager_cloned let _: usize = vec.iter().filter(|x| x == &"2").count(); - //~^ redundant_clone + //~^ redundant_iter_cloned let _: Vec<_> = vec.iter().take(2).cloned().collect(); //~^ iter_overeager_cloned @@ -77,19 +77,19 @@ fn main() { } let _ = vec.iter().map(|x| x.len()); - //~^ redundant_clone + //~^ redundant_iter_cloned // This would fail if changed. let _ = vec.iter().cloned().map(|x| x + "2"); let _ = vec.iter().for_each(|x| assert!(!x.is_empty())); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().all(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().any(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); diff --git a/tests/ui/iter_overeager_cloned.rs b/tests/ui/iter_overeager_cloned.rs index cedf62a6b473..fe6aba24dd3e 100644 --- a/tests/ui/iter_overeager_cloned.rs +++ b/tests/ui/iter_overeager_cloned.rs @@ -1,4 +1,4 @@ -#![warn(clippy::iter_overeager_cloned, clippy::redundant_clone, clippy::filter_next)] +#![warn(clippy::iter_overeager_cloned, clippy::redundant_iter_cloned, clippy::filter_next)] #![allow( dead_code, clippy::let_unit_value, @@ -16,7 +16,7 @@ fn main() { //~^ iter_overeager_cloned let _: usize = vec.iter().filter(|x| x == &"2").cloned().count(); - //~^ redundant_clone + //~^ redundant_iter_cloned let _: Vec<_> = vec.iter().cloned().take(2).collect(); //~^ iter_overeager_cloned @@ -78,19 +78,19 @@ fn main() { } let _ = vec.iter().cloned().map(|x| x.len()); - //~^ redundant_clone + //~^ redundant_iter_cloned // This would fail if changed. let _ = vec.iter().cloned().map(|x| x + "2"); let _ = vec.iter().cloned().for_each(|x| assert!(!x.is_empty())); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().cloned().all(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().cloned().any(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); diff --git a/tests/ui/iter_overeager_cloned.stderr b/tests/ui/iter_overeager_cloned.stderr index 1616dec95b79..f234d19e4aaa 100644 --- a/tests/ui/iter_overeager_cloned.stderr +++ b/tests/ui/iter_overeager_cloned.stderr @@ -25,8 +25,8 @@ LL | let _: usize = vec.iter().filter(|x| x == &"2").cloned().count(); | | | help: try: `.count()` | - = note: `-D clippy::redundant-clone` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::redundant_clone)]` + = note: `-D clippy::redundant-iter-cloned` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::redundant_iter_cloned)]` error: unnecessarily eager cloning of iterator items --> tests/ui/iter_overeager_cloned.rs:21:21 diff --git a/tests/ui/unused_enumerate_index.stderr b/tests/ui/unused_enumerate_index.stderr index 14d1d20a66e4..c742cc8a85ba 100644 --- a/tests/ui/unused_enumerate_index.stderr +++ b/tests/ui/unused_enumerate_index.stderr @@ -1,8 +1,8 @@ error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:12:19 + --> tests/ui/unused_enumerate_index.rs:12:27 | LL | for (_, x) in v.iter().enumerate() { - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | = note: `-D clippy::unused-enumerate-index` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unused_enumerate_index)]` @@ -13,10 +13,10 @@ LL + for x in v.iter() { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:60:19 + --> tests/ui/unused_enumerate_index.rs:60:24 | LL | for (_, x) in dummy.enumerate() { - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -25,10 +25,10 @@ LL + for x in dummy { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:65:39 + --> tests/ui/unused_enumerate_index.rs:65:38 | LL | let _ = vec![1, 2, 3].into_iter().enumerate().map(|(_, x)| println!("{x}")); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -37,10 +37,10 @@ LL + let _ = vec![1, 2, 3].into_iter().map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:68:39 + --> tests/ui/unused_enumerate_index.rs:68:38 | LL | let p = vec![1, 2, 3].into_iter().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -50,10 +50,10 @@ LL ~ p.map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:90:17 + --> tests/ui/unused_enumerate_index.rs:90:16 | LL | _ = mac2!().enumerate().map(|(_, _v)| {}); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -62,10 +62,10 @@ LL + _ = mac2!().map(|_v| {}); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:99:39 + --> tests/ui/unused_enumerate_index.rs:99:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -75,10 +75,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:105:39 + --> tests/ui/unused_enumerate_index.rs:105:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -88,10 +88,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:110:39 + --> tests/ui/unused_enumerate_index.rs:110:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | From 5da633a75d0ed043f1edbb085329ea9c0896d4e7 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Jul 2025 11:46:21 -0400 Subject: [PATCH 08/10] Move `methods` lint pass to a new crate part 1/2 --- .../methods => clippy_lints_methods/src}/bind_instead_of_map.rs | 0 .../src/methods => clippy_lints_methods/src}/bytecount.rs | 0 .../methods => clippy_lints_methods/src}/bytes_count_to_len.rs | 0 .../src/methods => clippy_lints_methods/src}/bytes_nth.rs | 0 .../src}/case_sensitive_file_extension_comparisons.rs | 0 .../src/methods => clippy_lints_methods/src}/chars_cmp.rs | 0 .../methods => clippy_lints_methods/src}/chars_cmp_with_unwrap.rs | 0 .../src/methods => clippy_lints_methods/src}/chars_last_cmp.rs | 0 .../src}/chars_last_cmp_with_unwrap.rs | 0 .../src/methods => clippy_lints_methods/src}/chars_next_cmp.rs | 0 .../src}/chars_next_cmp_with_unwrap.rs | 0 .../src/methods => clippy_lints_methods/src}/clear_with_drain.rs | 0 .../src/methods => clippy_lints_methods/src}/clone_on_copy.rs | 0 .../src/methods => clippy_lints_methods/src}/clone_on_ref_ptr.rs | 0 .../src}/cloned_instead_of_copied.rs | 0 .../src}/collapsible_str_replace.rs | 0 .../src}/double_ended_iterator_last.rs | 0 .../src/methods => clippy_lints_methods/src}/drain_collect.rs | 0 .../src/methods => clippy_lints_methods/src}/err_expect.rs | 0 .../src/methods => clippy_lints_methods/src}/expect_fun_call.rs | 0 .../src/methods => clippy_lints_methods/src}/extend_with_drain.rs | 0 .../src/methods => clippy_lints_methods/src}/filetype_is_file.rs | 0 .../src/methods => clippy_lints_methods/src}/filter_map.rs | 0 .../methods => clippy_lints_methods/src}/filter_map_bool_then.rs | 0 .../methods => clippy_lints_methods/src}/filter_map_identity.rs | 0 .../src/methods => clippy_lints_methods/src}/filter_map_next.rs | 0 .../src/methods => clippy_lints_methods/src}/filter_next.rs | 0 .../src/methods => clippy_lints_methods/src}/flat_map_identity.rs | 0 .../src/methods => clippy_lints_methods/src}/flat_map_option.rs | 0 .../src/methods => clippy_lints_methods/src}/format_collect.rs | 0 .../src}/from_iter_instead_of_collect.rs | 0 .../src/methods => clippy_lints_methods/src}/get_first.rs | 0 .../src/methods => clippy_lints_methods/src}/get_last_with_len.rs | 0 .../src/methods => clippy_lints_methods/src}/get_unwrap.rs | 0 .../src/methods => clippy_lints_methods/src}/implicit_clone.rs | 0 .../methods => clippy_lints_methods/src}/inefficient_to_string.rs | 0 .../src/methods => clippy_lints_methods/src}/inspect_for_each.rs | 0 .../src/methods => clippy_lints_methods/src}/into_iter_on_ref.rs | 0 .../src/methods => clippy_lints_methods/src}/io_other_error.rs | 0 .../src/methods => clippy_lints_methods/src}/ip_constant.rs | 0 .../methods => clippy_lints_methods/src}/is_digit_ascii_radix.rs | 0 .../src/methods => clippy_lints_methods/src}/is_empty.rs | 0 .../methods => clippy_lints_methods/src}/iter_cloned_collect.rs | 0 .../src/methods => clippy_lints_methods/src}/iter_count.rs | 0 .../src/methods => clippy_lints_methods/src}/iter_filter.rs | 0 .../src/methods => clippy_lints_methods/src}/iter_kv_map.rs | 0 .../src/methods => clippy_lints_methods/src}/iter_next_slice.rs | 0 .../src/methods => clippy_lints_methods/src}/iter_nth.rs | 0 .../src/methods => clippy_lints_methods/src}/iter_nth_zero.rs | 0 .../src}/iter_on_single_or_empty_collections.rs | 0 .../methods => clippy_lints_methods/src}/iter_out_of_bounds.rs | 0 .../methods => clippy_lints_methods/src}/iter_overeager_cloned.rs | 0 .../src/methods => clippy_lints_methods/src}/iter_skip_next.rs | 0 .../src/methods => clippy_lints_methods/src}/iter_skip_zero.rs | 0 .../src/methods => clippy_lints_methods/src}/iter_with_drain.rs | 0 .../methods => clippy_lints_methods/src}/iterator_step_by_zero.rs | 0 .../methods => clippy_lints_methods/src}/join_absolute_paths.rs | 0 .../src/methods/mod.rs => clippy_lints_methods/src/lib.rs | 0 .../methods => clippy_lints_methods/src}/manual_c_str_literals.rs | 0 .../src/methods => clippy_lints_methods/src}/manual_contains.rs | 0 .../src/methods => clippy_lints_methods/src}/manual_inspect.rs | 0 .../methods => clippy_lints_methods/src}/manual_is_variant_and.rs | 0 .../src/methods => clippy_lints_methods/src}/manual_next_back.rs | 0 .../src/methods => clippy_lints_methods/src}/manual_ok_or.rs | 0 .../src/methods => clippy_lints_methods/src}/manual_repeat_n.rs | 0 .../src}/manual_saturating_arithmetic.rs | 0 .../src/methods => clippy_lints_methods/src}/manual_str_repeat.rs | 0 .../src/methods => clippy_lints_methods/src}/manual_try_fold.rs | 0 .../methods => clippy_lints_methods/src}/map_all_any_identity.rs | 0 .../src/methods => clippy_lints_methods/src}/map_clone.rs | 0 .../src}/map_collect_result_unit.rs | 0 .../src/methods => clippy_lints_methods/src}/map_err_ignore.rs | 0 .../src/methods => clippy_lints_methods/src}/map_flatten.rs | 0 .../src/methods => clippy_lints_methods/src}/map_identity.rs | 0 .../src/methods => clippy_lints_methods/src}/map_unwrap_or.rs | 0 .../src}/map_with_unused_argument_over_ranges.rs | 0 .../src/methods => clippy_lints_methods/src}/mut_mutex_lock.rs | 0 .../src/methods => clippy_lints_methods/src}/needless_as_bytes.rs | 0 .../src}/needless_character_iteration.rs | 0 .../src/methods => clippy_lints_methods/src}/needless_collect.rs | 0 .../src}/needless_option_as_deref.rs | 0 .../methods => clippy_lints_methods/src}/needless_option_take.rs | 0 .../src/methods => clippy_lints_methods/src}/no_effect_replace.rs | 0 .../methods => clippy_lints_methods/src}/obfuscated_if_else.rs | 0 .../src/methods => clippy_lints_methods/src}/ok_expect.rs | 0 .../src/methods => clippy_lints_methods/src}/open_options.rs | 0 .../methods => clippy_lints_methods/src}/option_as_ref_cloned.rs | 0 .../methods => clippy_lints_methods/src}/option_as_ref_deref.rs | 0 .../methods => clippy_lints_methods/src}/option_map_or_none.rs | 0 .../methods => clippy_lints_methods/src}/option_map_unwrap_or.rs | 0 .../src/methods => clippy_lints_methods/src}/or_fun_call.rs | 0 .../src/methods => clippy_lints_methods/src}/or_then_unwrap.rs | 0 .../src}/path_buf_push_overwrite.rs | 0 .../methods => clippy_lints_methods/src}/path_ends_with_ext.rs | 0 .../methods => clippy_lints_methods/src}/range_zip_with_len.rs | 0 .../src}/read_line_without_trim.rs | 0 .../methods => clippy_lints_methods/src}/readonly_write_lock.rs | 0 .../src/methods => clippy_lints_methods/src}/redundant_as_str.rs | 0 .../src/methods => clippy_lints_methods/src}/repeat_once.rs | 0 .../src}/result_map_or_else_none.rs | 0 .../src/methods => clippy_lints_methods/src}/return_and_then.rs | 0 .../src/methods => clippy_lints_methods/src}/search_is_some.rs | 0 .../src/methods => clippy_lints_methods/src}/seek_from_current.rs | 0 .../src}/seek_to_start_instead_of_rewind.rs | 0 .../methods => clippy_lints_methods/src}/single_char_add_str.rs | 0 .../src}/single_char_insert_string.rs | 0 .../src}/single_char_push_string.rs | 0 .../src/methods => clippy_lints_methods/src}/skip_while_next.rs | 0 .../src}/sliced_string_as_bytes.rs | 0 .../methods => clippy_lints_methods/src}/stable_sort_primitive.rs | 0 .../src/methods => clippy_lints_methods/src}/str_split.rs | 0 .../src/methods => clippy_lints_methods/src}/str_splitn.rs | 0 .../methods => clippy_lints_methods/src}/string_extend_chars.rs | 0 .../methods => clippy_lints_methods/src}/string_lit_chars_any.rs | 0 .../src}/suspicious_command_arg_space.rs | 0 .../src/methods => clippy_lints_methods/src}/suspicious_map.rs | 0 .../src/methods => clippy_lints_methods/src}/suspicious_splitn.rs | 0 .../methods => clippy_lints_methods/src}/suspicious_to_owned.rs | 0 .../methods => clippy_lints_methods/src}/swap_with_temporary.rs | 0 .../src/methods => clippy_lints_methods/src}/type_id_on_box.rs | 0 .../src/methods => clippy_lints_methods/src}/unbuffered_bytes.rs | 0 .../methods => clippy_lints_methods/src}/uninit_assumed_init.rs | 0 .../src/methods => clippy_lints_methods/src}/unit_hash.rs | 0 .../src}/unnecessary_fallible_conversions.rs | 0 .../src}/unnecessary_filter_map.rs | 0 .../src}/unnecessary_first_then_check.rs | 0 .../src/methods => clippy_lints_methods/src}/unnecessary_fold.rs | 0 .../src}/unnecessary_get_then_check.rs | 0 .../src}/unnecessary_iter_cloned.rs | 0 .../src/methods => clippy_lints_methods/src}/unnecessary_join.rs | 0 .../methods => clippy_lints_methods/src}/unnecessary_lazy_eval.rs | 0 .../src}/unnecessary_literal_unwrap.rs | 0 .../methods => clippy_lints_methods/src}/unnecessary_map_or.rs | 0 .../src}/unnecessary_min_or_max.rs | 0 .../src}/unnecessary_result_map_or_else.rs | 0 .../methods => clippy_lints_methods/src}/unnecessary_sort_by.rs | 0 .../methods => clippy_lints_methods/src}/unnecessary_to_owned.rs | 0 .../methods => clippy_lints_methods/src}/unwrap_expect_used.rs | 0 .../src/methods => clippy_lints_methods/src}/useless_asref.rs | 0 .../src}/useless_nonzero_new_unchecked.rs | 0 {clippy_lints/src/methods => clippy_lints_methods/src}/utils.rs | 0 .../methods => clippy_lints_methods/src}/vec_resize_to_zero.rs | 0 .../methods => clippy_lints_methods/src}/verbose_file_reads.rs | 0 .../src/methods => clippy_lints_methods/src}/waker_clone_wake.rs | 0 .../methods => clippy_lints_methods/src}/wrong_self_convention.rs | 0 .../src/methods => clippy_lints_methods/src}/zst_offset.rs | 0 146 files changed, 0 insertions(+), 0 deletions(-) rename {clippy_lints/src/methods => clippy_lints_methods/src}/bind_instead_of_map.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/bytecount.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/bytes_count_to_len.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/bytes_nth.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/case_sensitive_file_extension_comparisons.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/chars_cmp.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/chars_cmp_with_unwrap.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/chars_last_cmp.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/chars_last_cmp_with_unwrap.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/chars_next_cmp.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/chars_next_cmp_with_unwrap.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/clear_with_drain.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/clone_on_copy.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/clone_on_ref_ptr.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/cloned_instead_of_copied.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/collapsible_str_replace.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/double_ended_iterator_last.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/drain_collect.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/err_expect.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/expect_fun_call.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/extend_with_drain.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/filetype_is_file.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/filter_map.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/filter_map_bool_then.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/filter_map_identity.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/filter_map_next.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/filter_next.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/flat_map_identity.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/flat_map_option.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/format_collect.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/from_iter_instead_of_collect.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/get_first.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/get_last_with_len.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/get_unwrap.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/implicit_clone.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/inefficient_to_string.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/inspect_for_each.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/into_iter_on_ref.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/io_other_error.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/ip_constant.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/is_digit_ascii_radix.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/is_empty.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_cloned_collect.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_count.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_filter.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_kv_map.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_next_slice.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_nth.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_nth_zero.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_on_single_or_empty_collections.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_out_of_bounds.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_overeager_cloned.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_skip_next.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_skip_zero.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iter_with_drain.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/iterator_step_by_zero.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/join_absolute_paths.rs (100%) rename clippy_lints/src/methods/mod.rs => clippy_lints_methods/src/lib.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/manual_c_str_literals.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/manual_contains.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/manual_inspect.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/manual_is_variant_and.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/manual_next_back.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/manual_ok_or.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/manual_repeat_n.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/manual_saturating_arithmetic.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/manual_str_repeat.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/manual_try_fold.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/map_all_any_identity.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/map_clone.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/map_collect_result_unit.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/map_err_ignore.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/map_flatten.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/map_identity.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/map_unwrap_or.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/map_with_unused_argument_over_ranges.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/mut_mutex_lock.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/needless_as_bytes.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/needless_character_iteration.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/needless_collect.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/needless_option_as_deref.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/needless_option_take.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/no_effect_replace.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/obfuscated_if_else.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/ok_expect.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/open_options.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/option_as_ref_cloned.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/option_as_ref_deref.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/option_map_or_none.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/option_map_unwrap_or.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/or_fun_call.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/or_then_unwrap.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/path_buf_push_overwrite.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/path_ends_with_ext.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/range_zip_with_len.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/read_line_without_trim.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/readonly_write_lock.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/redundant_as_str.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/repeat_once.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/result_map_or_else_none.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/return_and_then.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/search_is_some.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/seek_from_current.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/seek_to_start_instead_of_rewind.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/single_char_add_str.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/single_char_insert_string.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/single_char_push_string.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/skip_while_next.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/sliced_string_as_bytes.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/stable_sort_primitive.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/str_split.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/str_splitn.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/string_extend_chars.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/string_lit_chars_any.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/suspicious_command_arg_space.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/suspicious_map.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/suspicious_splitn.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/suspicious_to_owned.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/swap_with_temporary.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/type_id_on_box.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unbuffered_bytes.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/uninit_assumed_init.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unit_hash.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_fallible_conversions.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_filter_map.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_first_then_check.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_fold.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_get_then_check.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_iter_cloned.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_join.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_lazy_eval.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_literal_unwrap.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_map_or.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_min_or_max.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_result_map_or_else.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_sort_by.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unnecessary_to_owned.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/unwrap_expect_used.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/useless_asref.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/useless_nonzero_new_unchecked.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/utils.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/vec_resize_to_zero.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/verbose_file_reads.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/waker_clone_wake.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/wrong_self_convention.rs (100%) rename {clippy_lints/src/methods => clippy_lints_methods/src}/zst_offset.rs (100%) diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints_methods/src/bind_instead_of_map.rs similarity index 100% rename from clippy_lints/src/methods/bind_instead_of_map.rs rename to clippy_lints_methods/src/bind_instead_of_map.rs diff --git a/clippy_lints/src/methods/bytecount.rs b/clippy_lints_methods/src/bytecount.rs similarity index 100% rename from clippy_lints/src/methods/bytecount.rs rename to clippy_lints_methods/src/bytecount.rs diff --git a/clippy_lints/src/methods/bytes_count_to_len.rs b/clippy_lints_methods/src/bytes_count_to_len.rs similarity index 100% rename from clippy_lints/src/methods/bytes_count_to_len.rs rename to clippy_lints_methods/src/bytes_count_to_len.rs diff --git a/clippy_lints/src/methods/bytes_nth.rs b/clippy_lints_methods/src/bytes_nth.rs similarity index 100% rename from clippy_lints/src/methods/bytes_nth.rs rename to clippy_lints_methods/src/bytes_nth.rs diff --git a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/clippy_lints_methods/src/case_sensitive_file_extension_comparisons.rs similarity index 100% rename from clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs rename to clippy_lints_methods/src/case_sensitive_file_extension_comparisons.rs diff --git a/clippy_lints/src/methods/chars_cmp.rs b/clippy_lints_methods/src/chars_cmp.rs similarity index 100% rename from clippy_lints/src/methods/chars_cmp.rs rename to clippy_lints_methods/src/chars_cmp.rs diff --git a/clippy_lints/src/methods/chars_cmp_with_unwrap.rs b/clippy_lints_methods/src/chars_cmp_with_unwrap.rs similarity index 100% rename from clippy_lints/src/methods/chars_cmp_with_unwrap.rs rename to clippy_lints_methods/src/chars_cmp_with_unwrap.rs diff --git a/clippy_lints/src/methods/chars_last_cmp.rs b/clippy_lints_methods/src/chars_last_cmp.rs similarity index 100% rename from clippy_lints/src/methods/chars_last_cmp.rs rename to clippy_lints_methods/src/chars_last_cmp.rs diff --git a/clippy_lints/src/methods/chars_last_cmp_with_unwrap.rs b/clippy_lints_methods/src/chars_last_cmp_with_unwrap.rs similarity index 100% rename from clippy_lints/src/methods/chars_last_cmp_with_unwrap.rs rename to clippy_lints_methods/src/chars_last_cmp_with_unwrap.rs diff --git a/clippy_lints/src/methods/chars_next_cmp.rs b/clippy_lints_methods/src/chars_next_cmp.rs similarity index 100% rename from clippy_lints/src/methods/chars_next_cmp.rs rename to clippy_lints_methods/src/chars_next_cmp.rs diff --git a/clippy_lints/src/methods/chars_next_cmp_with_unwrap.rs b/clippy_lints_methods/src/chars_next_cmp_with_unwrap.rs similarity index 100% rename from clippy_lints/src/methods/chars_next_cmp_with_unwrap.rs rename to clippy_lints_methods/src/chars_next_cmp_with_unwrap.rs diff --git a/clippy_lints/src/methods/clear_with_drain.rs b/clippy_lints_methods/src/clear_with_drain.rs similarity index 100% rename from clippy_lints/src/methods/clear_with_drain.rs rename to clippy_lints_methods/src/clear_with_drain.rs diff --git a/clippy_lints/src/methods/clone_on_copy.rs b/clippy_lints_methods/src/clone_on_copy.rs similarity index 100% rename from clippy_lints/src/methods/clone_on_copy.rs rename to clippy_lints_methods/src/clone_on_copy.rs diff --git a/clippy_lints/src/methods/clone_on_ref_ptr.rs b/clippy_lints_methods/src/clone_on_ref_ptr.rs similarity index 100% rename from clippy_lints/src/methods/clone_on_ref_ptr.rs rename to clippy_lints_methods/src/clone_on_ref_ptr.rs diff --git a/clippy_lints/src/methods/cloned_instead_of_copied.rs b/clippy_lints_methods/src/cloned_instead_of_copied.rs similarity index 100% rename from clippy_lints/src/methods/cloned_instead_of_copied.rs rename to clippy_lints_methods/src/cloned_instead_of_copied.rs diff --git a/clippy_lints/src/methods/collapsible_str_replace.rs b/clippy_lints_methods/src/collapsible_str_replace.rs similarity index 100% rename from clippy_lints/src/methods/collapsible_str_replace.rs rename to clippy_lints_methods/src/collapsible_str_replace.rs diff --git a/clippy_lints/src/methods/double_ended_iterator_last.rs b/clippy_lints_methods/src/double_ended_iterator_last.rs similarity index 100% rename from clippy_lints/src/methods/double_ended_iterator_last.rs rename to clippy_lints_methods/src/double_ended_iterator_last.rs diff --git a/clippy_lints/src/methods/drain_collect.rs b/clippy_lints_methods/src/drain_collect.rs similarity index 100% rename from clippy_lints/src/methods/drain_collect.rs rename to clippy_lints_methods/src/drain_collect.rs diff --git a/clippy_lints/src/methods/err_expect.rs b/clippy_lints_methods/src/err_expect.rs similarity index 100% rename from clippy_lints/src/methods/err_expect.rs rename to clippy_lints_methods/src/err_expect.rs diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints_methods/src/expect_fun_call.rs similarity index 100% rename from clippy_lints/src/methods/expect_fun_call.rs rename to clippy_lints_methods/src/expect_fun_call.rs diff --git a/clippy_lints/src/methods/extend_with_drain.rs b/clippy_lints_methods/src/extend_with_drain.rs similarity index 100% rename from clippy_lints/src/methods/extend_with_drain.rs rename to clippy_lints_methods/src/extend_with_drain.rs diff --git a/clippy_lints/src/methods/filetype_is_file.rs b/clippy_lints_methods/src/filetype_is_file.rs similarity index 100% rename from clippy_lints/src/methods/filetype_is_file.rs rename to clippy_lints_methods/src/filetype_is_file.rs diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints_methods/src/filter_map.rs similarity index 100% rename from clippy_lints/src/methods/filter_map.rs rename to clippy_lints_methods/src/filter_map.rs diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints_methods/src/filter_map_bool_then.rs similarity index 100% rename from clippy_lints/src/methods/filter_map_bool_then.rs rename to clippy_lints_methods/src/filter_map_bool_then.rs diff --git a/clippy_lints/src/methods/filter_map_identity.rs b/clippy_lints_methods/src/filter_map_identity.rs similarity index 100% rename from clippy_lints/src/methods/filter_map_identity.rs rename to clippy_lints_methods/src/filter_map_identity.rs diff --git a/clippy_lints/src/methods/filter_map_next.rs b/clippy_lints_methods/src/filter_map_next.rs similarity index 100% rename from clippy_lints/src/methods/filter_map_next.rs rename to clippy_lints_methods/src/filter_map_next.rs diff --git a/clippy_lints/src/methods/filter_next.rs b/clippy_lints_methods/src/filter_next.rs similarity index 100% rename from clippy_lints/src/methods/filter_next.rs rename to clippy_lints_methods/src/filter_next.rs diff --git a/clippy_lints/src/methods/flat_map_identity.rs b/clippy_lints_methods/src/flat_map_identity.rs similarity index 100% rename from clippy_lints/src/methods/flat_map_identity.rs rename to clippy_lints_methods/src/flat_map_identity.rs diff --git a/clippy_lints/src/methods/flat_map_option.rs b/clippy_lints_methods/src/flat_map_option.rs similarity index 100% rename from clippy_lints/src/methods/flat_map_option.rs rename to clippy_lints_methods/src/flat_map_option.rs diff --git a/clippy_lints/src/methods/format_collect.rs b/clippy_lints_methods/src/format_collect.rs similarity index 100% rename from clippy_lints/src/methods/format_collect.rs rename to clippy_lints_methods/src/format_collect.rs diff --git a/clippy_lints/src/methods/from_iter_instead_of_collect.rs b/clippy_lints_methods/src/from_iter_instead_of_collect.rs similarity index 100% rename from clippy_lints/src/methods/from_iter_instead_of_collect.rs rename to clippy_lints_methods/src/from_iter_instead_of_collect.rs diff --git a/clippy_lints/src/methods/get_first.rs b/clippy_lints_methods/src/get_first.rs similarity index 100% rename from clippy_lints/src/methods/get_first.rs rename to clippy_lints_methods/src/get_first.rs diff --git a/clippy_lints/src/methods/get_last_with_len.rs b/clippy_lints_methods/src/get_last_with_len.rs similarity index 100% rename from clippy_lints/src/methods/get_last_with_len.rs rename to clippy_lints_methods/src/get_last_with_len.rs diff --git a/clippy_lints/src/methods/get_unwrap.rs b/clippy_lints_methods/src/get_unwrap.rs similarity index 100% rename from clippy_lints/src/methods/get_unwrap.rs rename to clippy_lints_methods/src/get_unwrap.rs diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints_methods/src/implicit_clone.rs similarity index 100% rename from clippy_lints/src/methods/implicit_clone.rs rename to clippy_lints_methods/src/implicit_clone.rs diff --git a/clippy_lints/src/methods/inefficient_to_string.rs b/clippy_lints_methods/src/inefficient_to_string.rs similarity index 100% rename from clippy_lints/src/methods/inefficient_to_string.rs rename to clippy_lints_methods/src/inefficient_to_string.rs diff --git a/clippy_lints/src/methods/inspect_for_each.rs b/clippy_lints_methods/src/inspect_for_each.rs similarity index 100% rename from clippy_lints/src/methods/inspect_for_each.rs rename to clippy_lints_methods/src/inspect_for_each.rs diff --git a/clippy_lints/src/methods/into_iter_on_ref.rs b/clippy_lints_methods/src/into_iter_on_ref.rs similarity index 100% rename from clippy_lints/src/methods/into_iter_on_ref.rs rename to clippy_lints_methods/src/into_iter_on_ref.rs diff --git a/clippy_lints/src/methods/io_other_error.rs b/clippy_lints_methods/src/io_other_error.rs similarity index 100% rename from clippy_lints/src/methods/io_other_error.rs rename to clippy_lints_methods/src/io_other_error.rs diff --git a/clippy_lints/src/methods/ip_constant.rs b/clippy_lints_methods/src/ip_constant.rs similarity index 100% rename from clippy_lints/src/methods/ip_constant.rs rename to clippy_lints_methods/src/ip_constant.rs diff --git a/clippy_lints/src/methods/is_digit_ascii_radix.rs b/clippy_lints_methods/src/is_digit_ascii_radix.rs similarity index 100% rename from clippy_lints/src/methods/is_digit_ascii_radix.rs rename to clippy_lints_methods/src/is_digit_ascii_radix.rs diff --git a/clippy_lints/src/methods/is_empty.rs b/clippy_lints_methods/src/is_empty.rs similarity index 100% rename from clippy_lints/src/methods/is_empty.rs rename to clippy_lints_methods/src/is_empty.rs diff --git a/clippy_lints/src/methods/iter_cloned_collect.rs b/clippy_lints_methods/src/iter_cloned_collect.rs similarity index 100% rename from clippy_lints/src/methods/iter_cloned_collect.rs rename to clippy_lints_methods/src/iter_cloned_collect.rs diff --git a/clippy_lints/src/methods/iter_count.rs b/clippy_lints_methods/src/iter_count.rs similarity index 100% rename from clippy_lints/src/methods/iter_count.rs rename to clippy_lints_methods/src/iter_count.rs diff --git a/clippy_lints/src/methods/iter_filter.rs b/clippy_lints_methods/src/iter_filter.rs similarity index 100% rename from clippy_lints/src/methods/iter_filter.rs rename to clippy_lints_methods/src/iter_filter.rs diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints_methods/src/iter_kv_map.rs similarity index 100% rename from clippy_lints/src/methods/iter_kv_map.rs rename to clippy_lints_methods/src/iter_kv_map.rs diff --git a/clippy_lints/src/methods/iter_next_slice.rs b/clippy_lints_methods/src/iter_next_slice.rs similarity index 100% rename from clippy_lints/src/methods/iter_next_slice.rs rename to clippy_lints_methods/src/iter_next_slice.rs diff --git a/clippy_lints/src/methods/iter_nth.rs b/clippy_lints_methods/src/iter_nth.rs similarity index 100% rename from clippy_lints/src/methods/iter_nth.rs rename to clippy_lints_methods/src/iter_nth.rs diff --git a/clippy_lints/src/methods/iter_nth_zero.rs b/clippy_lints_methods/src/iter_nth_zero.rs similarity index 100% rename from clippy_lints/src/methods/iter_nth_zero.rs rename to clippy_lints_methods/src/iter_nth_zero.rs diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints_methods/src/iter_on_single_or_empty_collections.rs similarity index 100% rename from clippy_lints/src/methods/iter_on_single_or_empty_collections.rs rename to clippy_lints_methods/src/iter_on_single_or_empty_collections.rs diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints_methods/src/iter_out_of_bounds.rs similarity index 100% rename from clippy_lints/src/methods/iter_out_of_bounds.rs rename to clippy_lints_methods/src/iter_out_of_bounds.rs diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints_methods/src/iter_overeager_cloned.rs similarity index 100% rename from clippy_lints/src/methods/iter_overeager_cloned.rs rename to clippy_lints_methods/src/iter_overeager_cloned.rs diff --git a/clippy_lints/src/methods/iter_skip_next.rs b/clippy_lints_methods/src/iter_skip_next.rs similarity index 100% rename from clippy_lints/src/methods/iter_skip_next.rs rename to clippy_lints_methods/src/iter_skip_next.rs diff --git a/clippy_lints/src/methods/iter_skip_zero.rs b/clippy_lints_methods/src/iter_skip_zero.rs similarity index 100% rename from clippy_lints/src/methods/iter_skip_zero.rs rename to clippy_lints_methods/src/iter_skip_zero.rs diff --git a/clippy_lints/src/methods/iter_with_drain.rs b/clippy_lints_methods/src/iter_with_drain.rs similarity index 100% rename from clippy_lints/src/methods/iter_with_drain.rs rename to clippy_lints_methods/src/iter_with_drain.rs diff --git a/clippy_lints/src/methods/iterator_step_by_zero.rs b/clippy_lints_methods/src/iterator_step_by_zero.rs similarity index 100% rename from clippy_lints/src/methods/iterator_step_by_zero.rs rename to clippy_lints_methods/src/iterator_step_by_zero.rs diff --git a/clippy_lints/src/methods/join_absolute_paths.rs b/clippy_lints_methods/src/join_absolute_paths.rs similarity index 100% rename from clippy_lints/src/methods/join_absolute_paths.rs rename to clippy_lints_methods/src/join_absolute_paths.rs diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints_methods/src/lib.rs similarity index 100% rename from clippy_lints/src/methods/mod.rs rename to clippy_lints_methods/src/lib.rs diff --git a/clippy_lints/src/methods/manual_c_str_literals.rs b/clippy_lints_methods/src/manual_c_str_literals.rs similarity index 100% rename from clippy_lints/src/methods/manual_c_str_literals.rs rename to clippy_lints_methods/src/manual_c_str_literals.rs diff --git a/clippy_lints/src/methods/manual_contains.rs b/clippy_lints_methods/src/manual_contains.rs similarity index 100% rename from clippy_lints/src/methods/manual_contains.rs rename to clippy_lints_methods/src/manual_contains.rs diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints_methods/src/manual_inspect.rs similarity index 100% rename from clippy_lints/src/methods/manual_inspect.rs rename to clippy_lints_methods/src/manual_inspect.rs diff --git a/clippy_lints/src/methods/manual_is_variant_and.rs b/clippy_lints_methods/src/manual_is_variant_and.rs similarity index 100% rename from clippy_lints/src/methods/manual_is_variant_and.rs rename to clippy_lints_methods/src/manual_is_variant_and.rs diff --git a/clippy_lints/src/methods/manual_next_back.rs b/clippy_lints_methods/src/manual_next_back.rs similarity index 100% rename from clippy_lints/src/methods/manual_next_back.rs rename to clippy_lints_methods/src/manual_next_back.rs diff --git a/clippy_lints/src/methods/manual_ok_or.rs b/clippy_lints_methods/src/manual_ok_or.rs similarity index 100% rename from clippy_lints/src/methods/manual_ok_or.rs rename to clippy_lints_methods/src/manual_ok_or.rs diff --git a/clippy_lints/src/methods/manual_repeat_n.rs b/clippy_lints_methods/src/manual_repeat_n.rs similarity index 100% rename from clippy_lints/src/methods/manual_repeat_n.rs rename to clippy_lints_methods/src/manual_repeat_n.rs diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints_methods/src/manual_saturating_arithmetic.rs similarity index 100% rename from clippy_lints/src/methods/manual_saturating_arithmetic.rs rename to clippy_lints_methods/src/manual_saturating_arithmetic.rs diff --git a/clippy_lints/src/methods/manual_str_repeat.rs b/clippy_lints_methods/src/manual_str_repeat.rs similarity index 100% rename from clippy_lints/src/methods/manual_str_repeat.rs rename to clippy_lints_methods/src/manual_str_repeat.rs diff --git a/clippy_lints/src/methods/manual_try_fold.rs b/clippy_lints_methods/src/manual_try_fold.rs similarity index 100% rename from clippy_lints/src/methods/manual_try_fold.rs rename to clippy_lints_methods/src/manual_try_fold.rs diff --git a/clippy_lints/src/methods/map_all_any_identity.rs b/clippy_lints_methods/src/map_all_any_identity.rs similarity index 100% rename from clippy_lints/src/methods/map_all_any_identity.rs rename to clippy_lints_methods/src/map_all_any_identity.rs diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints_methods/src/map_clone.rs similarity index 100% rename from clippy_lints/src/methods/map_clone.rs rename to clippy_lints_methods/src/map_clone.rs diff --git a/clippy_lints/src/methods/map_collect_result_unit.rs b/clippy_lints_methods/src/map_collect_result_unit.rs similarity index 100% rename from clippy_lints/src/methods/map_collect_result_unit.rs rename to clippy_lints_methods/src/map_collect_result_unit.rs diff --git a/clippy_lints/src/methods/map_err_ignore.rs b/clippy_lints_methods/src/map_err_ignore.rs similarity index 100% rename from clippy_lints/src/methods/map_err_ignore.rs rename to clippy_lints_methods/src/map_err_ignore.rs diff --git a/clippy_lints/src/methods/map_flatten.rs b/clippy_lints_methods/src/map_flatten.rs similarity index 100% rename from clippy_lints/src/methods/map_flatten.rs rename to clippy_lints_methods/src/map_flatten.rs diff --git a/clippy_lints/src/methods/map_identity.rs b/clippy_lints_methods/src/map_identity.rs similarity index 100% rename from clippy_lints/src/methods/map_identity.rs rename to clippy_lints_methods/src/map_identity.rs diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints_methods/src/map_unwrap_or.rs similarity index 100% rename from clippy_lints/src/methods/map_unwrap_or.rs rename to clippy_lints_methods/src/map_unwrap_or.rs diff --git a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs b/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs similarity index 100% rename from clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs rename to clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs diff --git a/clippy_lints/src/methods/mut_mutex_lock.rs b/clippy_lints_methods/src/mut_mutex_lock.rs similarity index 100% rename from clippy_lints/src/methods/mut_mutex_lock.rs rename to clippy_lints_methods/src/mut_mutex_lock.rs diff --git a/clippy_lints/src/methods/needless_as_bytes.rs b/clippy_lints_methods/src/needless_as_bytes.rs similarity index 100% rename from clippy_lints/src/methods/needless_as_bytes.rs rename to clippy_lints_methods/src/needless_as_bytes.rs diff --git a/clippy_lints/src/methods/needless_character_iteration.rs b/clippy_lints_methods/src/needless_character_iteration.rs similarity index 100% rename from clippy_lints/src/methods/needless_character_iteration.rs rename to clippy_lints_methods/src/needless_character_iteration.rs diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints_methods/src/needless_collect.rs similarity index 100% rename from clippy_lints/src/methods/needless_collect.rs rename to clippy_lints_methods/src/needless_collect.rs diff --git a/clippy_lints/src/methods/needless_option_as_deref.rs b/clippy_lints_methods/src/needless_option_as_deref.rs similarity index 100% rename from clippy_lints/src/methods/needless_option_as_deref.rs rename to clippy_lints_methods/src/needless_option_as_deref.rs diff --git a/clippy_lints/src/methods/needless_option_take.rs b/clippy_lints_methods/src/needless_option_take.rs similarity index 100% rename from clippy_lints/src/methods/needless_option_take.rs rename to clippy_lints_methods/src/needless_option_take.rs diff --git a/clippy_lints/src/methods/no_effect_replace.rs b/clippy_lints_methods/src/no_effect_replace.rs similarity index 100% rename from clippy_lints/src/methods/no_effect_replace.rs rename to clippy_lints_methods/src/no_effect_replace.rs diff --git a/clippy_lints/src/methods/obfuscated_if_else.rs b/clippy_lints_methods/src/obfuscated_if_else.rs similarity index 100% rename from clippy_lints/src/methods/obfuscated_if_else.rs rename to clippy_lints_methods/src/obfuscated_if_else.rs diff --git a/clippy_lints/src/methods/ok_expect.rs b/clippy_lints_methods/src/ok_expect.rs similarity index 100% rename from clippy_lints/src/methods/ok_expect.rs rename to clippy_lints_methods/src/ok_expect.rs diff --git a/clippy_lints/src/methods/open_options.rs b/clippy_lints_methods/src/open_options.rs similarity index 100% rename from clippy_lints/src/methods/open_options.rs rename to clippy_lints_methods/src/open_options.rs diff --git a/clippy_lints/src/methods/option_as_ref_cloned.rs b/clippy_lints_methods/src/option_as_ref_cloned.rs similarity index 100% rename from clippy_lints/src/methods/option_as_ref_cloned.rs rename to clippy_lints_methods/src/option_as_ref_cloned.rs diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints_methods/src/option_as_ref_deref.rs similarity index 100% rename from clippy_lints/src/methods/option_as_ref_deref.rs rename to clippy_lints_methods/src/option_as_ref_deref.rs diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints_methods/src/option_map_or_none.rs similarity index 100% rename from clippy_lints/src/methods/option_map_or_none.rs rename to clippy_lints_methods/src/option_map_or_none.rs diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints_methods/src/option_map_unwrap_or.rs similarity index 100% rename from clippy_lints/src/methods/option_map_unwrap_or.rs rename to clippy_lints_methods/src/option_map_unwrap_or.rs diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints_methods/src/or_fun_call.rs similarity index 100% rename from clippy_lints/src/methods/or_fun_call.rs rename to clippy_lints_methods/src/or_fun_call.rs diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints_methods/src/or_then_unwrap.rs similarity index 100% rename from clippy_lints/src/methods/or_then_unwrap.rs rename to clippy_lints_methods/src/or_then_unwrap.rs diff --git a/clippy_lints/src/methods/path_buf_push_overwrite.rs b/clippy_lints_methods/src/path_buf_push_overwrite.rs similarity index 100% rename from clippy_lints/src/methods/path_buf_push_overwrite.rs rename to clippy_lints_methods/src/path_buf_push_overwrite.rs diff --git a/clippy_lints/src/methods/path_ends_with_ext.rs b/clippy_lints_methods/src/path_ends_with_ext.rs similarity index 100% rename from clippy_lints/src/methods/path_ends_with_ext.rs rename to clippy_lints_methods/src/path_ends_with_ext.rs diff --git a/clippy_lints/src/methods/range_zip_with_len.rs b/clippy_lints_methods/src/range_zip_with_len.rs similarity index 100% rename from clippy_lints/src/methods/range_zip_with_len.rs rename to clippy_lints_methods/src/range_zip_with_len.rs diff --git a/clippy_lints/src/methods/read_line_without_trim.rs b/clippy_lints_methods/src/read_line_without_trim.rs similarity index 100% rename from clippy_lints/src/methods/read_line_without_trim.rs rename to clippy_lints_methods/src/read_line_without_trim.rs diff --git a/clippy_lints/src/methods/readonly_write_lock.rs b/clippy_lints_methods/src/readonly_write_lock.rs similarity index 100% rename from clippy_lints/src/methods/readonly_write_lock.rs rename to clippy_lints_methods/src/readonly_write_lock.rs diff --git a/clippy_lints/src/methods/redundant_as_str.rs b/clippy_lints_methods/src/redundant_as_str.rs similarity index 100% rename from clippy_lints/src/methods/redundant_as_str.rs rename to clippy_lints_methods/src/redundant_as_str.rs diff --git a/clippy_lints/src/methods/repeat_once.rs b/clippy_lints_methods/src/repeat_once.rs similarity index 100% rename from clippy_lints/src/methods/repeat_once.rs rename to clippy_lints_methods/src/repeat_once.rs diff --git a/clippy_lints/src/methods/result_map_or_else_none.rs b/clippy_lints_methods/src/result_map_or_else_none.rs similarity index 100% rename from clippy_lints/src/methods/result_map_or_else_none.rs rename to clippy_lints_methods/src/result_map_or_else_none.rs diff --git a/clippy_lints/src/methods/return_and_then.rs b/clippy_lints_methods/src/return_and_then.rs similarity index 100% rename from clippy_lints/src/methods/return_and_then.rs rename to clippy_lints_methods/src/return_and_then.rs diff --git a/clippy_lints/src/methods/search_is_some.rs b/clippy_lints_methods/src/search_is_some.rs similarity index 100% rename from clippy_lints/src/methods/search_is_some.rs rename to clippy_lints_methods/src/search_is_some.rs diff --git a/clippy_lints/src/methods/seek_from_current.rs b/clippy_lints_methods/src/seek_from_current.rs similarity index 100% rename from clippy_lints/src/methods/seek_from_current.rs rename to clippy_lints_methods/src/seek_from_current.rs diff --git a/clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs b/clippy_lints_methods/src/seek_to_start_instead_of_rewind.rs similarity index 100% rename from clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs rename to clippy_lints_methods/src/seek_to_start_instead_of_rewind.rs diff --git a/clippy_lints/src/methods/single_char_add_str.rs b/clippy_lints_methods/src/single_char_add_str.rs similarity index 100% rename from clippy_lints/src/methods/single_char_add_str.rs rename to clippy_lints_methods/src/single_char_add_str.rs diff --git a/clippy_lints/src/methods/single_char_insert_string.rs b/clippy_lints_methods/src/single_char_insert_string.rs similarity index 100% rename from clippy_lints/src/methods/single_char_insert_string.rs rename to clippy_lints_methods/src/single_char_insert_string.rs diff --git a/clippy_lints/src/methods/single_char_push_string.rs b/clippy_lints_methods/src/single_char_push_string.rs similarity index 100% rename from clippy_lints/src/methods/single_char_push_string.rs rename to clippy_lints_methods/src/single_char_push_string.rs diff --git a/clippy_lints/src/methods/skip_while_next.rs b/clippy_lints_methods/src/skip_while_next.rs similarity index 100% rename from clippy_lints/src/methods/skip_while_next.rs rename to clippy_lints_methods/src/skip_while_next.rs diff --git a/clippy_lints/src/methods/sliced_string_as_bytes.rs b/clippy_lints_methods/src/sliced_string_as_bytes.rs similarity index 100% rename from clippy_lints/src/methods/sliced_string_as_bytes.rs rename to clippy_lints_methods/src/sliced_string_as_bytes.rs diff --git a/clippy_lints/src/methods/stable_sort_primitive.rs b/clippy_lints_methods/src/stable_sort_primitive.rs similarity index 100% rename from clippy_lints/src/methods/stable_sort_primitive.rs rename to clippy_lints_methods/src/stable_sort_primitive.rs diff --git a/clippy_lints/src/methods/str_split.rs b/clippy_lints_methods/src/str_split.rs similarity index 100% rename from clippy_lints/src/methods/str_split.rs rename to clippy_lints_methods/src/str_split.rs diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints_methods/src/str_splitn.rs similarity index 100% rename from clippy_lints/src/methods/str_splitn.rs rename to clippy_lints_methods/src/str_splitn.rs diff --git a/clippy_lints/src/methods/string_extend_chars.rs b/clippy_lints_methods/src/string_extend_chars.rs similarity index 100% rename from clippy_lints/src/methods/string_extend_chars.rs rename to clippy_lints_methods/src/string_extend_chars.rs diff --git a/clippy_lints/src/methods/string_lit_chars_any.rs b/clippy_lints_methods/src/string_lit_chars_any.rs similarity index 100% rename from clippy_lints/src/methods/string_lit_chars_any.rs rename to clippy_lints_methods/src/string_lit_chars_any.rs diff --git a/clippy_lints/src/methods/suspicious_command_arg_space.rs b/clippy_lints_methods/src/suspicious_command_arg_space.rs similarity index 100% rename from clippy_lints/src/methods/suspicious_command_arg_space.rs rename to clippy_lints_methods/src/suspicious_command_arg_space.rs diff --git a/clippy_lints/src/methods/suspicious_map.rs b/clippy_lints_methods/src/suspicious_map.rs similarity index 100% rename from clippy_lints/src/methods/suspicious_map.rs rename to clippy_lints_methods/src/suspicious_map.rs diff --git a/clippy_lints/src/methods/suspicious_splitn.rs b/clippy_lints_methods/src/suspicious_splitn.rs similarity index 100% rename from clippy_lints/src/methods/suspicious_splitn.rs rename to clippy_lints_methods/src/suspicious_splitn.rs diff --git a/clippy_lints/src/methods/suspicious_to_owned.rs b/clippy_lints_methods/src/suspicious_to_owned.rs similarity index 100% rename from clippy_lints/src/methods/suspicious_to_owned.rs rename to clippy_lints_methods/src/suspicious_to_owned.rs diff --git a/clippy_lints/src/methods/swap_with_temporary.rs b/clippy_lints_methods/src/swap_with_temporary.rs similarity index 100% rename from clippy_lints/src/methods/swap_with_temporary.rs rename to clippy_lints_methods/src/swap_with_temporary.rs diff --git a/clippy_lints/src/methods/type_id_on_box.rs b/clippy_lints_methods/src/type_id_on_box.rs similarity index 100% rename from clippy_lints/src/methods/type_id_on_box.rs rename to clippy_lints_methods/src/type_id_on_box.rs diff --git a/clippy_lints/src/methods/unbuffered_bytes.rs b/clippy_lints_methods/src/unbuffered_bytes.rs similarity index 100% rename from clippy_lints/src/methods/unbuffered_bytes.rs rename to clippy_lints_methods/src/unbuffered_bytes.rs diff --git a/clippy_lints/src/methods/uninit_assumed_init.rs b/clippy_lints_methods/src/uninit_assumed_init.rs similarity index 100% rename from clippy_lints/src/methods/uninit_assumed_init.rs rename to clippy_lints_methods/src/uninit_assumed_init.rs diff --git a/clippy_lints/src/methods/unit_hash.rs b/clippy_lints_methods/src/unit_hash.rs similarity index 100% rename from clippy_lints/src/methods/unit_hash.rs rename to clippy_lints_methods/src/unit_hash.rs diff --git a/clippy_lints/src/methods/unnecessary_fallible_conversions.rs b/clippy_lints_methods/src/unnecessary_fallible_conversions.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_fallible_conversions.rs rename to clippy_lints_methods/src/unnecessary_fallible_conversions.rs diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints_methods/src/unnecessary_filter_map.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_filter_map.rs rename to clippy_lints_methods/src/unnecessary_filter_map.rs diff --git a/clippy_lints/src/methods/unnecessary_first_then_check.rs b/clippy_lints_methods/src/unnecessary_first_then_check.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_first_then_check.rs rename to clippy_lints_methods/src/unnecessary_first_then_check.rs diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints_methods/src/unnecessary_fold.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_fold.rs rename to clippy_lints_methods/src/unnecessary_fold.rs diff --git a/clippy_lints/src/methods/unnecessary_get_then_check.rs b/clippy_lints_methods/src/unnecessary_get_then_check.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_get_then_check.rs rename to clippy_lints_methods/src/unnecessary_get_then_check.rs diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints_methods/src/unnecessary_iter_cloned.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_iter_cloned.rs rename to clippy_lints_methods/src/unnecessary_iter_cloned.rs diff --git a/clippy_lints/src/methods/unnecessary_join.rs b/clippy_lints_methods/src/unnecessary_join.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_join.rs rename to clippy_lints_methods/src/unnecessary_join.rs diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints_methods/src/unnecessary_lazy_eval.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_lazy_eval.rs rename to clippy_lints_methods/src/unnecessary_lazy_eval.rs diff --git a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs b/clippy_lints_methods/src/unnecessary_literal_unwrap.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_literal_unwrap.rs rename to clippy_lints_methods/src/unnecessary_literal_unwrap.rs diff --git a/clippy_lints/src/methods/unnecessary_map_or.rs b/clippy_lints_methods/src/unnecessary_map_or.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_map_or.rs rename to clippy_lints_methods/src/unnecessary_map_or.rs diff --git a/clippy_lints/src/methods/unnecessary_min_or_max.rs b/clippy_lints_methods/src/unnecessary_min_or_max.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_min_or_max.rs rename to clippy_lints_methods/src/unnecessary_min_or_max.rs diff --git a/clippy_lints/src/methods/unnecessary_result_map_or_else.rs b/clippy_lints_methods/src/unnecessary_result_map_or_else.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_result_map_or_else.rs rename to clippy_lints_methods/src/unnecessary_result_map_or_else.rs diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints_methods/src/unnecessary_sort_by.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_sort_by.rs rename to clippy_lints_methods/src/unnecessary_sort_by.rs diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints_methods/src/unnecessary_to_owned.rs similarity index 100% rename from clippy_lints/src/methods/unnecessary_to_owned.rs rename to clippy_lints_methods/src/unnecessary_to_owned.rs diff --git a/clippy_lints/src/methods/unwrap_expect_used.rs b/clippy_lints_methods/src/unwrap_expect_used.rs similarity index 100% rename from clippy_lints/src/methods/unwrap_expect_used.rs rename to clippy_lints_methods/src/unwrap_expect_used.rs diff --git a/clippy_lints/src/methods/useless_asref.rs b/clippy_lints_methods/src/useless_asref.rs similarity index 100% rename from clippy_lints/src/methods/useless_asref.rs rename to clippy_lints_methods/src/useless_asref.rs diff --git a/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs b/clippy_lints_methods/src/useless_nonzero_new_unchecked.rs similarity index 100% rename from clippy_lints/src/methods/useless_nonzero_new_unchecked.rs rename to clippy_lints_methods/src/useless_nonzero_new_unchecked.rs diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints_methods/src/utils.rs similarity index 100% rename from clippy_lints/src/methods/utils.rs rename to clippy_lints_methods/src/utils.rs diff --git a/clippy_lints/src/methods/vec_resize_to_zero.rs b/clippy_lints_methods/src/vec_resize_to_zero.rs similarity index 100% rename from clippy_lints/src/methods/vec_resize_to_zero.rs rename to clippy_lints_methods/src/vec_resize_to_zero.rs diff --git a/clippy_lints/src/methods/verbose_file_reads.rs b/clippy_lints_methods/src/verbose_file_reads.rs similarity index 100% rename from clippy_lints/src/methods/verbose_file_reads.rs rename to clippy_lints_methods/src/verbose_file_reads.rs diff --git a/clippy_lints/src/methods/waker_clone_wake.rs b/clippy_lints_methods/src/waker_clone_wake.rs similarity index 100% rename from clippy_lints/src/methods/waker_clone_wake.rs rename to clippy_lints_methods/src/waker_clone_wake.rs diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints_methods/src/wrong_self_convention.rs similarity index 100% rename from clippy_lints/src/methods/wrong_self_convention.rs rename to clippy_lints_methods/src/wrong_self_convention.rs diff --git a/clippy_lints/src/methods/zst_offset.rs b/clippy_lints_methods/src/zst_offset.rs similarity index 100% rename from clippy_lints/src/methods/zst_offset.rs rename to clippy_lints_methods/src/zst_offset.rs From 3dba4056ebf677438178038d1abcb2b591ab27d0 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Jul 2025 11:48:45 -0400 Subject: [PATCH 09/10] Move `methods` lint pass to a new crate part 2/2 --- Cargo.toml | 1 + clippy_dev/src/release.rs | 1 + clippy_dev/src/serve.rs | 1 + clippy_dev/src/setup/intellij.rs | 5 + clippy_lints/src/declared_lints.rs | 149 ----------------- clippy_lints/src/lib.rs | 8 +- clippy_lints_methods/Cargo.toml | 19 +++ clippy_lints_methods/src/bytes_nth.rs | 2 +- clippy_lints_methods/src/chars_cmp.rs | 2 +- .../src/chars_cmp_with_unwrap.rs | 2 +- clippy_lints_methods/src/chars_last_cmp.rs | 4 +- .../src/chars_last_cmp_with_unwrap.rs | 4 +- clippy_lints_methods/src/chars_next_cmp.rs | 4 +- .../src/chars_next_cmp_with_unwrap.rs | 4 +- clippy_lints_methods/src/declared_lints.rs | 155 ++++++++++++++++++ clippy_lints_methods/src/drain_collect.rs | 2 +- .../src/iter_cloned_collect.rs | 2 +- clippy_lints_methods/src/lib.rs | 55 ++++++- .../map_with_unused_argument_over_ranges.rs | 2 +- .../src/single_char_add_str.rs | 2 +- clippy_lints_methods/src/type_id_on_box.rs | 2 +- .../src/wrong_self_convention.rs | 2 +- src/driver.rs | 4 +- tests/dogfood.rs | 1 + 24 files changed, 258 insertions(+), 175 deletions(-) create mode 100644 clippy_lints_methods/Cargo.toml create mode 100644 clippy_lints_methods/src/declared_lints.rs diff --git a/Cargo.toml b/Cargo.toml index 2b6139e7e7f3..e26644871263 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ path = "src/driver.rs" [dependencies] clippy_config = { path = "clippy_config" } clippy_lints = { path = "clippy_lints" } +clippy_lints_methods = { path = "clippy_lints_methods" } clippy_utils = { path = "clippy_utils" } declare_clippy_lint = { path = "declare_clippy_lint" } rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" } diff --git a/clippy_dev/src/release.rs b/clippy_dev/src/release.rs index 15392dd1d292..c6d1564e1e46 100644 --- a/clippy_dev/src/release.rs +++ b/clippy_dev/src/release.rs @@ -4,6 +4,7 @@ use std::fmt::Write; static CARGO_TOML_FILES: &[&str] = &[ "clippy_config/Cargo.toml", "clippy_lints/Cargo.toml", + "clippy_lints_methods/Cargo.toml", "clippy_utils/Cargo.toml", "declare_clippy_lint/Cargo.toml", "Cargo.toml", diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index 498ffeba9d67..933a035c7d00 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -22,6 +22,7 @@ pub fn run(port: u16, lint: Option) -> ! { let index_time = mtime("util/gh-pages/index.html"); let times = [ "clippy_lints/src", + "clippy_lints_methods/src", "util/gh-pages/index_template.html", "tests/compile-test.rs", ] diff --git a/clippy_dev/src/setup/intellij.rs b/clippy_dev/src/setup/intellij.rs index c56811ee0a01..c5254105289e 100644 --- a/clippy_dev/src/setup/intellij.rs +++ b/clippy_dev/src/setup/intellij.rs @@ -14,6 +14,11 @@ const DEPENDENCIES_SECTION: &str = "[dependencies]"; const CLIPPY_PROJECTS: &[ClippyProjectInfo] = &[ ClippyProjectInfo::new("root", "Cargo.toml", "src/driver.rs"), ClippyProjectInfo::new("clippy_lints", "clippy_lints/Cargo.toml", "clippy_lints/src/lib.rs"), + ClippyProjectInfo::new( + "clippy_lints_methods", + "clippy_lints_methods/Cargo.toml", + "clippy_lints_methods/src/lib.rs", + ), ClippyProjectInfo::new("clippy_utils", "clippy_utils/Cargo.toml", "clippy_utils/src/lib.rs"), ]; diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index dec2da9c07fa..d4be135f8ce9 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -349,155 +349,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::mem_replace::MEM_REPLACE_OPTION_WITH_SOME_INFO, crate::mem_replace::MEM_REPLACE_WITH_DEFAULT_INFO, crate::mem_replace::MEM_REPLACE_WITH_UNINIT_INFO, - crate::methods::BIND_INSTEAD_OF_MAP_INFO, - crate::methods::BYTES_COUNT_TO_LEN_INFO, - crate::methods::BYTES_NTH_INFO, - crate::methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO, - crate::methods::CHARS_LAST_CMP_INFO, - crate::methods::CHARS_NEXT_CMP_INFO, - crate::methods::CLEAR_WITH_DRAIN_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, - crate::methods::DRAIN_COLLECT_INFO, - crate::methods::ERR_EXPECT_INFO, - crate::methods::EXPECT_FUN_CALL_INFO, - crate::methods::EXPECT_USED_INFO, - crate::methods::EXTEND_WITH_DRAIN_INFO, - crate::methods::FILETYPE_IS_FILE_INFO, - crate::methods::FILTER_MAP_BOOL_THEN_INFO, - crate::methods::FILTER_MAP_IDENTITY_INFO, - crate::methods::FILTER_MAP_NEXT_INFO, - crate::methods::FILTER_NEXT_INFO, - crate::methods::FLAT_MAP_IDENTITY_INFO, - crate::methods::FLAT_MAP_OPTION_INFO, - crate::methods::FORMAT_COLLECT_INFO, - crate::methods::FROM_ITER_INSTEAD_OF_COLLECT_INFO, - crate::methods::GET_FIRST_INFO, - crate::methods::GET_LAST_WITH_LEN_INFO, - crate::methods::GET_UNWRAP_INFO, - crate::methods::IMPLICIT_CLONE_INFO, - crate::methods::INEFFICIENT_TO_STRING_INFO, - crate::methods::INSPECT_FOR_EACH_INFO, - crate::methods::INTO_ITER_ON_REF_INFO, - crate::methods::IO_OTHER_ERROR_INFO, - crate::methods::IP_CONSTANT_INFO, - crate::methods::IS_DIGIT_ASCII_RADIX_INFO, - crate::methods::ITER_CLONED_COLLECT_INFO, - crate::methods::ITER_COUNT_INFO, - crate::methods::ITER_FILTER_IS_OK_INFO, - crate::methods::ITER_FILTER_IS_SOME_INFO, - crate::methods::ITER_KV_MAP_INFO, - crate::methods::ITER_NEXT_SLICE_INFO, - crate::methods::ITER_NTH_INFO, - crate::methods::ITER_NTH_ZERO_INFO, - crate::methods::ITER_ON_EMPTY_COLLECTIONS_INFO, - crate::methods::ITER_ON_SINGLE_ITEMS_INFO, - crate::methods::ITER_OUT_OF_BOUNDS_INFO, - crate::methods::ITER_OVEREAGER_CLONED_INFO, - 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_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, - crate::methods::MANUAL_IS_VARIANT_AND_INFO, - crate::methods::MANUAL_NEXT_BACK_INFO, - crate::methods::MANUAL_OK_OR_INFO, - crate::methods::MANUAL_REPEAT_N_INFO, - crate::methods::MANUAL_SATURATING_ARITHMETIC_INFO, - crate::methods::MANUAL_SPLIT_ONCE_INFO, - crate::methods::MANUAL_STR_REPEAT_INFO, - crate::methods::MANUAL_TRY_FOLD_INFO, - crate::methods::MAP_ALL_ANY_IDENTITY_INFO, - crate::methods::MAP_CLONE_INFO, - crate::methods::MAP_COLLECT_RESULT_UNIT_INFO, - crate::methods::MAP_ERR_IGNORE_INFO, - crate::methods::MAP_FLATTEN_INFO, - crate::methods::MAP_IDENTITY_INFO, - crate::methods::MAP_UNWRAP_OR_INFO, - crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO, - crate::methods::MUT_MUTEX_LOCK_INFO, - crate::methods::NAIVE_BYTECOUNT_INFO, - crate::methods::NEEDLESS_AS_BYTES_INFO, - crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO, - crate::methods::NEEDLESS_COLLECT_INFO, - crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO, - crate::methods::NEEDLESS_OPTION_TAKE_INFO, - crate::methods::NEEDLESS_SPLITN_INFO, - crate::methods::NEW_RET_NO_SELF_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, - crate::methods::OPTION_AS_REF_DEREF_INFO, - crate::methods::OPTION_FILTER_MAP_INFO, - crate::methods::OPTION_MAP_OR_NONE_INFO, - crate::methods::OR_FUN_CALL_INFO, - crate::methods::OR_THEN_UNWRAP_INFO, - crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO, - crate::methods::PATH_ENDS_WITH_EXT_INFO, - crate::methods::RANGE_ZIP_WITH_LEN_INFO, - crate::methods::READ_LINE_WITHOUT_TRIM_INFO, - crate::methods::READONLY_WRITE_LOCK_INFO, - crate::methods::REDUNDANT_AS_STR_INFO, - crate::methods::REDUNDANT_ITER_CLONED_INFO, - crate::methods::REPEAT_ONCE_INFO, - crate::methods::RESULT_FILTER_MAP_INFO, - crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, - crate::methods::RETURN_AND_THEN_INFO, - crate::methods::SEARCH_IS_SOME_INFO, - crate::methods::SEEK_FROM_CURRENT_INFO, - crate::methods::SEEK_TO_START_INSTEAD_OF_REWIND_INFO, - crate::methods::SHOULD_IMPLEMENT_TRAIT_INFO, - crate::methods::SINGLE_CHAR_ADD_STR_INFO, - 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::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, - crate::methods::SUSPICIOUS_MAP_INFO, - crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO, - crate::methods::SUSPICIOUS_SPLITN_INFO, - crate::methods::SUSPICIOUS_TO_OWNED_INFO, - crate::methods::SWAP_WITH_TEMPORARY_INFO, - crate::methods::TYPE_ID_ON_BOX_INFO, - crate::methods::UNBUFFERED_BYTES_INFO, - crate::methods::UNINIT_ASSUMED_INIT_INFO, - crate::methods::UNIT_HASH_INFO, - crate::methods::UNNECESSARY_FALLIBLE_CONVERSIONS_INFO, - crate::methods::UNNECESSARY_FILTER_MAP_INFO, - crate::methods::UNNECESSARY_FIND_MAP_INFO, - crate::methods::UNNECESSARY_FIRST_THEN_CHECK_INFO, - crate::methods::UNNECESSARY_FOLD_INFO, - crate::methods::UNNECESSARY_GET_THEN_CHECK_INFO, - crate::methods::UNNECESSARY_JOIN_INFO, - crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO, - crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO, - crate::methods::UNNECESSARY_MAP_OR_INFO, - crate::methods::UNNECESSARY_MIN_OR_MAX_INFO, - crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO, - crate::methods::UNNECESSARY_SORT_BY_INFO, - crate::methods::UNNECESSARY_TO_OWNED_INFO, - crate::methods::UNWRAP_OR_DEFAULT_INFO, - crate::methods::UNWRAP_USED_INFO, - crate::methods::USELESS_ASREF_INFO, - crate::methods::USELESS_NONZERO_NEW_UNCHECKED_INFO, - crate::methods::VEC_RESIZE_TO_ZERO_INFO, - crate::methods::VERBOSE_FILE_READS_INFO, - crate::methods::WAKER_CLONE_WAKE_INFO, - crate::methods::WRONG_SELF_CONVENTION_INFO, - crate::methods::ZST_OFFSET_INFO, crate::min_ident_chars::MIN_IDENT_CHARS_INFO, crate::minmax::MIN_MAX_INFO, crate::misc::SHORT_CIRCUIT_STATEMENT_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 96a6dee58852..ab56fc93732a 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -55,7 +55,6 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; extern crate rustc_trait_selection; -extern crate smallvec; extern crate thin_vec; #[macro_use] @@ -231,7 +230,6 @@ mod map_unit_fn; mod match_result_ok; mod matches; mod mem_replace; -mod methods; mod min_ident_chars; mod minmax; mod misc; @@ -440,7 +438,7 @@ pub fn explain(name: &str) -> i32 { /// /// Used in `./src/driver.rs`. #[expect(clippy::too_many_lines)] -pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Conf) { +pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Conf) -> FormatArgsStorage { for (old_name, new_name) in deprecated_lints::RENAMED { store.register_renamed(old_name, new_name); } @@ -503,8 +501,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)); store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(conf))); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(methods::Methods::new(conf, format_args.clone()))); store.register_late_pass(move |_| Box::new(matches::Matches::new(conf))); store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustive::new(conf))); store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::new(conf))); @@ -831,4 +827,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom)); store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); // add lints here, do not remove this comment, it's used in `new_lint` + + format_args_storage } diff --git a/clippy_lints_methods/Cargo.toml b/clippy_lints_methods/Cargo.toml new file mode 100644 index 000000000000..31301d9f16b6 --- /dev/null +++ b/clippy_lints_methods/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "clippy_lints_methods" +version = "0.1.90" +description = "A bunch of helpful lints to avoid common pitfalls in Rust" +repository = "https://github.com/rust-lang/rust-clippy" +readme = "README.md" +license = "MIT OR Apache-2.0" +keywords = ["clippy", "lint", "plugin"] +edition = "2024" + +[dependencies] +clippy_config = { path = "../clippy_config" } +clippy_utils = { path = "../clippy_utils" } +declare_clippy_lint = { path = "../declare_clippy_lint" } +itertools = "0.12" + +[package.metadata.rust-analyzer] +# This crate uses #[feature(rustc_private)] +rustc_private = true diff --git a/clippy_lints_methods/src/bytes_nth.rs b/clippy_lints_methods/src/bytes_nth.rs index 02fc09170e59..f0df6e6269b4 100644 --- a/clippy_lints_methods/src/bytes_nth.rs +++ b/clippy_lints_methods/src/bytes_nth.rs @@ -6,7 +6,7 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; -use crate::methods::method_call; +use crate::method_call; use super::BYTES_NTH; diff --git a/clippy_lints_methods/src/chars_cmp.rs b/clippy_lints_methods/src/chars_cmp.rs index de27a45ba4d9..7f2bfb6bd5d3 100644 --- a/clippy_lints_methods/src/chars_cmp.rs +++ b/clippy_lints_methods/src/chars_cmp.rs @@ -10,7 +10,7 @@ use rustc_span::Symbol; /// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. pub(super) fn check( cx: &LateContext<'_>, - info: &crate::methods::BinaryExprInfo<'_>, + info: &crate::BinaryExprInfo<'_>, chain_methods: &[Symbol], lint: &'static Lint, suggest: &str, diff --git a/clippy_lints_methods/src/chars_cmp_with_unwrap.rs b/clippy_lints_methods/src/chars_cmp_with_unwrap.rs index 1c72a973cfa1..00db3eb02528 100644 --- a/clippy_lints_methods/src/chars_cmp_with_unwrap.rs +++ b/clippy_lints_methods/src/chars_cmp_with_unwrap.rs @@ -10,7 +10,7 @@ use rustc_span::Symbol; /// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`. pub(super) fn check( cx: &LateContext<'_>, - info: &crate::methods::BinaryExprInfo<'_>, + info: &crate::BinaryExprInfo<'_>, chain_methods: &[Symbol], lint: &'static Lint, suggest: &str, diff --git a/clippy_lints_methods/src/chars_last_cmp.rs b/clippy_lints_methods/src/chars_last_cmp.rs index 8729e91d191f..624637efbd62 100644 --- a/clippy_lints_methods/src/chars_last_cmp.rs +++ b/clippy_lints_methods/src/chars_last_cmp.rs @@ -1,11 +1,11 @@ -use crate::methods::chars_cmp; +use crate::chars_cmp; use clippy_utils::sym; use rustc_lint::LateContext; use super::CHARS_LAST_CMP; /// Checks for the `CHARS_LAST_CMP` lint. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool { +pub(super) fn check(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { if chars_cmp::check(cx, info, &[sym::chars, sym::last], CHARS_LAST_CMP, "ends_with") { true } else { diff --git a/clippy_lints_methods/src/chars_last_cmp_with_unwrap.rs b/clippy_lints_methods/src/chars_last_cmp_with_unwrap.rs index 027d0a3947bf..679c2e666a25 100644 --- a/clippy_lints_methods/src/chars_last_cmp_with_unwrap.rs +++ b/clippy_lints_methods/src/chars_last_cmp_with_unwrap.rs @@ -1,11 +1,11 @@ -use crate::methods::chars_cmp_with_unwrap; +use crate::chars_cmp_with_unwrap; use clippy_utils::sym; use rustc_lint::LateContext; use super::CHARS_LAST_CMP; /// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool { +pub(super) fn check(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { if chars_cmp_with_unwrap::check( cx, info, diff --git a/clippy_lints_methods/src/chars_next_cmp.rs b/clippy_lints_methods/src/chars_next_cmp.rs index 2438843bf3ab..31cc8753b96e 100644 --- a/clippy_lints_methods/src/chars_next_cmp.rs +++ b/clippy_lints_methods/src/chars_next_cmp.rs @@ -4,6 +4,6 @@ use rustc_lint::LateContext; use super::CHARS_NEXT_CMP; /// Checks for the `CHARS_NEXT_CMP` lint. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool { - crate::methods::chars_cmp::check(cx, info, &[sym::chars, sym::next], CHARS_NEXT_CMP, "starts_with") +pub(super) fn check(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { + crate::chars_cmp::check(cx, info, &[sym::chars, sym::next], CHARS_NEXT_CMP, "starts_with") } diff --git a/clippy_lints_methods/src/chars_next_cmp_with_unwrap.rs b/clippy_lints_methods/src/chars_next_cmp_with_unwrap.rs index 9b3609f19d72..b0f10d8e7249 100644 --- a/clippy_lints_methods/src/chars_next_cmp_with_unwrap.rs +++ b/clippy_lints_methods/src/chars_next_cmp_with_unwrap.rs @@ -4,8 +4,8 @@ use rustc_lint::LateContext; use super::CHARS_NEXT_CMP; /// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool { - crate::methods::chars_cmp_with_unwrap::check( +pub(super) fn check(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { + crate::chars_cmp_with_unwrap::check( cx, info, &[sym::chars, sym::next, sym::unwrap], diff --git a/clippy_lints_methods/src/declared_lints.rs b/clippy_lints_methods/src/declared_lints.rs new file mode 100644 index 000000000000..269692db095b --- /dev/null +++ b/clippy_lints_methods/src/declared_lints.rs @@ -0,0 +1,155 @@ +// This file was generated by `cargo dev update_lints`. +// Use that command to update this file and do not edit by hand. +// Manual edits will be overwritten. + +pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ + crate::BIND_INSTEAD_OF_MAP_INFO, + crate::BYTES_COUNT_TO_LEN_INFO, + crate::BYTES_NTH_INFO, + crate::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO, + crate::CHARS_LAST_CMP_INFO, + crate::CHARS_NEXT_CMP_INFO, + crate::CLEAR_WITH_DRAIN_INFO, + crate::CLONE_ON_COPY_INFO, + crate::CLONE_ON_REF_PTR_INFO, + crate::CLONED_INSTEAD_OF_COPIED_INFO, + crate::COLLAPSIBLE_STR_REPLACE_INFO, + crate::CONST_IS_EMPTY_INFO, + crate::DOUBLE_ENDED_ITERATOR_LAST_INFO, + crate::DRAIN_COLLECT_INFO, + crate::ERR_EXPECT_INFO, + crate::EXPECT_FUN_CALL_INFO, + crate::EXPECT_USED_INFO, + crate::EXTEND_WITH_DRAIN_INFO, + crate::FILETYPE_IS_FILE_INFO, + crate::FILTER_MAP_BOOL_THEN_INFO, + crate::FILTER_MAP_IDENTITY_INFO, + crate::FILTER_MAP_NEXT_INFO, + crate::FILTER_NEXT_INFO, + crate::FLAT_MAP_IDENTITY_INFO, + crate::FLAT_MAP_OPTION_INFO, + crate::FORMAT_COLLECT_INFO, + crate::FROM_ITER_INSTEAD_OF_COLLECT_INFO, + crate::GET_FIRST_INFO, + crate::GET_LAST_WITH_LEN_INFO, + crate::GET_UNWRAP_INFO, + crate::IMPLICIT_CLONE_INFO, + crate::INEFFICIENT_TO_STRING_INFO, + crate::INSPECT_FOR_EACH_INFO, + crate::INTO_ITER_ON_REF_INFO, + crate::IO_OTHER_ERROR_INFO, + crate::IP_CONSTANT_INFO, + crate::IS_DIGIT_ASCII_RADIX_INFO, + crate::ITER_CLONED_COLLECT_INFO, + crate::ITER_COUNT_INFO, + crate::ITER_FILTER_IS_OK_INFO, + crate::ITER_FILTER_IS_SOME_INFO, + crate::ITER_KV_MAP_INFO, + crate::ITER_NEXT_SLICE_INFO, + crate::ITER_NTH_INFO, + crate::ITER_NTH_ZERO_INFO, + crate::ITER_ON_EMPTY_COLLECTIONS_INFO, + crate::ITER_ON_SINGLE_ITEMS_INFO, + crate::ITER_OUT_OF_BOUNDS_INFO, + crate::ITER_OVEREAGER_CLONED_INFO, + crate::ITER_SKIP_NEXT_INFO, + crate::ITER_SKIP_ZERO_INFO, + crate::ITER_WITH_DRAIN_INFO, + crate::ITERATOR_STEP_BY_ZERO_INFO, + crate::JOIN_ABSOLUTE_PATHS_INFO, + crate::MANUAL_C_STR_LITERALS_INFO, + crate::MANUAL_CONTAINS_INFO, + crate::MANUAL_FILTER_MAP_INFO, + crate::MANUAL_FIND_MAP_INFO, + crate::MANUAL_INSPECT_INFO, + crate::MANUAL_IS_VARIANT_AND_INFO, + crate::MANUAL_NEXT_BACK_INFO, + crate::MANUAL_OK_OR_INFO, + crate::MANUAL_REPEAT_N_INFO, + crate::MANUAL_SATURATING_ARITHMETIC_INFO, + crate::MANUAL_SPLIT_ONCE_INFO, + crate::MANUAL_STR_REPEAT_INFO, + crate::MANUAL_TRY_FOLD_INFO, + crate::MAP_ALL_ANY_IDENTITY_INFO, + crate::MAP_CLONE_INFO, + crate::MAP_COLLECT_RESULT_UNIT_INFO, + crate::MAP_ERR_IGNORE_INFO, + crate::MAP_FLATTEN_INFO, + crate::MAP_IDENTITY_INFO, + crate::MAP_UNWRAP_OR_INFO, + crate::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO, + crate::MUT_MUTEX_LOCK_INFO, + crate::NAIVE_BYTECOUNT_INFO, + crate::NEEDLESS_AS_BYTES_INFO, + crate::NEEDLESS_CHARACTER_ITERATION_INFO, + crate::NEEDLESS_COLLECT_INFO, + crate::NEEDLESS_OPTION_AS_DEREF_INFO, + crate::NEEDLESS_OPTION_TAKE_INFO, + crate::NEEDLESS_SPLITN_INFO, + crate::NEW_RET_NO_SELF_INFO, + crate::NO_EFFECT_REPLACE_INFO, + crate::NONSENSICAL_OPEN_OPTIONS_INFO, + crate::OBFUSCATED_IF_ELSE_INFO, + crate::OK_EXPECT_INFO, + crate::OPTION_AS_REF_CLONED_INFO, + crate::OPTION_AS_REF_DEREF_INFO, + crate::OPTION_FILTER_MAP_INFO, + crate::OPTION_MAP_OR_NONE_INFO, + crate::OR_FUN_CALL_INFO, + crate::OR_THEN_UNWRAP_INFO, + crate::PATH_BUF_PUSH_OVERWRITE_INFO, + crate::PATH_ENDS_WITH_EXT_INFO, + crate::RANGE_ZIP_WITH_LEN_INFO, + crate::READ_LINE_WITHOUT_TRIM_INFO, + crate::READONLY_WRITE_LOCK_INFO, + crate::REDUNDANT_AS_STR_INFO, + crate::REDUNDANT_ITER_CLONED_INFO, + crate::REPEAT_ONCE_INFO, + crate::RESULT_FILTER_MAP_INFO, + crate::RESULT_MAP_OR_INTO_OPTION_INFO, + crate::RETURN_AND_THEN_INFO, + crate::SEARCH_IS_SOME_INFO, + crate::SEEK_FROM_CURRENT_INFO, + crate::SEEK_TO_START_INSTEAD_OF_REWIND_INFO, + crate::SHOULD_IMPLEMENT_TRAIT_INFO, + crate::SINGLE_CHAR_ADD_STR_INFO, + crate::SKIP_WHILE_NEXT_INFO, + crate::SLICED_STRING_AS_BYTES_INFO, + crate::STABLE_SORT_PRIMITIVE_INFO, + crate::STR_SPLIT_AT_NEWLINE_INFO, + crate::STRING_EXTEND_CHARS_INFO, + crate::STRING_LIT_CHARS_ANY_INFO, + crate::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, + crate::SUSPICIOUS_MAP_INFO, + crate::SUSPICIOUS_OPEN_OPTIONS_INFO, + crate::SUSPICIOUS_SPLITN_INFO, + crate::SUSPICIOUS_TO_OWNED_INFO, + crate::SWAP_WITH_TEMPORARY_INFO, + crate::TYPE_ID_ON_BOX_INFO, + crate::UNBUFFERED_BYTES_INFO, + crate::UNINIT_ASSUMED_INIT_INFO, + crate::UNIT_HASH_INFO, + crate::UNNECESSARY_FALLIBLE_CONVERSIONS_INFO, + crate::UNNECESSARY_FILTER_MAP_INFO, + crate::UNNECESSARY_FIND_MAP_INFO, + crate::UNNECESSARY_FIRST_THEN_CHECK_INFO, + crate::UNNECESSARY_FOLD_INFO, + crate::UNNECESSARY_GET_THEN_CHECK_INFO, + crate::UNNECESSARY_JOIN_INFO, + crate::UNNECESSARY_LAZY_EVALUATIONS_INFO, + crate::UNNECESSARY_LITERAL_UNWRAP_INFO, + crate::UNNECESSARY_MAP_OR_INFO, + crate::UNNECESSARY_MIN_OR_MAX_INFO, + crate::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO, + crate::UNNECESSARY_SORT_BY_INFO, + crate::UNNECESSARY_TO_OWNED_INFO, + crate::UNWRAP_OR_DEFAULT_INFO, + crate::UNWRAP_USED_INFO, + crate::USELESS_ASREF_INFO, + crate::USELESS_NONZERO_NEW_UNCHECKED_INFO, + crate::VEC_RESIZE_TO_ZERO_INFO, + crate::VERBOSE_FILE_READS_INFO, + crate::WAKER_CLONE_WAKE_INFO, + crate::WRONG_SELF_CONVENTION_INFO, + crate::ZST_OFFSET_INFO, +]; diff --git a/clippy_lints_methods/src/drain_collect.rs b/clippy_lints_methods/src/drain_collect.rs index cbf713a3b17c..d6e012f8b5ad 100644 --- a/clippy_lints_methods/src/drain_collect.rs +++ b/clippy_lints_methods/src/drain_collect.rs @@ -1,4 +1,4 @@ -use crate::methods::DRAIN_COLLECT; +use crate::DRAIN_COLLECT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_lang_item; diff --git a/clippy_lints_methods/src/iter_cloned_collect.rs b/clippy_lints_methods/src/iter_cloned_collect.rs index b4ab313fe98d..9a4bd706e7a4 100644 --- a/clippy_lints_methods/src/iter_cloned_collect.rs +++ b/clippy_lints_methods/src/iter_cloned_collect.rs @@ -1,4 +1,4 @@ -use crate::methods::utils::derefs_to_slice; +use crate::utils::derefs_to_slice; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::{get_iterator_item_ty, is_type_diagnostic_item}; use rustc_errors::Applicability; diff --git a/clippy_lints_methods/src/lib.rs b/clippy_lints_methods/src/lib.rs index 4190a71d672c..d93e0d5a5781 100644 --- a/clippy_lints_methods/src/lib.rs +++ b/clippy_lints_methods/src/lib.rs @@ -1,3 +1,48 @@ +#![feature( + if_let_guard, + macro_metavar_expr_concat, + never_type, + rustc_private, + unwrap_infallible +)] +#![allow( + clippy::missing_docs_in_private_items, + clippy::must_use_candidate, + rustc::diagnostic_outside_of_impl, + rustc::untranslatable_diagnostic, + clippy::literal_string_with_formatting_args +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + rust_2018_idioms, + unused_lifetimes, + unused_qualifications, + rustc::internal +)] + +extern crate rustc_abi; +extern crate rustc_ast; +extern crate rustc_data_structures; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_hir_typeck; +extern crate rustc_infer; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_trait_selection; +extern crate smallvec; + +#[macro_use] +extern crate declare_clippy_lint; + +pub mod declared_lints; + +// begin lints modules, do not remove this comment, it's used in `update_lints` +// end lints modules, do not remove this comment, it's used in `update_lints` + mod bind_instead_of_map; mod bytecount; mod bytes_count_to_len; @@ -4590,7 +4635,7 @@ declare_clippy_lint! { } #[expect(clippy::struct_excessive_bools)] -pub struct Methods { +struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, allow_expect_in_tests: bool, @@ -4602,7 +4647,7 @@ pub struct Methods { } impl Methods { - pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { + fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { let mut allowed_dotfiles: FxHashSet<_> = conf.allowed_dotfiles.iter().map(|s| &**s).collect(); allowed_dotfiles.extend(DEFAULT_ALLOWED_DOTFILES); @@ -4774,7 +4819,7 @@ impl_lint_pass!(Methods => [ /// Extracts a method call name, args, and `Span` of the method name. /// This ensures that neither the receiver nor any of the arguments /// come from expansion. -pub fn method_call<'tcx>(recv: &'tcx Expr<'tcx>) -> Option<(Symbol, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> { +fn method_call<'tcx>(recv: &'tcx Expr<'tcx>) -> Option<(Symbol, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> { if let ExprKind::MethodCall(path, receiver, args, call_span) = recv.kind && !args.iter().any(|e| e.span.from_expansion()) && !receiver.span.from_expansion() @@ -5830,3 +5875,7 @@ impl OutType { fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { expected.constness == actual.constness && expected.safety == actual.safety && expected.asyncness == actual.asyncness } + +pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Conf, format_args: FormatArgsStorage) { + store.register_late_pass(move |_| Box::new(Methods::new(conf, format_args.clone()))); +} diff --git a/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs b/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs index a2a522a60687..22400b02c293 100644 --- a/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs +++ b/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs @@ -1,4 +1,4 @@ -use crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES; +use crate::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; diff --git a/clippy_lints_methods/src/single_char_add_str.rs b/clippy_lints_methods/src/single_char_add_str.rs index ccdf5529d537..058d508b0acd 100644 --- a/clippy_lints_methods/src/single_char_add_str.rs +++ b/clippy_lints_methods/src/single_char_add_str.rs @@ -1,4 +1,4 @@ -use crate::methods::{single_char_insert_string, single_char_push_string}; +use crate::{single_char_insert_string, single_char_push_string}; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; diff --git a/clippy_lints_methods/src/type_id_on_box.rs b/clippy_lints_methods/src/type_id_on_box.rs index e67ba5c4d314..6c34f3d25a99 100644 --- a/clippy_lints_methods/src/type_id_on_box.rs +++ b/clippy_lints_methods/src/type_id_on_box.rs @@ -1,4 +1,4 @@ -use crate::methods::TYPE_ID_ON_BOX; +use crate::TYPE_ID_ON_BOX; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use rustc_errors::Applicability; diff --git a/clippy_lints_methods/src/wrong_self_convention.rs b/clippy_lints_methods/src/wrong_self_convention.rs index ad9b3c364542..881402d2e401 100644 --- a/clippy_lints_methods/src/wrong_self_convention.rs +++ b/clippy_lints_methods/src/wrong_self_convention.rs @@ -1,4 +1,4 @@ -use crate::methods::SelfKind; +use crate::SelfKind; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_copy; use rustc_lint::LateContext; diff --git a/src/driver.rs b/src/driver.rs index c4076cbaa77b..37475139d282 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -159,10 +159,12 @@ impl rustc_driver::Callbacks for ClippyCallbacks { let mut list_builder = LintListBuilder::default(); list_builder.insert(clippy_lints::declared_lints::LINTS); + list_builder.insert(clippy_lints_methods::declared_lints::LINTS); list_builder.register(lint_store); let conf = clippy_config::Conf::read(sess, &conf_path); - clippy_lints::register_lint_passes(lint_store, conf); + let format_args = clippy_lints::register_lint_passes(lint_store, conf); + clippy_lints_methods::register_lint_passes(lint_store, conf, format_args); #[cfg(feature = "internal")] clippy_lints_internal::register_lints(lint_store); diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 389616801fca..071d187b8de9 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -38,6 +38,7 @@ fn dogfood() { "clippy_dev", "clippy_lints_internal", "clippy_lints", + "clippy_lints_methods", "clippy_utils", "clippy_config", "declare_clippy_lint", From 87b553a0f711aec29a818a02411e74794fd35439 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Jul 2025 13:02:31 -0400 Subject: [PATCH 10/10] Move `methods` lint declarations into sub-modules --- .../src/bind_instead_of_map.rs | 34 +- clippy_lints_methods/src/bytecount.rs | 31 +- .../src/bytes_count_to_len.rs | 25 +- clippy_lints_methods/src/bytes_nth.rs | 23 +- ...se_sensitive_file_extension_comparisons.rs | 28 +- clippy_lints_methods/src/chars_cmp.rs | 138 +- .../src/chars_cmp_with_unwrap.rs | 42 - clippy_lints_methods/src/chars_last_cmp.rs | 14 - .../src/chars_last_cmp_with_unwrap.rs | 26 - clippy_lints_methods/src/chars_next_cmp.rs | 9 - .../src/chars_next_cmp_with_unwrap.rs | 15 - clippy_lints_methods/src/clear_with_drain.rs | 25 +- clippy_lints_methods/src/clone_on_copy.rs | 18 +- clippy_lints_methods/src/clone_on_ref_ptr.rs | 31 +- .../src/cloned_instead_of_copied.rs | 23 +- .../src/collapsible_str_replace.rs | 27 +- clippy_lints_methods/src/declared_lints.rs | 294 +- .../src/double_ended_iterator_last.rs | 26 +- clippy_lints_methods/src/drain_collect.rs | 37 +- clippy_lints_methods/src/err_expect.rs | 24 +- clippy_lints_methods/src/expect_fun_call.rs | 38 +- clippy_lints_methods/src/extend_with_drain.rs | 28 +- clippy_lints_methods/src/filetype_is_file.rs | 42 +- clippy_lints_methods/src/filter_map.rs | 98 +- .../src/filter_map_bool_then.rs | 32 +- .../src/filter_map_identity.rs | 23 +- clippy_lints_methods/src/filter_map_next.rs | 23 +- clippy_lints_methods/src/filter_next.rs | 25 +- clippy_lints_methods/src/flat_map_identity.rs | 23 +- clippy_lints_methods/src/flat_map_option.rs | 29 +- clippy_lints_methods/src/format_collect.rs | 36 +- .../src/from_iter_instead_of_collect.rs | 41 +- clippy_lints_methods/src/get_first.rs | 26 +- clippy_lints_methods/src/get_last_with_len.rs | 33 +- clippy_lints_methods/src/get_unwrap.rs | 37 +- clippy_lints_methods/src/implicit_clone.rs | 26 +- .../src/inefficient_to_string.rs | 24 +- clippy_lints_methods/src/inspect_for_each.rs | 30 +- clippy_lints_methods/src/into_iter_on_ref.rs | 27 +- clippy_lints_methods/src/io_other_error.rs | 24 +- clippy_lints_methods/src/ip_constant.rs | 36 +- .../src/is_digit_ascii_radix.rs | 28 +- clippy_lints_methods/src/is_empty.rs | 26 +- .../src/iter_cloned_collect.rs | 24 +- clippy_lints_methods/src/iter_count.rs | 29 +- clippy_lints_methods/src/iter_filter.rs | 57 +- clippy_lints_methods/src/iter_kv_map.rs | 32 +- clippy_lints_methods/src/iter_next_slice.rs | 27 +- clippy_lints_methods/src/iter_nth.rs | 26 +- clippy_lints_methods/src/iter_nth_zero.rs | 30 +- .../iter_on_single_or_empty_collections.rs | 63 +- .../src/iter_out_of_bounds.rs | 24 +- .../src/iter_overeager_cloned.rs | 56 +- clippy_lints_methods/src/iter_skip_next.rs | 25 +- clippy_lints_methods/src/iter_skip_zero.rs | 21 +- clippy_lints_methods/src/iter_with_drain.rs | 25 +- .../src/iterator_step_by_zero.rs | 20 +- .../src/join_absolute_paths.rs | 40 +- clippy_lints_methods/src/lib.rs | 4722 +---------------- .../src/manual_c_str_literals.rs | 34 +- clippy_lints_methods/src/manual_contains.rs | 25 +- clippy_lints_methods/src/manual_inspect.rs | 21 +- .../src/manual_is_variant_and.rs | 27 +- clippy_lints_methods/src/manual_next_back.rs | 25 +- clippy_lints_methods/src/manual_ok_or.rs | 26 +- clippy_lints_methods/src/manual_repeat_n.rs | 23 +- .../src/manual_saturating_arithmetic.rs | 31 +- clippy_lints_methods/src/manual_str_repeat.rs | 22 +- clippy_lints_methods/src/manual_try_fold.rs | 29 +- .../src/map_all_any_identity.rs | 25 +- clippy_lints_methods/src/map_clone.rs | 29 +- .../src/map_collect_result_unit.rs | 21 +- clippy_lints_methods/src/map_err_ignore.rs | 101 +- clippy_lints_methods/src/map_flatten.rs | 30 +- clippy_lints_methods/src/map_identity.rs | 23 +- clippy_lints_methods/src/map_unwrap_or.rs | 37 +- .../map_with_unused_argument_over_ranges.rs | 35 +- clippy_lints_methods/src/mut_mutex_lock.rs | 36 +- clippy_lints_methods/src/needless_as_bytes.rs | 25 +- .../src/needless_character_iteration.rs | 29 +- clippy_lints_methods/src/needless_collect.rs | 29 +- .../src/needless_option_as_deref.rs | 25 +- .../src/needless_option_take.rs | 24 +- clippy_lints_methods/src/no_effect_replace.rs | 18 +- .../src/obfuscated_if_else.rs | 30 +- clippy_lints_methods/src/ok_expect.rs | 25 +- clippy_lints_methods/src/open_options.rs | 59 +- .../src/option_as_ref_cloned.rs | 26 +- .../src/option_as_ref_deref.rs | 26 +- .../src/option_map_or_none.rs | 53 +- .../src/option_map_unwrap_or.rs | 2 +- clippy_lints_methods/src/or_fun_call.rs | 73 +- clippy_lints_methods/src/or_then_unwrap.rs | 36 +- .../src/path_buf_push_overwrite.rs | 32 +- .../src/path_ends_with_ext.rs | 40 +- .../src/range_zip_with_len.rs | 25 +- .../src/read_line_without_trim.rs | 31 +- .../src/readonly_write_lock.rs | 32 +- clippy_lints_methods/src/redundant_as_str.rs | 27 +- clippy_lints_methods/src/repeat_once.rs | 32 +- .../src/result_map_or_else_none.rs | 2 +- clippy_lints_methods/src/return_and_then.rs | 39 +- clippy_lints_methods/src/search_is_some.rs | 31 +- clippy_lints_methods/src/seek_from_current.rs | 43 +- .../src/seek_to_start_instead_of_rewind.rs | 31 +- .../src/single_char_add_str.rs | 139 +- .../src/single_char_insert_string.rs | 67 - .../src/single_char_push_string.rs | 65 - clippy_lints_methods/src/skip_while_next.rs | 25 +- .../src/sliced_string_as_bytes.rs | 28 +- .../src/stable_sort_primitive.rs | 38 +- clippy_lints_methods/src/str_split.rs | 29 +- clippy_lints_methods/src/str_splitn.rs | 59 +- .../src/string_extend_chars.rs | 30 +- .../src/string_lit_chars_any.rs | 28 +- .../src/suspicious_command_arg_space.rs | 26 +- clippy_lints_methods/src/suspicious_map.rs | 20 +- clippy_lints_methods/src/suspicious_splitn.rs | 31 +- .../src/suspicious_to_owned.rs | 48 +- .../src/swap_with_temporary.rs | 47 +- clippy_lints_methods/src/type_id_on_box.rs | 41 +- clippy_lints_methods/src/unbuffered_bytes.rs | 28 +- .../src/uninit_assumed_init.rs | 35 +- clippy_lints_methods/src/unit_hash.rs | 39 +- .../src/unnecessary_fallible_conversions.rs | 27 +- .../src/unnecessary_filter_map.rs | 60 +- .../src/unnecessary_first_then_check.rs | 28 +- clippy_lints_methods/src/unnecessary_fold.rs | 24 +- .../src/unnecessary_get_then_check.rs | 29 +- .../src/unnecessary_iter_cloned.rs | 143 - clippy_lints_methods/src/unnecessary_join.rs | 32 +- .../src/unnecessary_lazy_eval.rs | 39 +- .../src/unnecessary_literal_unwrap.rs | 26 +- .../src/unnecessary_map_or.rs | 35 +- .../src/unnecessary_min_or_max.rs | 30 +- .../src/unnecessary_result_map_or_else.rs | 27 +- .../src/unnecessary_sort_by.rs | 34 +- .../src/unnecessary_to_owned.rs | 174 +- .../src/unwrap_expect_used.rs | 87 +- clippy_lints_methods/src/useless_asref.rs | 26 +- .../src/useless_nonzero_new_unchecked.rs | 27 +- .../src/vec_resize_to_zero.rs | 22 +- .../src/verbose_file_reads.rs | 27 +- clippy_lints_methods/src/waker_clone_wake.rs | 22 +- .../src/wrong_self_convention.rs | 60 +- clippy_lints_methods/src/zst_offset.rs | 18 +- 146 files changed, 4906 insertions(+), 5225 deletions(-) delete mode 100644 clippy_lints_methods/src/chars_cmp_with_unwrap.rs delete mode 100644 clippy_lints_methods/src/chars_last_cmp.rs delete mode 100644 clippy_lints_methods/src/chars_last_cmp_with_unwrap.rs delete mode 100644 clippy_lints_methods/src/chars_next_cmp.rs delete mode 100644 clippy_lints_methods/src/chars_next_cmp_with_unwrap.rs delete mode 100644 clippy_lints_methods/src/single_char_insert_string.rs delete mode 100644 clippy_lints_methods/src/single_char_push_string.rs delete mode 100644 clippy_lints_methods/src/unnecessary_iter_cloned.rs diff --git a/clippy_lints_methods/src/bind_instead_of_map.rs b/clippy_lints_methods/src/bind_instead_of_map.rs index f8520c23ea50..21b1d66932d4 100644 --- a/clippy_lints_methods/src/bind_instead_of_map.rs +++ b/clippy_lints_methods/src/bind_instead_of_map.rs @@ -1,4 +1,4 @@ -use super::{BIND_INSTEAD_OF_MAP, contains_return}; +use super::contains_return; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::peel_blocks; use clippy_utils::source::{snippet, snippet_with_context}; @@ -10,6 +10,38 @@ use rustc_hir::{LangItem, QPath}; use rustc_lint::LateContext; use rustc_span::Span; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` + /// or `_.or_else(|x| Err(y))`. + /// + /// ### Why is this bad? + /// This can be written more concisely as `_.map(|x| y)` or `_.map_err(|x| y)`. + /// + /// ### Example + /// ```no_run + /// # fn opt() -> Option<&'static str> { Some("42") } + /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } + /// let _ = opt().and_then(|s| Some(s.len())); + /// let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) }); + /// let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) }); + /// ``` + /// + /// The correct use would be: + /// + /// ```no_run + /// # fn opt() -> Option<&'static str> { Some("42") } + /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } + /// let _ = opt().map(|s| s.len()); + /// let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 }); + /// let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 }); + /// ``` + #[clippy::version = "1.45.0"] + pub BIND_INSTEAD_OF_MAP, + complexity, + "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`" +} + pub(super) fn check_and_then_some( cx: &LateContext<'_>, expr: &hir::Expr<'_>, diff --git a/clippy_lints_methods/src/bytecount.rs b/clippy_lints_methods/src/bytecount.rs index 0498f317442a..7b5b46ba6651 100644 --- a/clippy_lints_methods/src/bytecount.rs +++ b/clippy_lints_methods/src/bytecount.rs @@ -9,7 +9,36 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self, UintTy}; use rustc_span::sym; -use super::NAIVE_BYTECOUNT; +declare_clippy_lint! { + /// ### What it does + /// Checks for naive byte counts + /// + /// ### Why is this bad? + /// The [`bytecount`](https://crates.io/crates/bytecount) + /// crate has methods to count your bytes faster, especially for large slices. + /// + /// ### Known problems + /// If you have predominantly small slices, the + /// `bytecount::count(..)` method may actually be slower. However, if you can + /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be + /// faster in those cases. + /// + /// ### Example + /// ```no_run + /// # let vec = vec![1_u8]; + /// let count = vec.iter().filter(|x| **x == 0u8).count(); + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// # let vec = vec![1_u8]; + /// let count = bytecount::count(&vec, 0u8); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub NAIVE_BYTECOUNT, + pedantic, + "use of naive `.filter(|&x| x == y).count()` to count byte values" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/bytes_count_to_len.rs b/clippy_lints_methods/src/bytes_count_to_len.rs index a9f6a41c2357..2b01b4abeefe 100644 --- a/clippy_lints_methods/src/bytes_count_to_len.rs +++ b/clippy_lints_methods/src/bytes_count_to_len.rs @@ -5,7 +5,30 @@ use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use super::BYTES_COUNT_TO_LEN; +declare_clippy_lint! { + /// ### What it does + /// It checks for `str::bytes().count()` and suggests replacing it with + /// `str::len()`. + /// + /// ### Why is this bad? + /// `str::bytes().count()` is longer and may not be as performant as using + /// `str::len()`. + /// + /// ### Example + /// ```no_run + /// "hello".bytes().count(); + /// String::from("hello").bytes().count(); + /// ``` + /// Use instead: + /// ```no_run + /// "hello".len(); + /// String::from("hello").len(); + /// ``` + #[clippy::version = "1.62.0"] + pub BYTES_COUNT_TO_LEN, + complexity, + "Using `bytes().count()` when `len()` performs the same functionality" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/bytes_nth.rs b/clippy_lints_methods/src/bytes_nth.rs index f0df6e6269b4..10fa38004f6d 100644 --- a/clippy_lints_methods/src/bytes_nth.rs +++ b/clippy_lints_methods/src/bytes_nth.rs @@ -8,7 +8,28 @@ use rustc_lint::LateContext; use crate::method_call; -use super::BYTES_NTH; +declare_clippy_lint! { + /// ### What it does + /// Checks for the use of `.bytes().nth()`. + /// + /// ### Why is this bad? + /// `.as_bytes().get()` is more efficient and more + /// readable. + /// + /// ### Example + /// ```no_run + /// "Hello".bytes().nth(3); + /// ``` + /// + /// Use instead: + /// ```no_run + /// "Hello".as_bytes().get(3); + /// ``` + #[clippy::version = "1.52.0"] + pub BYTES_NTH, + style, + "replace `.bytes().nth()` with `.as_bytes().get()`" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, n_arg: &'tcx Expr<'tcx>) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); diff --git a/clippy_lints_methods/src/case_sensitive_file_extension_comparisons.rs b/clippy_lints_methods/src/case_sensitive_file_extension_comparisons.rs index 292fa08b5984..01100a119c27 100644 --- a/clippy_lints_methods/src/case_sensitive_file_extension_comparisons.rs +++ b/clippy_lints_methods/src/case_sensitive_file_extension_comparisons.rs @@ -10,7 +10,33 @@ use rustc_lint::LateContext; use rustc_span::Span; use rustc_span::source_map::Spanned; -use super::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `ends_with` with possible file extensions + /// and suggests to use a case-insensitive approach instead. + /// + /// ### Why is this bad? + /// `ends_with` is case-sensitive and may not detect files with a valid extension. + /// + /// ### Example + /// ```no_run + /// fn is_rust_file(filename: &str) -> bool { + /// filename.ends_with(".rs") + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn is_rust_file(filename: &str) -> bool { + /// let filename = std::path::Path::new(filename); + /// filename.extension() + /// .map_or(false, |ext| ext.eq_ignore_ascii_case("rs")) + /// } + /// ``` + #[clippy::version = "1.51.0"] + pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + pedantic, + "Checks for calls to ends_with with case-sensitive file extensions" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/chars_cmp.rs b/clippy_lints_methods/src/chars_cmp.rs index 7f2bfb6bd5d3..456853ae5576 100644 --- a/clippy_lints_methods/src/chars_cmp.rs +++ b/clippy_lints_methods/src/chars_cmp.rs @@ -1,14 +1,112 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{method_chain_args, path_def_id}; +use clippy_utils::{method_chain_args, path_def_id, sym}; use rustc_errors::Applicability; -use rustc_hir as hir; use rustc_lint::{LateContext, Lint}; use rustc_middle::ty; use rustc_span::Symbol; +use {rustc_ast as ast, rustc_hir as hir}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.chars().next()` on a `str` to check + /// if it starts with a given char. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.starts_with(_)`. + /// + /// ### Example + /// ```no_run + /// let name = "foo"; + /// if name.chars().next() == Some('_') {}; + /// ``` + /// + /// Use instead: + /// ```no_run + /// let name = "foo"; + /// if name.starts_with('_') {}; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CHARS_NEXT_CMP, + style, + "using `.chars().next()` to check if a string starts with a char" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.chars().last()` or + /// `_.chars().next_back()` on a `str` to check if it ends with a given char. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.ends_with(_)`. + /// + /// ### Example + /// ```no_run + /// # let name = "_"; + /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-'); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let name = "_"; + /// name.ends_with('_') || name.ends_with('-'); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CHARS_LAST_CMP, + style, + "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char" +} + +/// Checks for the `CHARS_NEXT_CMP` lint. +pub(super) fn check_next(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { + check(cx, info, &[sym::chars, sym::next], CHARS_NEXT_CMP, "starts_with") +} + +/// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`. +pub(super) fn check_next_unwrap(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { + check_unwrap( + cx, + info, + &[sym::chars, sym::next, sym::unwrap], + CHARS_NEXT_CMP, + "starts_with", + ) +} + +/// Checks for the `CHARS_LAST_CMP` lint. +pub(super) fn check_last(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { + if check(cx, info, &[sym::chars, sym::last], CHARS_LAST_CMP, "ends_with") { + true + } else { + check(cx, info, &[sym::chars, sym::next_back], CHARS_LAST_CMP, "ends_with") + } +} + +/// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`. +pub(super) fn check_last_unwrap(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { + if check_unwrap( + cx, + info, + &[sym::chars, sym::last, sym::unwrap], + CHARS_LAST_CMP, + "ends_with", + ) { + true + } else { + check_unwrap( + cx, + info, + &[sym::chars, sym::next_back, sym::unwrap], + CHARS_LAST_CMP, + "ends_with", + ) + } +} /// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. -pub(super) fn check( +fn check( cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>, chain_methods: &[Symbol], @@ -47,3 +145,37 @@ pub(super) fn check( false } + +/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`. +fn check_unwrap( + cx: &LateContext<'_>, + info: &crate::BinaryExprInfo<'_>, + chain_methods: &[Symbol], + lint: &'static Lint, + suggest: &str, +) -> bool { + if let Some(args) = method_chain_args(info.chain, chain_methods) + && let hir::ExprKind::Lit(lit) = info.other.kind + && let ast::LitKind::Char(c) = lit.node + { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + lint, + info.expr.span, + format!("you should use the `{suggest}` method"), + "like this", + format!( + "{}{}.{suggest}('{}')", + if info.eq { "" } else { "!" }, + snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability), + c.escape_default() + ), + applicability, + ); + + true + } else { + false + } +} diff --git a/clippy_lints_methods/src/chars_cmp_with_unwrap.rs b/clippy_lints_methods/src/chars_cmp_with_unwrap.rs deleted file mode 100644 index 00db3eb02528..000000000000 --- a/clippy_lints_methods/src/chars_cmp_with_unwrap.rs +++ /dev/null @@ -1,42 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::method_chain_args; -use clippy_utils::source::snippet_with_applicability; -use rustc_ast::ast; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, Lint}; -use rustc_span::Symbol; - -/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`. -pub(super) fn check( - cx: &LateContext<'_>, - info: &crate::BinaryExprInfo<'_>, - chain_methods: &[Symbol], - lint: &'static Lint, - suggest: &str, -) -> bool { - if let Some(args) = method_chain_args(info.chain, chain_methods) - && let hir::ExprKind::Lit(lit) = info.other.kind - && let ast::LitKind::Char(c) = lit.node - { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - lint, - info.expr.span, - format!("you should use the `{suggest}` method"), - "like this", - format!( - "{}{}.{suggest}('{}')", - if info.eq { "" } else { "!" }, - snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability), - c.escape_default() - ), - applicability, - ); - - true - } else { - false - } -} diff --git a/clippy_lints_methods/src/chars_last_cmp.rs b/clippy_lints_methods/src/chars_last_cmp.rs deleted file mode 100644 index 624637efbd62..000000000000 --- a/clippy_lints_methods/src/chars_last_cmp.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::chars_cmp; -use clippy_utils::sym; -use rustc_lint::LateContext; - -use super::CHARS_LAST_CMP; - -/// Checks for the `CHARS_LAST_CMP` lint. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { - if chars_cmp::check(cx, info, &[sym::chars, sym::last], CHARS_LAST_CMP, "ends_with") { - true - } else { - chars_cmp::check(cx, info, &[sym::chars, sym::next_back], CHARS_LAST_CMP, "ends_with") - } -} diff --git a/clippy_lints_methods/src/chars_last_cmp_with_unwrap.rs b/clippy_lints_methods/src/chars_last_cmp_with_unwrap.rs deleted file mode 100644 index 679c2e666a25..000000000000 --- a/clippy_lints_methods/src/chars_last_cmp_with_unwrap.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::chars_cmp_with_unwrap; -use clippy_utils::sym; -use rustc_lint::LateContext; - -use super::CHARS_LAST_CMP; - -/// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { - if chars_cmp_with_unwrap::check( - cx, - info, - &[sym::chars, sym::last, sym::unwrap], - CHARS_LAST_CMP, - "ends_with", - ) { - true - } else { - chars_cmp_with_unwrap::check( - cx, - info, - &[sym::chars, sym::next_back, sym::unwrap], - CHARS_LAST_CMP, - "ends_with", - ) - } -} diff --git a/clippy_lints_methods/src/chars_next_cmp.rs b/clippy_lints_methods/src/chars_next_cmp.rs deleted file mode 100644 index 31cc8753b96e..000000000000 --- a/clippy_lints_methods/src/chars_next_cmp.rs +++ /dev/null @@ -1,9 +0,0 @@ -use clippy_utils::sym; -use rustc_lint::LateContext; - -use super::CHARS_NEXT_CMP; - -/// Checks for the `CHARS_NEXT_CMP` lint. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { - crate::chars_cmp::check(cx, info, &[sym::chars, sym::next], CHARS_NEXT_CMP, "starts_with") -} diff --git a/clippy_lints_methods/src/chars_next_cmp_with_unwrap.rs b/clippy_lints_methods/src/chars_next_cmp_with_unwrap.rs deleted file mode 100644 index b0f10d8e7249..000000000000 --- a/clippy_lints_methods/src/chars_next_cmp_with_unwrap.rs +++ /dev/null @@ -1,15 +0,0 @@ -use clippy_utils::sym; -use rustc_lint::LateContext; - -use super::CHARS_NEXT_CMP; - -/// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { - crate::chars_cmp_with_unwrap::check( - cx, - info, - &[sym::chars, sym::next, sym::unwrap], - CHARS_NEXT_CMP, - "starts_with", - ) -} diff --git a/clippy_lints_methods/src/clear_with_drain.rs b/clippy_lints_methods/src/clear_with_drain.rs index 6e24cabca8bb..9f4230022dee 100644 --- a/clippy_lints_methods/src/clear_with_drain.rs +++ b/clippy_lints_methods/src/clear_with_drain.rs @@ -7,7 +7,30 @@ use rustc_lint::LateContext; use rustc_span::Span; use rustc_span::symbol::sym; -use super::CLEAR_WITH_DRAIN; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.drain(..)` for the sole purpose of clearing a container. + /// + /// ### Why is this bad? + /// This creates an unnecessary iterator that is dropped immediately. + /// + /// Calling `.clear()` also makes the intent clearer. + /// + /// ### Example + /// ```no_run + /// let mut v = vec![1, 2, 3]; + /// v.drain(..); + /// ``` + /// Use instead: + /// ```no_run + /// let mut v = vec![1, 2, 3]; + /// v.clear(); + /// ``` + #[clippy::version = "1.70.0"] + pub CLEAR_WITH_DRAIN, + nursery, + "calling `drain` in order to `clear` a container" +} // Add `String` here when it is added to diagnostic items const ACCEPTABLE_TYPES_WITH_ARG: [rustc_span::Symbol; 2] = [sym::Vec, sym::VecDeque]; diff --git a/clippy_lints_methods/src/clone_on_copy.rs b/clippy_lints_methods/src/clone_on_copy.rs index 2ecf3eb89798..ae912c4bdbca 100644 --- a/clippy_lints_methods/src/clone_on_copy.rs +++ b/clippy_lints_methods/src/clone_on_copy.rs @@ -9,7 +9,23 @@ use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::{self}; use rustc_span::symbol::{Symbol, sym}; -use super::CLONE_ON_COPY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.clone()` on a `Copy` type. + /// + /// ### Why is this bad? + /// The only reason `Copy` types implement `Clone` is for + /// generics, not for using the `clone` method on a concrete type. + /// + /// ### Example + /// ```no_run + /// 42u64.clone(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CLONE_ON_COPY, + complexity, + "using `clone` on a `Copy` type" +} /// Checks for the `CLONE_ON_COPY` lint. #[allow(clippy::too_many_lines)] diff --git a/clippy_lints_methods/src/clone_on_ref_ptr.rs b/clippy_lints_methods/src/clone_on_ref_ptr.rs index 96e2de0dc1cb..c207ce193b4d 100644 --- a/clippy_lints_methods/src/clone_on_ref_ptr.rs +++ b/clippy_lints_methods/src/clone_on_ref_ptr.rs @@ -6,7 +6,36 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::symbol::{Symbol, sym}; -use super::CLONE_ON_REF_PTR; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.clone()` on a ref-counted pointer, + /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified + /// function syntax instead (e.g., `Rc::clone(foo)`). + /// + /// ### Why restrict this? + /// Calling `.clone()` on an `Rc`, `Arc`, or `Weak` + /// can obscure the fact that only the pointer is being cloned, not the underlying + /// data. + /// + /// ### Example + /// ```no_run + /// # use std::rc::Rc; + /// let x = Rc::new(1); + /// + /// x.clone(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::rc::Rc; + /// # let x = Rc::new(1); + /// Rc::clone(&x); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CLONE_ON_REF_PTR, + restriction, + "using `clone` on a ref-counted pointer" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints_methods/src/cloned_instead_of_copied.rs b/clippy_lints_methods/src/cloned_instead_of_copied.rs index f50fb627b89a..6a309b6dd966 100644 --- a/clippy_lints_methods/src/cloned_instead_of_copied.rs +++ b/clippy_lints_methods/src/cloned_instead_of_copied.rs @@ -8,7 +8,28 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Span, sym}; -use super::CLONED_INSTEAD_OF_COPIED; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `cloned()` on an `Iterator` or `Option` where + /// `copied()` could be used instead. + /// + /// ### Why is this bad? + /// `copied()` is better because it guarantees that the type being cloned + /// implements `Copy`. + /// + /// ### Example + /// ```no_run + /// [1, 2, 3].iter().cloned(); + /// ``` + /// Use instead: + /// ```no_run + /// [1, 2, 3].iter().copied(); + /// ``` + #[clippy::version = "1.53.0"] + pub CLONED_INSTEAD_OF_COPIED, + pedantic, + "used `cloned` where `copied` could be used instead" +} pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: Msrv) { let recv_ty = cx.typeck_results().expr_ty_adjusted(recv); diff --git a/clippy_lints_methods/src/collapsible_str_replace.rs b/clippy_lints_methods/src/collapsible_str_replace.rs index 6d0b944df55d..e057290ad8b0 100644 --- a/clippy_lints_methods/src/collapsible_str_replace.rs +++ b/clippy_lints_methods/src/collapsible_str_replace.rs @@ -1,3 +1,4 @@ +use super::method_call; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_expr_without_closures; @@ -8,7 +9,31 @@ use rustc_hir as hir; use rustc_lint::LateContext; use std::collections::VecDeque; -use super::{COLLAPSIBLE_STR_REPLACE, method_call}; +declare_clippy_lint! { + /// ### What it does + /// Checks for consecutive calls to `str::replace` (2 or more) + /// that can be collapsed into a single call. + /// + /// ### Why is this bad? + /// Consecutive `str::replace` calls scan the string multiple times + /// with repetitive code. + /// + /// ### Example + /// ```no_run + /// let hello = "hesuo worpd" + /// .replace('s', "l") + /// .replace("u", "l") + /// .replace('p', "l"); + /// ``` + /// Use instead: + /// ```no_run + /// let hello = "hesuo worpd".replace(['s', 'u', 'p'], "l"); + /// ``` + #[clippy::version = "1.65.0"] + pub COLLAPSIBLE_STR_REPLACE, + perf, + "collapse consecutive calls to str::replace (2 or more) into a single call" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/declared_lints.rs b/clippy_lints_methods/src/declared_lints.rs index 269692db095b..a16b547841db 100644 --- a/clippy_lints_methods/src/declared_lints.rs +++ b/clippy_lints_methods/src/declared_lints.rs @@ -3,153 +3,153 @@ // Manual edits will be overwritten. pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ - crate::BIND_INSTEAD_OF_MAP_INFO, - crate::BYTES_COUNT_TO_LEN_INFO, - crate::BYTES_NTH_INFO, - crate::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO, - crate::CHARS_LAST_CMP_INFO, - crate::CHARS_NEXT_CMP_INFO, - crate::CLEAR_WITH_DRAIN_INFO, - crate::CLONE_ON_COPY_INFO, - crate::CLONE_ON_REF_PTR_INFO, - crate::CLONED_INSTEAD_OF_COPIED_INFO, - crate::COLLAPSIBLE_STR_REPLACE_INFO, - crate::CONST_IS_EMPTY_INFO, - crate::DOUBLE_ENDED_ITERATOR_LAST_INFO, - crate::DRAIN_COLLECT_INFO, - crate::ERR_EXPECT_INFO, - crate::EXPECT_FUN_CALL_INFO, - crate::EXPECT_USED_INFO, - crate::EXTEND_WITH_DRAIN_INFO, - crate::FILETYPE_IS_FILE_INFO, - crate::FILTER_MAP_BOOL_THEN_INFO, - crate::FILTER_MAP_IDENTITY_INFO, - crate::FILTER_MAP_NEXT_INFO, - crate::FILTER_NEXT_INFO, - crate::FLAT_MAP_IDENTITY_INFO, - crate::FLAT_MAP_OPTION_INFO, - crate::FORMAT_COLLECT_INFO, - crate::FROM_ITER_INSTEAD_OF_COLLECT_INFO, - crate::GET_FIRST_INFO, - crate::GET_LAST_WITH_LEN_INFO, - crate::GET_UNWRAP_INFO, - crate::IMPLICIT_CLONE_INFO, - crate::INEFFICIENT_TO_STRING_INFO, - crate::INSPECT_FOR_EACH_INFO, - crate::INTO_ITER_ON_REF_INFO, - crate::IO_OTHER_ERROR_INFO, - crate::IP_CONSTANT_INFO, - crate::IS_DIGIT_ASCII_RADIX_INFO, - crate::ITER_CLONED_COLLECT_INFO, - crate::ITER_COUNT_INFO, - crate::ITER_FILTER_IS_OK_INFO, - crate::ITER_FILTER_IS_SOME_INFO, - crate::ITER_KV_MAP_INFO, - crate::ITER_NEXT_SLICE_INFO, - crate::ITER_NTH_INFO, - crate::ITER_NTH_ZERO_INFO, - crate::ITER_ON_EMPTY_COLLECTIONS_INFO, - crate::ITER_ON_SINGLE_ITEMS_INFO, - crate::ITER_OUT_OF_BOUNDS_INFO, - crate::ITER_OVEREAGER_CLONED_INFO, - crate::ITER_SKIP_NEXT_INFO, - crate::ITER_SKIP_ZERO_INFO, - crate::ITER_WITH_DRAIN_INFO, - crate::ITERATOR_STEP_BY_ZERO_INFO, - crate::JOIN_ABSOLUTE_PATHS_INFO, - crate::MANUAL_C_STR_LITERALS_INFO, - crate::MANUAL_CONTAINS_INFO, - crate::MANUAL_FILTER_MAP_INFO, - crate::MANUAL_FIND_MAP_INFO, - crate::MANUAL_INSPECT_INFO, - crate::MANUAL_IS_VARIANT_AND_INFO, - crate::MANUAL_NEXT_BACK_INFO, - crate::MANUAL_OK_OR_INFO, - crate::MANUAL_REPEAT_N_INFO, - crate::MANUAL_SATURATING_ARITHMETIC_INFO, - crate::MANUAL_SPLIT_ONCE_INFO, - crate::MANUAL_STR_REPEAT_INFO, - crate::MANUAL_TRY_FOLD_INFO, - crate::MAP_ALL_ANY_IDENTITY_INFO, - crate::MAP_CLONE_INFO, - crate::MAP_COLLECT_RESULT_UNIT_INFO, - crate::MAP_ERR_IGNORE_INFO, - crate::MAP_FLATTEN_INFO, - crate::MAP_IDENTITY_INFO, - crate::MAP_UNWRAP_OR_INFO, - crate::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO, - crate::MUT_MUTEX_LOCK_INFO, - crate::NAIVE_BYTECOUNT_INFO, - crate::NEEDLESS_AS_BYTES_INFO, - crate::NEEDLESS_CHARACTER_ITERATION_INFO, - crate::NEEDLESS_COLLECT_INFO, - crate::NEEDLESS_OPTION_AS_DEREF_INFO, - crate::NEEDLESS_OPTION_TAKE_INFO, - crate::NEEDLESS_SPLITN_INFO, crate::NEW_RET_NO_SELF_INFO, - crate::NO_EFFECT_REPLACE_INFO, - crate::NONSENSICAL_OPEN_OPTIONS_INFO, - crate::OBFUSCATED_IF_ELSE_INFO, - crate::OK_EXPECT_INFO, - crate::OPTION_AS_REF_CLONED_INFO, - crate::OPTION_AS_REF_DEREF_INFO, - crate::OPTION_FILTER_MAP_INFO, - crate::OPTION_MAP_OR_NONE_INFO, - crate::OR_FUN_CALL_INFO, - crate::OR_THEN_UNWRAP_INFO, - crate::PATH_BUF_PUSH_OVERWRITE_INFO, - crate::PATH_ENDS_WITH_EXT_INFO, - crate::RANGE_ZIP_WITH_LEN_INFO, - crate::READ_LINE_WITHOUT_TRIM_INFO, - crate::READONLY_WRITE_LOCK_INFO, - crate::REDUNDANT_AS_STR_INFO, - crate::REDUNDANT_ITER_CLONED_INFO, - crate::REPEAT_ONCE_INFO, - crate::RESULT_FILTER_MAP_INFO, - crate::RESULT_MAP_OR_INTO_OPTION_INFO, - crate::RETURN_AND_THEN_INFO, - crate::SEARCH_IS_SOME_INFO, - crate::SEEK_FROM_CURRENT_INFO, - crate::SEEK_TO_START_INSTEAD_OF_REWIND_INFO, crate::SHOULD_IMPLEMENT_TRAIT_INFO, - crate::SINGLE_CHAR_ADD_STR_INFO, - crate::SKIP_WHILE_NEXT_INFO, - crate::SLICED_STRING_AS_BYTES_INFO, - crate::STABLE_SORT_PRIMITIVE_INFO, - crate::STR_SPLIT_AT_NEWLINE_INFO, - crate::STRING_EXTEND_CHARS_INFO, - crate::STRING_LIT_CHARS_ANY_INFO, - crate::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, - crate::SUSPICIOUS_MAP_INFO, - crate::SUSPICIOUS_OPEN_OPTIONS_INFO, - crate::SUSPICIOUS_SPLITN_INFO, - crate::SUSPICIOUS_TO_OWNED_INFO, - crate::SWAP_WITH_TEMPORARY_INFO, - crate::TYPE_ID_ON_BOX_INFO, - crate::UNBUFFERED_BYTES_INFO, - crate::UNINIT_ASSUMED_INIT_INFO, - crate::UNIT_HASH_INFO, - crate::UNNECESSARY_FALLIBLE_CONVERSIONS_INFO, - crate::UNNECESSARY_FILTER_MAP_INFO, - crate::UNNECESSARY_FIND_MAP_INFO, - crate::UNNECESSARY_FIRST_THEN_CHECK_INFO, - crate::UNNECESSARY_FOLD_INFO, - crate::UNNECESSARY_GET_THEN_CHECK_INFO, - crate::UNNECESSARY_JOIN_INFO, - crate::UNNECESSARY_LAZY_EVALUATIONS_INFO, - crate::UNNECESSARY_LITERAL_UNWRAP_INFO, - crate::UNNECESSARY_MAP_OR_INFO, - crate::UNNECESSARY_MIN_OR_MAX_INFO, - crate::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO, - crate::UNNECESSARY_SORT_BY_INFO, - crate::UNNECESSARY_TO_OWNED_INFO, - crate::UNWRAP_OR_DEFAULT_INFO, - crate::UNWRAP_USED_INFO, - crate::USELESS_ASREF_INFO, - crate::USELESS_NONZERO_NEW_UNCHECKED_INFO, - crate::VEC_RESIZE_TO_ZERO_INFO, - crate::VERBOSE_FILE_READS_INFO, - crate::WAKER_CLONE_WAKE_INFO, - crate::WRONG_SELF_CONVENTION_INFO, - crate::ZST_OFFSET_INFO, + crate::bind_instead_of_map::BIND_INSTEAD_OF_MAP_INFO, + crate::bytecount::NAIVE_BYTECOUNT_INFO, + crate::bytes_count_to_len::BYTES_COUNT_TO_LEN_INFO, + crate::bytes_nth::BYTES_NTH_INFO, + crate::case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO, + crate::chars_cmp::CHARS_LAST_CMP_INFO, + crate::chars_cmp::CHARS_NEXT_CMP_INFO, + crate::clear_with_drain::CLEAR_WITH_DRAIN_INFO, + crate::clone_on_copy::CLONE_ON_COPY_INFO, + crate::clone_on_ref_ptr::CLONE_ON_REF_PTR_INFO, + crate::cloned_instead_of_copied::CLONED_INSTEAD_OF_COPIED_INFO, + crate::collapsible_str_replace::COLLAPSIBLE_STR_REPLACE_INFO, + crate::double_ended_iterator_last::DOUBLE_ENDED_ITERATOR_LAST_INFO, + crate::drain_collect::DRAIN_COLLECT_INFO, + crate::err_expect::ERR_EXPECT_INFO, + crate::expect_fun_call::EXPECT_FUN_CALL_INFO, + crate::extend_with_drain::EXTEND_WITH_DRAIN_INFO, + crate::filetype_is_file::FILETYPE_IS_FILE_INFO, + crate::filter_map::MANUAL_FILTER_MAP_INFO, + crate::filter_map::MANUAL_FIND_MAP_INFO, + crate::filter_map::OPTION_FILTER_MAP_INFO, + crate::filter_map::RESULT_FILTER_MAP_INFO, + crate::filter_map_bool_then::FILTER_MAP_BOOL_THEN_INFO, + crate::filter_map_identity::FILTER_MAP_IDENTITY_INFO, + crate::filter_map_next::FILTER_MAP_NEXT_INFO, + crate::filter_next::FILTER_NEXT_INFO, + crate::flat_map_identity::FLAT_MAP_IDENTITY_INFO, + crate::flat_map_option::FLAT_MAP_OPTION_INFO, + crate::format_collect::FORMAT_COLLECT_INFO, + crate::from_iter_instead_of_collect::FROM_ITER_INSTEAD_OF_COLLECT_INFO, + crate::get_first::GET_FIRST_INFO, + crate::get_last_with_len::GET_LAST_WITH_LEN_INFO, + crate::get_unwrap::GET_UNWRAP_INFO, + crate::implicit_clone::IMPLICIT_CLONE_INFO, + crate::inefficient_to_string::INEFFICIENT_TO_STRING_INFO, + crate::inspect_for_each::INSPECT_FOR_EACH_INFO, + crate::into_iter_on_ref::INTO_ITER_ON_REF_INFO, + crate::io_other_error::IO_OTHER_ERROR_INFO, + crate::ip_constant::IP_CONSTANT_INFO, + crate::is_digit_ascii_radix::IS_DIGIT_ASCII_RADIX_INFO, + crate::is_empty::CONST_IS_EMPTY_INFO, + crate::iter_cloned_collect::ITER_CLONED_COLLECT_INFO, + crate::iter_count::ITER_COUNT_INFO, + crate::iter_filter::ITER_FILTER_IS_OK_INFO, + crate::iter_filter::ITER_FILTER_IS_SOME_INFO, + crate::iter_kv_map::ITER_KV_MAP_INFO, + crate::iter_next_slice::ITER_NEXT_SLICE_INFO, + crate::iter_nth::ITER_NTH_INFO, + crate::iter_nth_zero::ITER_NTH_ZERO_INFO, + crate::iter_on_single_or_empty_collections::ITER_ON_EMPTY_COLLECTIONS_INFO, + crate::iter_on_single_or_empty_collections::ITER_ON_SINGLE_ITEMS_INFO, + crate::iter_out_of_bounds::ITER_OUT_OF_BOUNDS_INFO, + crate::iter_overeager_cloned::ITER_OVEREAGER_CLONED_INFO, + crate::iter_overeager_cloned::REDUNDANT_ITER_CLONED_INFO, + crate::iter_skip_next::ITER_SKIP_NEXT_INFO, + crate::iter_skip_zero::ITER_SKIP_ZERO_INFO, + crate::iter_with_drain::ITER_WITH_DRAIN_INFO, + crate::iterator_step_by_zero::ITERATOR_STEP_BY_ZERO_INFO, + crate::join_absolute_paths::JOIN_ABSOLUTE_PATHS_INFO, + crate::manual_c_str_literals::MANUAL_C_STR_LITERALS_INFO, + crate::manual_contains::MANUAL_CONTAINS_INFO, + crate::manual_inspect::MANUAL_INSPECT_INFO, + crate::manual_is_variant_and::MANUAL_IS_VARIANT_AND_INFO, + crate::manual_next_back::MANUAL_NEXT_BACK_INFO, + crate::manual_ok_or::MANUAL_OK_OR_INFO, + crate::manual_repeat_n::MANUAL_REPEAT_N_INFO, + crate::manual_saturating_arithmetic::MANUAL_SATURATING_ARITHMETIC_INFO, + crate::manual_str_repeat::MANUAL_STR_REPEAT_INFO, + crate::manual_try_fold::MANUAL_TRY_FOLD_INFO, + crate::map_all_any_identity::MAP_ALL_ANY_IDENTITY_INFO, + crate::map_clone::MAP_CLONE_INFO, + crate::map_collect_result_unit::MAP_COLLECT_RESULT_UNIT_INFO, + crate::map_err_ignore::MAP_ERR_IGNORE_INFO, + crate::map_flatten::MAP_FLATTEN_INFO, + crate::map_identity::MAP_IDENTITY_INFO, + crate::map_unwrap_or::MAP_UNWRAP_OR_INFO, + crate::map_with_unused_argument_over_ranges::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO, + crate::mut_mutex_lock::MUT_MUTEX_LOCK_INFO, + crate::needless_as_bytes::NEEDLESS_AS_BYTES_INFO, + crate::needless_character_iteration::NEEDLESS_CHARACTER_ITERATION_INFO, + crate::needless_collect::NEEDLESS_COLLECT_INFO, + crate::needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF_INFO, + crate::needless_option_take::NEEDLESS_OPTION_TAKE_INFO, + crate::no_effect_replace::NO_EFFECT_REPLACE_INFO, + crate::obfuscated_if_else::OBFUSCATED_IF_ELSE_INFO, + crate::ok_expect::OK_EXPECT_INFO, + crate::open_options::NONSENSICAL_OPEN_OPTIONS_INFO, + crate::open_options::SUSPICIOUS_OPEN_OPTIONS_INFO, + crate::option_as_ref_cloned::OPTION_AS_REF_CLONED_INFO, + crate::option_as_ref_deref::OPTION_AS_REF_DEREF_INFO, + crate::option_map_or_none::OPTION_MAP_OR_NONE_INFO, + crate::option_map_or_none::RESULT_MAP_OR_INTO_OPTION_INFO, + crate::or_fun_call::OR_FUN_CALL_INFO, + crate::or_fun_call::UNWRAP_OR_DEFAULT_INFO, + crate::or_then_unwrap::OR_THEN_UNWRAP_INFO, + crate::path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE_INFO, + crate::path_ends_with_ext::PATH_ENDS_WITH_EXT_INFO, + crate::range_zip_with_len::RANGE_ZIP_WITH_LEN_INFO, + crate::read_line_without_trim::READ_LINE_WITHOUT_TRIM_INFO, + crate::readonly_write_lock::READONLY_WRITE_LOCK_INFO, + crate::redundant_as_str::REDUNDANT_AS_STR_INFO, + crate::repeat_once::REPEAT_ONCE_INFO, + crate::return_and_then::RETURN_AND_THEN_INFO, + crate::search_is_some::SEARCH_IS_SOME_INFO, + crate::seek_from_current::SEEK_FROM_CURRENT_INFO, + crate::seek_to_start_instead_of_rewind::SEEK_TO_START_INSTEAD_OF_REWIND_INFO, + crate::single_char_add_str::SINGLE_CHAR_ADD_STR_INFO, + crate::skip_while_next::SKIP_WHILE_NEXT_INFO, + crate::sliced_string_as_bytes::SLICED_STRING_AS_BYTES_INFO, + crate::stable_sort_primitive::STABLE_SORT_PRIMITIVE_INFO, + crate::str_split::STR_SPLIT_AT_NEWLINE_INFO, + crate::str_splitn::MANUAL_SPLIT_ONCE_INFO, + crate::str_splitn::NEEDLESS_SPLITN_INFO, + crate::string_extend_chars::STRING_EXTEND_CHARS_INFO, + crate::string_lit_chars_any::STRING_LIT_CHARS_ANY_INFO, + crate::suspicious_command_arg_space::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, + crate::suspicious_map::SUSPICIOUS_MAP_INFO, + crate::suspicious_splitn::SUSPICIOUS_SPLITN_INFO, + crate::suspicious_to_owned::SUSPICIOUS_TO_OWNED_INFO, + crate::swap_with_temporary::SWAP_WITH_TEMPORARY_INFO, + crate::type_id_on_box::TYPE_ID_ON_BOX_INFO, + crate::unbuffered_bytes::UNBUFFERED_BYTES_INFO, + crate::uninit_assumed_init::UNINIT_ASSUMED_INIT_INFO, + crate::unit_hash::UNIT_HASH_INFO, + crate::unnecessary_fallible_conversions::UNNECESSARY_FALLIBLE_CONVERSIONS_INFO, + crate::unnecessary_filter_map::UNNECESSARY_FILTER_MAP_INFO, + crate::unnecessary_filter_map::UNNECESSARY_FIND_MAP_INFO, + crate::unnecessary_first_then_check::UNNECESSARY_FIRST_THEN_CHECK_INFO, + crate::unnecessary_fold::UNNECESSARY_FOLD_INFO, + crate::unnecessary_get_then_check::UNNECESSARY_GET_THEN_CHECK_INFO, + crate::unnecessary_join::UNNECESSARY_JOIN_INFO, + crate::unnecessary_lazy_eval::UNNECESSARY_LAZY_EVALUATIONS_INFO, + crate::unnecessary_literal_unwrap::UNNECESSARY_LITERAL_UNWRAP_INFO, + crate::unnecessary_map_or::UNNECESSARY_MAP_OR_INFO, + crate::unnecessary_min_or_max::UNNECESSARY_MIN_OR_MAX_INFO, + crate::unnecessary_result_map_or_else::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO, + crate::unnecessary_sort_by::UNNECESSARY_SORT_BY_INFO, + crate::unnecessary_to_owned::UNNECESSARY_TO_OWNED_INFO, + crate::unwrap_expect_used::EXPECT_USED_INFO, + crate::unwrap_expect_used::UNWRAP_USED_INFO, + crate::useless_asref::USELESS_ASREF_INFO, + crate::useless_nonzero_new_unchecked::USELESS_NONZERO_NEW_UNCHECKED_INFO, + crate::vec_resize_to_zero::VEC_RESIZE_TO_ZERO_INFO, + crate::verbose_file_reads::VERBOSE_FILE_READS_INFO, + crate::waker_clone_wake::WAKER_CLONE_WAKE_INFO, + crate::wrong_self_convention::WRONG_SELF_CONVENTION_INFO, + crate::zst_offset::ZST_OFFSET_INFO, ]; diff --git a/clippy_lints_methods/src/double_ended_iterator_last.rs b/clippy_lints_methods/src/double_ended_iterator_last.rs index 6d841853fbe5..ca7bbf56a3a9 100644 --- a/clippy_lints_methods/src/double_ended_iterator_last.rs +++ b/clippy_lints_methods/src/double_ended_iterator_last.rs @@ -7,7 +7,31 @@ use rustc_lint::LateContext; use rustc_middle::ty::Instance; use rustc_span::Span; -use super::DOUBLE_ENDED_ITERATOR_LAST; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Iterator::last` being called on a `DoubleEndedIterator`, which can be replaced + /// with `DoubleEndedIterator::next_back`. + /// + /// ### Why is this bad? + /// + /// `Iterator::last` is implemented by consuming the iterator, which is unnecessary if + /// the iterator is a `DoubleEndedIterator`. Since Rust traits do not allow specialization, + /// `Iterator::last` cannot be optimized for `DoubleEndedIterator`. + /// + /// ### Example + /// ```no_run + /// let last_arg = "echo hello world".split(' ').last(); + /// ``` + /// Use instead: + /// ```no_run + /// let last_arg = "echo hello world".split(' ').next_back(); + /// ``` + #[clippy::version = "1.86.0"] + pub DOUBLE_ENDED_ITERATOR_LAST, + perf, + "using `Iterator::last` on a `DoubleEndedIterator`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Expr<'_>, call_span: Span) { let typeck = cx.typeck_results(); diff --git a/clippy_lints_methods/src/drain_collect.rs b/clippy_lints_methods/src/drain_collect.rs index d6e012f8b5ad..3255d700383f 100644 --- a/clippy_lints_methods/src/drain_collect.rs +++ b/clippy_lints_methods/src/drain_collect.rs @@ -1,4 +1,3 @@ -use crate::DRAIN_COLLECT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_lang_item; @@ -10,6 +9,42 @@ use rustc_middle::ty; use rustc_middle::ty::Ty; use rustc_span::{Symbol, sym}; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `.drain()` that clear the collection, immediately followed by a call to `.collect()`. + /// + /// > "Collection" in this context refers to any type with a `drain` method: + /// > `Vec`, `VecDeque`, `BinaryHeap`, `HashSet`,`HashMap`, `String` + /// + /// ### Why is this bad? + /// Using `mem::take` is faster as it avoids the allocation. + /// When using `mem::take`, the old collection is replaced with an empty one and ownership of + /// the old collection is returned. + /// + /// ### Known issues + /// `mem::take(&mut vec)` is almost equivalent to `vec.drain(..).collect()`, except that + /// it also moves the **capacity**. The user might have explicitly written it this way + /// to keep the capacity on the original `Vec`. + /// + /// ### Example + /// ```no_run + /// fn remove_all(v: &mut Vec) -> Vec { + /// v.drain(..).collect() + /// } + /// ``` + /// Use instead: + /// ```no_run + /// use std::mem; + /// fn remove_all(v: &mut Vec) -> Vec { + /// mem::take(v) + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub DRAIN_COLLECT, + perf, + "calling `.drain(..).collect()` to move all elements into a new collection" +} + /// Checks if both types match the given diagnostic item, e.g.: /// /// `vec![1,2].drain(..).collect::>()` diff --git a/clippy_lints_methods/src/err_expect.rs b/clippy_lints_methods/src/err_expect.rs index 91ddaca07d8b..d89924cee4ac 100644 --- a/clippy_lints_methods/src/err_expect.rs +++ b/clippy_lints_methods/src/err_expect.rs @@ -1,4 +1,3 @@ -use super::ERR_EXPECT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::ty::{has_debug_impl, is_type_diagnostic_item}; @@ -8,6 +7,29 @@ use rustc_middle::ty; use rustc_middle::ty::Ty; use rustc_span::{Span, sym}; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.err().expect()` calls on the `Result` type. + /// + /// ### Why is this bad? + /// `.expect_err()` can be called directly to avoid the extra type conversion from `err()`. + /// + /// ### Example + /// ```should_panic + /// let x: Result = Ok(10); + /// x.err().expect("Testing err().expect()"); + /// ``` + /// Use instead: + /// ```should_panic + /// let x: Result = Ok(10); + /// x.expect_err("Testing expect_err"); + /// ``` + #[clippy::version = "1.62.0"] + pub ERR_EXPECT, + style, + r#"using `.err().expect("")` when `.expect_err("")` can be used"# +} + pub(super) fn check( cx: &LateContext<'_>, _expr: &rustc_hir::Expr<'_>, diff --git a/clippy_lints_methods/src/expect_fun_call.rs b/clippy_lints_methods/src/expect_fun_call.rs index 82e5a6d5a412..ec0c9a45eb8f 100644 --- a/clippy_lints_methods/src/expect_fun_call.rs +++ b/clippy_lints_methods/src/expect_fun_call.rs @@ -10,7 +10,43 @@ use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; use std::borrow::Cow; -use super::EXPECT_FUN_CALL; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`, + /// etc., and suggests to use `unwrap_or_else` instead + /// + /// ### Why is this bad? + /// The function will always be called. + /// + /// ### Known problems + /// If the function has side-effects, not calling it will + /// change the semantics of the program, but you shouldn't rely on that anyway. + /// + /// ### Example + /// ```no_run + /// # let foo = Some(String::new()); + /// # let err_code = "418"; + /// # let err_msg = "I'm a teapot"; + /// foo.expect(&format!("Err {}: {}", err_code, err_msg)); + /// + /// // or + /// + /// # let foo = Some(String::new()); + /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str()); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let foo = Some(String::new()); + /// # let err_code = "418"; + /// # let err_msg = "I'm a teapot"; + /// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg)); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub EXPECT_FUN_CALL, + perf, + "using any `expect` method with a function call" +} /// Checks for the `EXPECT_FUN_CALL` lint. #[allow(clippy::too_many_lines)] diff --git a/clippy_lints_methods/src/extend_with_drain.rs b/clippy_lints_methods/src/extend_with_drain.rs index db60061904f6..b7490260660d 100644 --- a/clippy_lints_methods/src/extend_with_drain.rs +++ b/clippy_lints_methods/src/extend_with_drain.rs @@ -6,7 +6,33 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::LateContext; -use super::EXTEND_WITH_DRAIN; +declare_clippy_lint! { + /// ### What it does + /// Checks for occurrences where one vector gets extended instead of append + /// + /// ### Why is this bad? + /// Using `append` instead of `extend` is more concise and faster + /// + /// ### Example + /// ```no_run + /// let mut a = vec![1, 2, 3]; + /// let mut b = vec![4, 5, 6]; + /// + /// a.extend(b.drain(..)); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let mut a = vec![1, 2, 3]; + /// let mut b = vec![4, 5, 6]; + /// + /// a.append(&mut b); + /// ``` + #[clippy::version = "1.55.0"] + pub EXTEND_WITH_DRAIN, + perf, + "using vec.append(&mut vec) to move the full range of a vector to another" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); diff --git a/clippy_lints_methods/src/filetype_is_file.rs b/clippy_lints_methods/src/filetype_is_file.rs index 35008c39c084..20a88cd6b50f 100644 --- a/clippy_lints_methods/src/filetype_is_file.rs +++ b/clippy_lints_methods/src/filetype_is_file.rs @@ -5,7 +5,47 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::FILETYPE_IS_FILE; +declare_clippy_lint! { + /// ### What it does + /// Checks for `FileType::is_file()`. + /// + /// ### Why restrict this? + /// When people testing a file type with `FileType::is_file` + /// they are testing whether a path is something they can get bytes from. But + /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover + /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention. + /// + /// ### Example + /// ```no_run + /// # || { + /// let metadata = std::fs::metadata("foo.txt")?; + /// let filetype = metadata.file_type(); + /// + /// if filetype.is_file() { + /// // read file + /// } + /// # Ok::<_, std::io::Error>(()) + /// # }; + /// ``` + /// + /// should be written as: + /// + /// ```no_run + /// # || { + /// let metadata = std::fs::metadata("foo.txt")?; + /// let filetype = metadata.file_type(); + /// + /// if !filetype.is_dir() { + /// // read file + /// } + /// # Ok::<_, std::io::Error>(()) + /// # }; + /// ``` + #[clippy::version = "1.42.0"] + pub FILETYPE_IS_FILE, + restriction, + "`FileType::is_file` is not recommended to test for readable file type" +} pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv); diff --git a/clippy_lints_methods/src/filter_map.rs b/clippy_lints_methods/src/filter_map.rs index 4dd54cf19745..d246a55ce098 100644 --- a/clippy_lints_methods/src/filter_map.rs +++ b/clippy_lints_methods/src/filter_map.rs @@ -13,7 +13,103 @@ use rustc_middle::ty::adjustment::Adjust; use rustc_span::Span; use rustc_span::symbol::{Ident, Symbol}; -use super::{MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP, RESULT_FILTER_MAP}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.filter(_).map(_)` that can be written more simply + /// as `filter_map(_)`. + /// + /// ### Why is this bad? + /// Redundant code in the `filter` and `map` operations is poor style and + /// less performant. + /// + /// ### Example + /// ```no_run + /// (0_i32..10) + /// .filter(|n| n.checked_add(1).is_some()) + /// .map(|n| n.checked_add(1).unwrap()); + /// ``` + /// + /// Use instead: + /// ```no_run + /// (0_i32..10).filter_map(|n| n.checked_add(1)); + /// ``` + #[clippy::version = "1.51.0"] + pub MANUAL_FILTER_MAP, + complexity, + "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.find(_).map(_)` that can be written more simply + /// as `find_map(_)`. + /// + /// ### Why is this bad? + /// Redundant code in the `find` and `map` operations is poor style and + /// less performant. + /// + /// ### Example + /// ```no_run + /// (0_i32..10) + /// .find(|n| n.checked_add(1).is_some()) + /// .map(|n| n.checked_add(1).unwrap()); + /// ``` + /// + /// Use instead: + /// ```no_run + /// (0_i32..10).find_map(|n| n.checked_add(1)); + /// ``` + #[clippy::version = "1.51.0"] + pub MANUAL_FIND_MAP, + complexity, + "using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for iterators of `Option`s using `.filter(Option::is_some).map(Option::unwrap)` that may + /// be replaced with a `.flatten()` call. + /// + /// ### Why is this bad? + /// `Option` is like a collection of 0-1 things, so `flatten` + /// automatically does this without suspicious-looking `unwrap` calls. + /// + /// ### Example + /// ```no_run + /// let _ = std::iter::empty::>().filter(Option::is_some).map(Option::unwrap); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = std::iter::empty::>().flatten(); + /// ``` + #[clippy::version = "1.53.0"] + pub OPTION_FILTER_MAP, + complexity, + "filtering `Option` for `Some` then force-unwrapping, which can be one type-safe operation" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for iterators of `Result`s using `.filter(Result::is_ok).map(Result::unwrap)` that may + /// be replaced with a `.flatten()` call. + /// + /// ### Why is this bad? + /// `Result` implements `IntoIterator`. This means that `Result` can be flattened + /// automatically without suspicious-looking `unwrap` calls. + /// + /// ### Example + /// ```no_run + /// let _ = std::iter::empty::>().filter(Result::is_ok).map(Result::unwrap); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = std::iter::empty::>().flatten(); + /// ``` + #[clippy::version = "1.77.0"] + pub RESULT_FILTER_MAP, + complexity, + "filtering `Result` for `Ok` then force-unwrapping, which can be one type-safe operation" +} fn is_method(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol) -> bool { match &expr.kind { diff --git a/clippy_lints_methods/src/filter_map_bool_then.rs b/clippy_lints_methods/src/filter_map_bool_then.rs index 965993808f6b..e25375abb500 100644 --- a/clippy_lints_methods/src/filter_map_bool_then.rs +++ b/clippy_lints_methods/src/filter_map_bool_then.rs @@ -1,4 +1,3 @@ -use super::FILTER_MAP_BOOL_THEN; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::is_copy; @@ -14,6 +13,37 @@ use rustc_middle::ty::Binder; use rustc_middle::ty::adjustment::Adjust; use rustc_span::{Span, sym}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `bool::then` in `Iterator::filter_map`. + /// + /// ### Why is this bad? + /// This can be written with `filter` then `map` instead, which would reduce nesting and + /// separates the filtering from the transformation phase. This comes with no cost to + /// performance and is just cleaner. + /// + /// ### Limitations + /// Does not lint `bool::then_some`, as it eagerly evaluates its arguments rather than lazily. + /// This can create differing behavior, so better safe than sorry. + /// + /// ### Example + /// ```no_run + /// # fn really_expensive_fn(i: i32) -> i32 { i } + /// # let v = vec![]; + /// _ = v.into_iter().filter_map(|i| (i % 2 == 0).then(|| really_expensive_fn(i))); + /// ``` + /// Use instead: + /// ```no_run + /// # fn really_expensive_fn(i: i32) -> i32 { i } + /// # let v = vec![]; + /// _ = v.into_iter().filter(|i| i % 2 == 0).map(|i| really_expensive_fn(i)); + /// ``` + #[clippy::version = "1.73.0"] + pub FILTER_MAP_BOOL_THEN, + style, + "checks for usage of `bool::then` in `Iterator::filter_map`" +} + pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &Expr<'_>, call_span: Span) { if !expr.span.in_external_macro(cx.sess().source_map()) && is_trait_method(cx, expr, sym::Iterator) diff --git a/clippy_lints_methods/src/filter_map_identity.rs b/clippy_lints_methods/src/filter_map_identity.rs index b04d761d4860..f70cdf157bdd 100644 --- a/clippy_lints_methods/src/filter_map_identity.rs +++ b/clippy_lints_methods/src/filter_map_identity.rs @@ -6,7 +6,28 @@ use rustc_hir::ExprKind; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::FILTER_MAP_IDENTITY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `filter_map(|x| x)`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely by using `flatten`. + /// + /// ### Example + /// ```no_run + /// # let iter = vec![Some(1)].into_iter(); + /// iter.filter_map(|x| x); + /// ``` + /// Use instead: + /// ```no_run + /// # let iter = vec![Some(1)].into_iter(); + /// iter.flatten(); + /// ``` + #[clippy::version = "1.52.0"] + pub FILTER_MAP_IDENTITY, + complexity, + "call to `filter_map` where `flatten` is sufficient" +} fn is_identity(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { if is_expr_untyped_identity_function(cx, expr) { diff --git a/clippy_lints_methods/src/filter_map_next.rs b/clippy_lints_methods/src/filter_map_next.rs index 9f3c346042ff..8536b756c45e 100644 --- a/clippy_lints_methods/src/filter_map_next.rs +++ b/clippy_lints_methods/src/filter_map_next.rs @@ -7,7 +7,28 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::FILTER_MAP_NEXT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.filter_map(_).next()`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.find_map(_)`. + /// + /// ### Example + /// ```no_run + /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next(); + /// ``` + /// Can be written as + /// + /// ```no_run + /// (0..3).find_map(|x| if x == 2 { Some(x) } else { None }); + /// ``` + #[clippy::version = "1.36.0"] + pub FILTER_MAP_NEXT, + pedantic, + "using combination of `filter_map` and `next` which can usually be written as a single method call" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/filter_next.rs b/clippy_lints_methods/src/filter_next.rs index 6c1a14fc8829..a79204e1f67a 100644 --- a/clippy_lints_methods/src/filter_next.rs +++ b/clippy_lints_methods/src/filter_next.rs @@ -7,7 +7,30 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::FILTER_NEXT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.filter(_).next()`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.find(_)`. + /// + /// ### Example + /// ```no_run + /// # let vec = vec![1]; + /// vec.iter().filter(|x| **x == 0).next(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let vec = vec![1]; + /// vec.iter().find(|x| **x == 0); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub FILTER_NEXT, + complexity, + "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`" +} fn path_to_local(expr: &hir::Expr<'_>) -> Option { match expr.kind { diff --git a/clippy_lints_methods/src/flat_map_identity.rs b/clippy_lints_methods/src/flat_map_identity.rs index 0c2ecfbc8ffd..ed4b6e57e72c 100644 --- a/clippy_lints_methods/src/flat_map_identity.rs +++ b/clippy_lints_methods/src/flat_map_identity.rs @@ -5,7 +5,28 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::FLAT_MAP_IDENTITY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `flat_map(|x| x)`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely by using `flatten`. + /// + /// ### Example + /// ```no_run + /// # let iter = vec![vec![0]].into_iter(); + /// iter.flat_map(|x| x); + /// ``` + /// Can be written as + /// ```no_run + /// # let iter = vec![vec![0]].into_iter(); + /// iter.flatten(); + /// ``` + #[clippy::version = "1.39.0"] + pub FLAT_MAP_IDENTITY, + complexity, + "call to `flat_map` where `flatten` is sufficient" +} /// lint use of `flat_map` for `Iterators` where `flatten` would be sufficient pub(super) fn check<'tcx>( diff --git a/clippy_lints_methods/src/flat_map_option.rs b/clippy_lints_methods/src/flat_map_option.rs index 3242dcadb701..92d66ef2eb38 100644 --- a/clippy_lints_methods/src/flat_map_option.rs +++ b/clippy_lints_methods/src/flat_map_option.rs @@ -1,13 +1,38 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_trait_method; +use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Span, sym}; -use super::FLAT_MAP_OPTION; -use clippy_utils::ty::is_type_diagnostic_item; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `Iterator::flat_map()` where `filter_map()` could be + /// used instead. + /// + /// ### Why is this bad? + /// `filter_map()` is known to always produce 0 or 1 output items per input item, + /// rather than however many the inner iterator type produces. + /// Therefore, it maintains the upper bound in `Iterator::size_hint()`, + /// and communicates to the reader that the input items are not being expanded into + /// multiple output items without their having to notice that the mapping function + /// returns an `Option`. + /// + /// ### Example + /// ```no_run + /// let nums: Vec = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// let nums: Vec = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect(); + /// ``` + #[clippy::version = "1.53.0"] + pub FLAT_MAP_OPTION, + pedantic, + "used `flat_map` where `filter_map` could be used instead" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) { if !is_trait_method(cx, expr, sym::Iterator) { diff --git a/clippy_lints_methods/src/format_collect.rs b/clippy_lints_methods/src/format_collect.rs index 1b28596d50da..394effa05967 100644 --- a/clippy_lints_methods/src/format_collect.rs +++ b/clippy_lints_methods/src/format_collect.rs @@ -1,4 +1,3 @@ -use super::FORMAT_COLLECT; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{is_format_macro, root_macro_call_first_node}; use clippy_utils::ty::is_type_lang_item; @@ -6,6 +5,41 @@ use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::LateContext; use rustc_span::Span; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map(|_| format!(..)).collect::()`. + /// + /// ### Why is this bad? + /// This allocates a new string for every element in the iterator. + /// This can be done more efficiently by creating the `String` once and appending to it in `Iterator::fold`, + /// using either the `write!` macro which supports exactly the same syntax as the `format!` macro, + /// or concatenating with `+` in case the iterator yields `&str`/`String`. + /// + /// Note also that `write!`-ing into a `String` can never fail, despite the return type of `write!` being `std::fmt::Result`, + /// so it can be safely ignored or unwrapped. + /// + /// ### Example + /// ```no_run + /// fn hex_encode(bytes: &[u8]) -> String { + /// bytes.iter().map(|b| format!("{b:02X}")).collect() + /// } + /// ``` + /// Use instead: + /// ```no_run + /// use std::fmt::Write; + /// fn hex_encode(bytes: &[u8]) -> String { + /// bytes.iter().fold(String::new(), |mut output, b| { + /// let _ = write!(output, "{b:02X}"); + /// output + /// }) + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub FORMAT_COLLECT, + pedantic, + "`format!`ing every element in a collection, then collecting the strings into a new `String`" +} + /// Same as `peel_blocks` but only actually considers blocks that are not from an expansion. /// This is needed because always calling `peel_blocks` would otherwise remove parts of the /// `format!` macro, which would cause `root_macro_call_first_node` to return `None`. diff --git a/clippy_lints_methods/src/from_iter_instead_of_collect.rs b/clippy_lints_methods/src/from_iter_instead_of_collect.rs index 045363058d19..f47b848fa7a4 100644 --- a/clippy_lints_methods/src/from_iter_instead_of_collect.rs +++ b/clippy_lints_methods/src/from_iter_instead_of_collect.rs @@ -11,7 +11,46 @@ use rustc_lint::LateContext; use rustc_middle::ty::GenericParamDefKind; use rustc_span::sym; -use super::FROM_ITER_INSTEAD_OF_COLLECT; +declare_clippy_lint! { + /// ### What it does + /// Checks for `from_iter()` function calls on types that implement the `FromIterator` + /// trait. + /// + /// ### Why is this bad? + /// If it's needed to create a collection from the contents of an iterator, the `Iterator::collect(_)` + /// method is preferred. However, when it's needed to specify the container type, + /// `Vec::from_iter(_)` can be more readable than using a turbofish (e.g. `_.collect::>()`). See + /// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html) + /// + /// ### Example + /// ```no_run + /// let five_fives = std::iter::repeat(5).take(5); + /// + /// let v = Vec::from_iter(five_fives); + /// + /// assert_eq!(v, vec![5, 5, 5, 5, 5]); + /// ``` + /// Use instead: + /// ```no_run + /// let five_fives = std::iter::repeat(5).take(5); + /// + /// let v: Vec = five_fives.collect(); + /// + /// assert_eq!(v, vec![5, 5, 5, 5, 5]); + /// ``` + /// but prefer to use + /// ```no_run + /// let numbers: Vec = FromIterator::from_iter(1..=5); + /// ``` + /// instead of + /// ```no_run + /// let numbers = (1..=5).collect::>(); + /// ``` + #[clippy::version = "1.49.0"] + pub FROM_ITER_INSTEAD_OF_COLLECT, + pedantic, + "use `.collect()` instead of `::from_iter()`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>], func: &Expr<'_>) { if is_path_diagnostic_item(cx, func, sym::from_iter_fn) diff --git a/clippy_lints_methods/src/get_first.rs b/clippy_lints_methods/src/get_first.rs index f4465e654c2e..de723684b845 100644 --- a/clippy_lints_methods/src/get_first.rs +++ b/clippy_lints_methods/src/get_first.rs @@ -9,7 +9,31 @@ use rustc_lint::LateContext; use rustc_span::source_map::Spanned; use rustc_span::sym; -use super::GET_FIRST; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `x.get(0)` instead of + /// `x.first()` or `x.front()`. + /// + /// ### Why is this bad? + /// Using `x.first()` for `Vec`s and slices or `x.front()` + /// for `VecDeque`s is easier to read and has the same result. + /// + /// ### Example + /// ```no_run + /// let x = vec![2, 3, 5]; + /// let first_element = x.get(0); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let x = vec![2, 3, 5]; + /// let first_element = x.first(); + /// ``` + #[clippy::version = "1.63.0"] + pub GET_FIRST, + style, + "Using `x.get(0)` when `x.first()` or `x.front()` is simpler" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/get_last_with_len.rs b/clippy_lints_methods/src/get_last_with_len.rs index 5f6fb4c821d5..f60e9cdc734e 100644 --- a/clippy_lints_methods/src/get_last_with_len.rs +++ b/clippy_lints_methods/src/get_last_with_len.rs @@ -8,7 +8,38 @@ use rustc_middle::ty; use rustc_span::source_map::Spanned; use rustc_span::sym; -use super::GET_LAST_WITH_LEN; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `x.get(x.len() - 1)` instead of + /// `x.last()`. + /// + /// ### Why is this bad? + /// Using `x.last()` is easier to read and has the same + /// result. + /// + /// Note that using `x[x.len() - 1]` is semantically different from + /// `x.last()`. Indexing into the array will panic on out-of-bounds + /// accesses, while `x.get()` and `x.last()` will return `None`. + /// + /// There is another lint (get_unwrap) that covers the case of using + /// `x.get(index).unwrap()` instead of `x[index]`. + /// + /// ### Example + /// ```no_run + /// let x = vec![2, 3, 5]; + /// let last_element = x.get(x.len() - 1); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let x = vec![2, 3, 5]; + /// let last_element = x.last(); + /// ``` + #[clippy::version = "1.37.0"] + pub GET_LAST_WITH_LEN, + complexity, + "Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { // Argument to "get" is a subtraction diff --git a/clippy_lints_methods/src/get_unwrap.rs b/clippy_lints_methods/src/get_unwrap.rs index 9daad1a8a949..fa6fef516443 100644 --- a/clippy_lints_methods/src/get_unwrap.rs +++ b/clippy_lints_methods/src/get_unwrap.rs @@ -8,7 +8,42 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::GET_UNWRAP; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.get().unwrap()` (or + /// `.get_mut().unwrap`) on a standard library type which implements `Index` + /// + /// ### Why restrict this? + /// Using the Index trait (`[]`) is more clear and more + /// concise. + /// + /// ### Known problems + /// Not a replacement for error handling: Using either + /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic` + /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a + /// temporary placeholder for dealing with the `Option` type, then this does + /// not mitigate the need for error handling. If there is a chance that `.get()` + /// will be `None` in your program, then it is advisable that the `None` case + /// is handled in a future refactor instead of using `.unwrap()` or the Index + /// trait. + /// + /// ### Example + /// ```no_run + /// let mut some_vec = vec![0, 1, 2, 3]; + /// let last = some_vec.get(3).unwrap(); + /// *some_vec.get_mut(0).unwrap() = 1; + /// ``` + /// The correct use would be: + /// ```no_run + /// let mut some_vec = vec![0, 1, 2, 3]; + /// let last = some_vec[3]; + /// some_vec[0] = 1; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub GET_UNWRAP, + restriction, + "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/implicit_clone.rs b/clippy_lints_methods/src/implicit_clone.rs index 9724463f0c08..5d2cff99cfba 100644 --- a/clippy_lints_methods/src/implicit_clone.rs +++ b/clippy_lints_methods/src/implicit_clone.rs @@ -7,7 +7,31 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::Symbol; -use super::IMPLICIT_CLONE; +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer. + /// + /// ### Why is this bad? + /// These methods do the same thing as `_.clone()` but may be confusing as + /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned. + /// + /// ### Example + /// ```no_run + /// let a = vec![1, 2, 3]; + /// let b = a.to_vec(); + /// let c = a.to_owned(); + /// ``` + /// Use instead: + /// ```no_run + /// let a = vec![1, 2, 3]; + /// let b = a.clone(); + /// let c = a.clone(); + /// ``` + #[clippy::version = "1.52.0"] + pub IMPLICIT_CLONE, + pedantic, + "implicitly cloning a value by invoking a function on its dereferenced type" +} pub fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) diff --git a/clippy_lints_methods/src/inefficient_to_string.rs b/clippy_lints_methods/src/inefficient_to_string.rs index 4ed7de81ea3d..9c666d8b9dbf 100644 --- a/clippy_lints_methods/src/inefficient_to_string.rs +++ b/clippy_lints_methods/src/inefficient_to_string.rs @@ -7,7 +7,29 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::{Symbol, sym}; -use super::INEFFICIENT_TO_STRING; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.to_string()` on an `&&T` where + /// `T` implements `ToString` directly (like `&&str` or `&&String`). + /// + /// ### Why is this bad? + /// This bypasses the specialized implementation of + /// `ToString` and instead goes through the more expensive string formatting + /// facilities. + /// + /// ### Example + /// ```no_run + /// // Generic implementation for `T: Display` is used (slow) + /// ["foo", "bar"].iter().map(|s| s.to_string()); + /// + /// // OK, the specialized impl is used + /// ["foo", "bar"].iter().map(|&s| s.to_string()); + /// ``` + #[clippy::version = "1.40.0"] + pub INEFFICIENT_TO_STRING, + pedantic, + "using `to_string` on `&&T` where `T: ToString`" +} /// Checks for the `INEFFICIENT_TO_STRING` lint pub fn check( diff --git a/clippy_lints_methods/src/inspect_for_each.rs b/clippy_lints_methods/src/inspect_for_each.rs index 3706f3b670ba..504904b4344d 100644 --- a/clippy_lints_methods/src/inspect_for_each.rs +++ b/clippy_lints_methods/src/inspect_for_each.rs @@ -4,7 +4,35 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::INSPECT_FOR_EACH; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `inspect().for_each()`. + /// + /// ### Why is this bad? + /// It is the same as performing the computation + /// inside `inspect` at the beginning of the closure in `for_each`. + /// + /// ### Example + /// ```no_run + /// [1,2,3,4,5].iter() + /// .inspect(|&x| println!("inspect the number: {}", x)) + /// .for_each(|&x| { + /// assert!(x >= 0); + /// }); + /// ``` + /// Can be written as + /// ```no_run + /// [1,2,3,4,5].iter() + /// .for_each(|&x| { + /// println!("inspect the number: {}", x); + /// assert!(x >= 0); + /// }); + /// ``` + #[clippy::version = "1.51.0"] + pub INSPECT_FOR_EACH, + complexity, + "using `.inspect().for_each()`, which can be replaced with `.for_each()`" +} /// lint use of `inspect().for_each()` for `Iterators` pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, inspect_span: Span) { diff --git a/clippy_lints_methods/src/into_iter_on_ref.rs b/clippy_lints_methods/src/into_iter_on_ref.rs index bedeb63367d0..ec22c7aca761 100644 --- a/clippy_lints_methods/src/into_iter_on_ref.rs +++ b/clippy_lints_methods/src/into_iter_on_ref.rs @@ -8,7 +8,32 @@ use rustc_middle::ty::{self, Ty}; use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; -use super::INTO_ITER_ON_REF; +declare_clippy_lint! { + /// ### What it does + /// Checks for `into_iter` calls on references which should be replaced by `iter` + /// or `iter_mut`. + /// + /// ### Why is this bad? + /// Readability. Calling `into_iter` on a reference will not move out its + /// content into the resulting iterator, which is confusing. It is better just call `iter` or + /// `iter_mut` directly. + /// + /// ### Example + /// ```no_run + /// # let vec = vec![3, 4, 5]; + /// (&vec).into_iter(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let vec = vec![3, 4, 5]; + /// (&vec).iter(); + /// ``` + #[clippy::version = "1.32.0"] + pub INTO_ITER_ON_REF, + style, + "using `.into_iter()` on a reference" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints_methods/src/io_other_error.rs b/clippy_lints_methods/src/io_other_error.rs index 9276261606e1..18482fdbc4b6 100644 --- a/clippy_lints_methods/src/io_other_error.rs +++ b/clippy_lints_methods/src/io_other_error.rs @@ -5,6 +5,28 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; +declare_clippy_lint! { + /// This lint warns on calling `io::Error::new(..)` with a kind of + /// `io::ErrorKind::Other`. + /// + /// ### Why is this bad? + /// Since Rust 1.74, there's the `io::Error::other(_)` shortcut. + /// + /// ### Example + /// ```no_run + /// use std::io; + /// let _ = io::Error::new(io::ErrorKind::Other, "bad".to_string()); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = std::io::Error::other("bad".to_string()); + /// ``` + #[clippy::version = "1.87.0"] + pub IO_OTHER_ERROR, + style, + "calling `std::io::Error::new(std::io::ErrorKind::Other, _)`" +} + pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args: &[Expr<'_>], msrv: Msrv) { if let [error_kind, error] = args && !expr.span.from_expansion() @@ -19,7 +41,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args { span_lint_and_then( cx, - super::IO_OTHER_ERROR, + IO_OTHER_ERROR, expr.span, "this can be `std::io::Error::other(_)`", |diag| { diff --git a/clippy_lints_methods/src/ip_constant.rs b/clippy_lints_methods/src/ip_constant.rs index 83803fba6a13..608a9787749b 100644 --- a/clippy_lints_methods/src/ip_constant.rs +++ b/clippy_lints_methods/src/ip_constant.rs @@ -6,7 +6,41 @@ use rustc_lint::LateContext; use rustc_span::sym; use smallvec::SmallVec; -use super::IP_CONSTANT; +declare_clippy_lint! { + /// ### What it does + /// Checks for IP addresses that could be replaced with predefined constants such as + /// `Ipv4Addr::new(127, 0, 0, 1)` instead of using the appropriate constants. + /// + /// ### Why is this bad? + /// Using specific IP addresses like `127.0.0.1` or `::1` is less clear and less maintainable than using the + /// predefined constants `Ipv4Addr::LOCALHOST` or `Ipv6Addr::LOCALHOST`. These constants improve code + /// readability, make the intent explicit, and are less error-prone. + /// + /// ### Example + /// ```no_run + /// use std::net::{Ipv4Addr, Ipv6Addr}; + /// + /// // IPv4 loopback + /// let addr_v4 = Ipv4Addr::new(127, 0, 0, 1); + /// + /// // IPv6 loopback + /// let addr_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + /// ``` + /// Use instead: + /// ```no_run + /// use std::net::{Ipv4Addr, Ipv6Addr}; + /// + /// // IPv4 loopback + /// let addr_v4 = Ipv4Addr::LOCALHOST; + /// + /// // IPv6 loopback + /// let addr_v6 = Ipv6Addr::LOCALHOST; + /// ``` + #[clippy::version = "1.89.0"] + pub IP_CONSTANT, + pedantic, + "hardcoded localhost IP address" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) { if let ExprKind::Path(QPath::TypeRelative( diff --git a/clippy_lints_methods/src/is_digit_ascii_radix.rs b/clippy_lints_methods/src/is_digit_ascii_radix.rs index 9c32e9ac539d..fd9cd51ed61e 100644 --- a/clippy_lints_methods/src/is_digit_ascii_radix.rs +++ b/clippy_lints_methods/src/is_digit_ascii_radix.rs @@ -1,4 +1,3 @@ -use super::IS_DIGIT_ASCII_RADIX; use clippy_utils::consts::{ConstEvalCtxt, FullInt}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; @@ -7,6 +6,33 @@ use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; +declare_clippy_lint! { + /// ### What it does + /// Finds usages of [`char::is_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that + /// can be replaced with [`is_ascii_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or + /// [`is_ascii_hexdigit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit). + /// + /// ### Why is this bad? + /// `is_digit(..)` is slower and requires specifying the radix. + /// + /// ### Example + /// ```no_run + /// let c: char = '6'; + /// c.is_digit(10); + /// c.is_digit(16); + /// ``` + /// Use instead: + /// ```no_run + /// let c: char = '6'; + /// c.is_ascii_digit(); + /// c.is_ascii_hexdigit(); + /// ``` + #[clippy::version = "1.62.0"] + pub IS_DIGIT_ASCII_RADIX, + style, + "use of `char::is_digit(..)` with literal radix of 10 or 16" +} + pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, diff --git a/clippy_lints_methods/src/is_empty.rs b/clippy_lints_methods/src/is_empty.rs index 545bef1a4c5b..1ea3814c88f1 100644 --- a/clippy_lints_methods/src/is_empty.rs +++ b/clippy_lints_methods/src/is_empty.rs @@ -6,7 +6,31 @@ use rustc_hir::{Expr, HirId}; use rustc_lint::{LateContext, LintContext}; use rustc_span::sym; -use super::CONST_IS_EMPTY; +declare_clippy_lint! { + /// ### What it does + /// It identifies calls to `.is_empty()` on constant values. + /// + /// ### Why is this bad? + /// String literals and constant values are known at compile time. Checking if they + /// are empty will always return the same value. This might not be the intention of + /// the expression. + /// + /// ### Example + /// ```no_run + /// let value = ""; + /// if value.is_empty() { + /// println!("the string is empty"); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// println!("the string is empty"); + /// ``` + #[clippy::version = "1.79.0"] + pub CONST_IS_EMPTY, + suspicious, + "is_empty() called on strings known at compile time" +} /// Expression whose initialization depend on a constant conditioned by a `#[cfg(…)]` directive will /// not trigger the lint. diff --git a/clippy_lints_methods/src/iter_cloned_collect.rs b/clippy_lints_methods/src/iter_cloned_collect.rs index 9a4bd706e7a4..8f301054def7 100644 --- a/clippy_lints_methods/src/iter_cloned_collect.rs +++ b/clippy_lints_methods/src/iter_cloned_collect.rs @@ -7,7 +7,29 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Symbol, sym}; -use super::ITER_CLONED_COLLECT; +declare_clippy_lint! { + /// ### What it does + /// Checks for the use of `.cloned().collect()` on slice to + /// create a `Vec`. + /// + /// ### Why is this bad? + /// `.to_vec()` is clearer + /// + /// ### Example + /// ```no_run + /// let s = [1, 2, 3, 4, 5]; + /// let s2: Vec = s[..].iter().cloned().collect(); + /// ``` + /// The better use would be: + /// ```no_run + /// let s = [1, 2, 3, 4, 5]; + /// let s2: Vec = s.to_vec(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub ITER_CLONED_COLLECT, + style, + "using `.cloned().collect()` on slice to create a `Vec`" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/iter_count.rs b/clippy_lints_methods/src/iter_count.rs index 6b64cc8b50ae..42d88c4c5a5d 100644 --- a/clippy_lints_methods/src/iter_count.rs +++ b/clippy_lints_methods/src/iter_count.rs @@ -7,7 +7,34 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::{Symbol, sym}; -use super::ITER_COUNT; +declare_clippy_lint! { + /// ### What it does + /// Checks for the use of `.iter().count()`. + /// + /// ### Why is this bad? + /// `.len()` is more efficient and more + /// readable. + /// + /// ### Example + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// + /// some_vec.iter().count(); + /// &some_vec[..].iter().count(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// + /// some_vec.len(); + /// &some_vec[..].len(); + /// ``` + #[clippy::version = "1.52.0"] + pub ITER_COUNT, + complexity, + "replace `.iter().count()` with `.len()`" +} pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, iter_method: Symbol) { let ty = cx.typeck_results().expr_ty(recv); diff --git a/clippy_lints_methods/src/iter_filter.rs b/clippy_lints_methods/src/iter_filter.rs index adeff375c8aa..7a209e1571d7 100644 --- a/clippy_lints_methods/src/iter_filter.rs +++ b/clippy_lints_methods/src/iter_filter.rs @@ -1,18 +1,63 @@ -use clippy_utils::ty::get_iterator_item_ty; -use hir::ExprKind; -use rustc_lint::{LateContext, LintContext}; - -use super::{ITER_FILTER_IS_OK, ITER_FILTER_IS_SOME}; - use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline}; +use clippy_utils::ty::get_iterator_item_ty; use clippy_utils::{get_parent_expr, is_trait_method, peel_blocks, span_contains_comment, sym}; +use hir::ExprKind; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::QPath; +use rustc_lint::{LateContext, LintContext}; use rustc_span::Span; use rustc_span::symbol::{Ident, Symbol}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.filter(Option::is_some)` that may be replaced with a `.flatten()` call. + /// This lint will require additional changes to the follow-up calls as it affects the type. + /// + /// ### Why is this bad? + /// This pattern is often followed by manual unwrapping of the `Option`. The simplification + /// results in more readable and succinct code without the need for manual unwrapping. + /// + /// ### Example + /// ```no_run + /// vec![Some(1)].into_iter().filter(Option::is_some); + /// + /// ``` + /// Use instead: + /// ```no_run + /// vec![Some(1)].into_iter().flatten(); + /// ``` + #[clippy::version = "1.77.0"] + pub ITER_FILTER_IS_SOME, + pedantic, + "filtering an iterator over `Option`s for `Some` can be achieved with `flatten`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.filter(Result::is_ok)` that may be replaced with a `.flatten()` call. + /// This lint will require additional changes to the follow-up calls as it affects the type. + /// + /// ### Why is this bad? + /// This pattern is often followed by manual unwrapping of `Result`. The simplification + /// results in more readable and succinct code without the need for manual unwrapping. + /// + /// ### Example + /// ```no_run + /// vec![Ok::(1)].into_iter().filter(Result::is_ok); + /// + /// ``` + /// Use instead: + /// ```no_run + /// vec![Ok::(1)].into_iter().flatten(); + /// ``` + #[clippy::version = "1.77.0"] + pub ITER_FILTER_IS_OK, + pedantic, + "filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`" +} + /// /// Returns true if the expression is a method call to `method_name` /// e.g. `a.method_name()` or `Option::method_name`. diff --git a/clippy_lints_methods/src/iter_kv_map.rs b/clippy_lints_methods/src/iter_kv_map.rs index cbb1b450e60f..6bf52b05ec54 100644 --- a/clippy_lints_methods/src/iter_kv_map.rs +++ b/clippy_lints_methods/src/iter_kv_map.rs @@ -1,4 +1,3 @@ -use super::ITER_KV_MAP; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; @@ -8,6 +7,37 @@ use rustc_hir::{Body, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::Symbol; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for iterating a map (`HashMap` or `BTreeMap`) and + /// ignoring either the keys or values. + /// + /// ### Why is this bad? + /// + /// Readability. There are `keys` and `values` methods that + /// can be used to express that we only need the keys or the values. + /// + /// ### Example + /// + /// ```no_run + /// # use std::collections::HashMap; + /// let map: HashMap = HashMap::new(); + /// let values = map.iter().map(|(_, value)| value).collect::>(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::collections::HashMap; + /// let map: HashMap = HashMap::new(); + /// let values = map.values().collect::>(); + /// ``` + #[clippy::version = "1.66.0"] + pub ITER_KV_MAP, + complexity, + "iterating on map using `iter` when `keys` or `values` would do" +} + /// lint use of: /// /// - `hashmap.iter().map(|(_, v)| v)` diff --git a/clippy_lints_methods/src/iter_next_slice.rs b/clippy_lints_methods/src/iter_next_slice.rs index fd4650e1e45f..95e8ec2c9f6f 100644 --- a/clippy_lints_methods/src/iter_next_slice.rs +++ b/clippy_lints_methods/src/iter_next_slice.rs @@ -10,7 +10,32 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::symbol::sym; -use super::ITER_NEXT_SLICE; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `iter().next()` on a Slice or an Array + /// + /// ### Why is this bad? + /// These can be shortened into `.get()` + /// + /// ### Example + /// ```no_run + /// # let a = [1, 2, 3]; + /// # let b = vec![1, 2, 3]; + /// a[2..].iter().next(); + /// b.iter().next(); + /// ``` + /// should be written as: + /// ```no_run + /// # let a = [1, 2, 3]; + /// # let b = vec![1, 2, 3]; + /// a.get(2); + /// b.get(0); + /// ``` + #[clippy::version = "1.46.0"] + pub ITER_NEXT_SLICE, + style, + "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, caller_expr: &'tcx hir::Expr<'_>) { // Skip lint if the `iter().next()` expression is a for loop argument, diff --git a/clippy_lints_methods/src/iter_nth.rs b/clippy_lints_methods/src/iter_nth.rs index 1fdbd81bf240..706f8e750fc7 100644 --- a/clippy_lints_methods/src/iter_nth.rs +++ b/clippy_lints_methods/src/iter_nth.rs @@ -6,7 +6,31 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; -use super::ITER_NTH; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.iter().nth()`/`.iter_mut().nth()` on standard library types that have + /// equivalent `.get()`/`.get_mut()` methods. + /// + /// ### Why is this bad? + /// `.get()` and `.get_mut()` are equivalent but more concise. + /// + /// ### Example + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().nth(3); + /// let bad_slice = &some_vec[..].iter().nth(3); + /// ``` + /// The correct use would be: + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.get(3); + /// let bad_slice = &some_vec[..].get(3); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub ITER_NTH, + style, + "using `.iter().nth()` on a standard library type with O(1) element access" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/iter_nth_zero.rs b/clippy_lints_methods/src/iter_nth_zero.rs index 4bdf589f4876..622f0249fe58 100644 --- a/clippy_lints_methods/src/iter_nth_zero.rs +++ b/clippy_lints_methods/src/iter_nth_zero.rs @@ -8,7 +8,35 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::ITER_NTH_ZERO; +declare_clippy_lint! { + /// ### What it does + /// Checks for the use of `iter.nth(0)`. + /// + /// ### Why is this bad? + /// `iter.next()` is equivalent to + /// `iter.nth(0)`, as they both consume the next element, + /// but is more readable. + /// + /// ### Example + /// ```no_run + /// # use std::collections::HashSet; + /// # let mut s = HashSet::new(); + /// # s.insert(1); + /// let x = s.iter().nth(0); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::collections::HashSet; + /// # let mut s = HashSet::new(); + /// # s.insert(1); + /// let x = s.iter().next(); + /// ``` + #[clippy::version = "1.42.0"] + pub ITER_NTH_ZERO, + style, + "replace `iter.nth(0)` with `iter.next()`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { if let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id)) diff --git a/clippy_lints_methods/src/iter_on_single_or_empty_collections.rs b/clippy_lints_methods/src/iter_on_single_or_empty_collections.rs index c0366765234f..3f7332040f22 100644 --- a/clippy_lints_methods/src/iter_on_single_or_empty_collections.rs +++ b/clippy_lints_methods/src/iter_on_single_or_empty_collections.rs @@ -12,7 +12,68 @@ use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; use rustc_span::Symbol; -use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS}; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for calls to `iter`, `iter_mut` or `into_iter` on collections containing a single item + /// + /// ### Why is this bad? + /// + /// It is simpler to use the once function from the standard library: + /// + /// ### Example + /// + /// ```no_run + /// let a = [123].iter(); + /// let b = Some(123).into_iter(); + /// ``` + /// Use instead: + /// ```no_run + /// use std::iter; + /// let a = iter::once(&123); + /// let b = iter::once(123); + /// ``` + /// + /// ### Known problems + /// + /// The type of the resulting iterator might become incompatible with its usage + #[clippy::version = "1.65.0"] + pub ITER_ON_SINGLE_ITEMS, + nursery, + "Iterator for array of length 1" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for calls to `iter`, `iter_mut` or `into_iter` on empty collections + /// + /// ### Why is this bad? + /// + /// It is simpler to use the empty function from the standard library: + /// + /// ### Example + /// + /// ```no_run + /// use std::{slice, option}; + /// let a: slice::Iter = [].iter(); + /// let f: option::IntoIter = None.into_iter(); + /// ``` + /// Use instead: + /// ```no_run + /// use std::iter; + /// let a: iter::Empty = iter::empty(); + /// let b: iter::Empty = iter::empty(); + /// ``` + /// + /// ### Known problems + /// + /// The type of the resulting iterator might become incompatible with its usage + #[clippy::version = "1.65.0"] + pub ITER_ON_EMPTY_COLLECTIONS, + nursery, + "Iterator for empty array" +} enum IterType { Iter, diff --git a/clippy_lints_methods/src/iter_out_of_bounds.rs b/clippy_lints_methods/src/iter_out_of_bounds.rs index 9a62b719a8fb..9a91a710d92d 100644 --- a/clippy_lints_methods/src/iter_out_of_bounds.rs +++ b/clippy_lints_methods/src/iter_out_of_bounds.rs @@ -7,7 +7,29 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self}; use rustc_span::sym; -use super::ITER_OUT_OF_BOUNDS; +declare_clippy_lint! { + /// ### What it does + /// Looks for iterator combinator calls such as `.take(x)` or `.skip(x)` + /// where `x` is greater than the amount of items that an iterator will produce. + /// + /// ### Why is this bad? + /// Taking or skipping more items than there are in an iterator either creates an iterator + /// with all items from the original iterator or an iterator with no items at all. + /// This is most likely not what the user intended to do. + /// + /// ### Example + /// ```no_run + /// for _ in [1, 2, 3].iter().take(4) {} + /// ``` + /// Use instead: + /// ```no_run + /// for _ in [1, 2, 3].iter() {} + /// ``` + #[clippy::version = "1.74.0"] + pub ITER_OUT_OF_BOUNDS, + suspicious, + "calls to `.take()` or `.skip()` that are out of bounds" +} fn expr_as_u128(cx: &LateContext<'_>, e: &Expr<'_>) -> Option { if let ExprKind::Lit(lit) = expr_or_init(cx, e).kind diff --git a/clippy_lints_methods/src/iter_overeager_cloned.rs b/clippy_lints_methods/src/iter_overeager_cloned.rs index d43b22c2bd51..09c2f00869b5 100644 --- a/clippy_lints_methods/src/iter_overeager_cloned.rs +++ b/clippy_lints_methods/src/iter_overeager_cloned.rs @@ -10,7 +10,61 @@ use rustc_middle::mir::{FakeReadCause, Mutability}; use rustc_middle::ty::{self, BorrowKind}; use rustc_span::{Symbol, sym}; -use super::{ITER_OVEREAGER_CLONED, REDUNDANT_ITER_CLONED}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.cloned().()` where call to `.cloned()` can be postponed. + /// + /// ### Why is this bad? + /// It's often inefficient to clone all elements of an iterator, when eventually, only some + /// of them will be consumed. + /// + /// ### Known Problems + /// This `lint` removes the side of effect of cloning items in the iterator. + /// A code that relies on that side-effect could fail. + /// + /// ### Examples + /// ```no_run + /// # let vec = vec!["string".to_string()]; + /// vec.iter().cloned().take(10); + /// vec.iter().cloned().last(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let vec = vec!["string".to_string()]; + /// vec.iter().take(10).cloned(); + /// vec.iter().last().cloned(); + /// ``` + #[clippy::version = "1.60.0"] + pub ITER_OVEREAGER_CLONED, + perf, + "using `cloned()` early with `Iterator::iter()` can lead to some performance inefficiencies" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `Iterator::cloned` where the original value could be used + /// instead. + /// + /// ### Why is this bad? + /// It is not always possible for the compiler to eliminate useless allocations and + /// deallocations generated by redundant `clone()`s. + /// + /// ### Example + /// ```no_run + /// let x = vec![String::new()]; + /// let _ = x.iter().cloned().map(|x| x.len()); + /// ``` + /// Use instead: + /// ```no_run + /// let x = vec![String::new()]; + /// let _ = x.iter().map(|x| x.len()); + /// ``` + #[clippy::version = "1.90.0"] + pub REDUNDANT_ITER_CLONED, + perf, + "detects redundant calls to `Iterator::cloned`" +} #[derive(Clone, Copy)] pub(super) enum Op<'a> { diff --git a/clippy_lints_methods/src/iter_skip_next.rs b/clippy_lints_methods/src/iter_skip_next.rs index fedb7c22eded..aaedb99552a6 100644 --- a/clippy_lints_methods/src/iter_skip_next.rs +++ b/clippy_lints_methods/src/iter_skip_next.rs @@ -7,7 +7,30 @@ use rustc_hir::{BindingMode, Node, PatKind}; use rustc_lint::LateContext; use rustc_span::sym; -use super::ITER_SKIP_NEXT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.skip(x).next()` on iterators. + /// + /// ### Why is this bad? + /// `.nth(x)` is cleaner + /// + /// ### Example + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().skip(3).next(); + /// let bad_slice = &some_vec[..].iter().skip(3).next(); + /// ``` + /// The correct use would be: + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().nth(3); + /// let bad_slice = &some_vec[..].iter().nth(3); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub ITER_SKIP_NEXT, + style, + "using `.skip(x).next()` on an iterator" +} pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { // lint if caller of skip is an Iterator diff --git a/clippy_lints_methods/src/iter_skip_zero.rs b/clippy_lints_methods/src/iter_skip_zero.rs index 39e440e784f6..770dd443b719 100644 --- a/clippy_lints_methods/src/iter_skip_zero.rs +++ b/clippy_lints_methods/src/iter_skip_zero.rs @@ -6,7 +6,26 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::sym; -use super::ITER_SKIP_ZERO; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.skip(0)` on iterators. + /// + /// ### Why is this bad? + /// This was likely intended to be `.skip(1)` to skip the first element, as `.skip(0)` does + /// nothing. If not, the call should be removed. + /// + /// ### Example + /// ```no_run + /// let v = vec![1, 2, 3]; + /// let x = v.iter().skip(0).collect::>(); + /// let y = v.iter().collect::>(); + /// assert_eq!(x, y); + /// ``` + #[clippy::version = "1.73.0"] + pub ITER_SKIP_ZERO, + correctness, + "disallows `.skip(0)`" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg_expr: &Expr<'_>) { if !expr.span.from_expansion() diff --git a/clippy_lints_methods/src/iter_with_drain.rs b/clippy_lints_methods/src/iter_with_drain.rs index aa45969c8982..18c8d9e4b188 100644 --- a/clippy_lints_methods/src/iter_with_drain.rs +++ b/clippy_lints_methods/src/iter_with_drain.rs @@ -6,7 +6,30 @@ use rustc_lint::LateContext; use rustc_span::Span; use rustc_span::symbol::sym; -use super::ITER_WITH_DRAIN; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.drain(..)` on `Vec` and `VecDeque` for iteration. + /// + /// ### Why is this bad? + /// `.into_iter()` is simpler with better performance. + /// + /// ### Example + /// ```no_run + /// # use std::collections::HashSet; + /// let mut foo = vec![0, 1, 2, 3]; + /// let bar: HashSet = foo.drain(..).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// # use std::collections::HashSet; + /// let foo = vec![0, 1, 2, 3]; + /// let bar: HashSet = foo.into_iter().collect(); + /// ``` + #[clippy::version = "1.61.0"] + pub ITER_WITH_DRAIN, + nursery, + "replace `.drain(..)` with `.into_iter()`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) { if !matches!(recv.kind, ExprKind::Field(..)) diff --git a/clippy_lints_methods/src/iterator_step_by_zero.rs b/clippy_lints_methods/src/iterator_step_by_zero.rs index 90d5d9df55ee..7f72a3d8341f 100644 --- a/clippy_lints_methods/src/iterator_step_by_zero.rs +++ b/clippy_lints_methods/src/iterator_step_by_zero.rs @@ -5,7 +5,25 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::ITERATOR_STEP_BY_ZERO; +declare_clippy_lint! { + /// ### What it does + /// Checks for calling `.step_by(0)` on iterators which panics. + /// + /// ### Why is this bad? + /// This very much looks like an oversight. Use `panic!()` instead if you + /// actually intend to panic. + /// + /// ### Example + /// ```rust,should_panic + /// for x in (0..100).step_by(0) { + /// //.. + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub ITERATOR_STEP_BY_ZERO, + correctness, + "using `Iterator::step_by(0)`, which will panic at runtime" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) { if is_trait_method(cx, expr, sym::Iterator) diff --git a/clippy_lints_methods/src/join_absolute_paths.rs b/clippy_lints_methods/src/join_absolute_paths.rs index 2ad070793cbb..57092542f586 100644 --- a/clippy_lints_methods/src/join_absolute_paths.rs +++ b/clippy_lints_methods/src/join_absolute_paths.rs @@ -9,7 +9,45 @@ use rustc_lint::LateContext; use rustc_span::Span; use rustc_span::symbol::sym; -use super::JOIN_ABSOLUTE_PATHS; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `Path::join` that start with a path separator (`\\` or `/`). + /// + /// ### Why is this bad? + /// If the argument to `Path::join` starts with a separator, it will overwrite + /// the original path. If this is intentional, prefer using `Path::new` instead. + /// + /// Note the behavior is platform dependent. A leading `\\` will be accepted + /// on unix systems as part of the file name + /// + /// See [`Path::join`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join) + /// + /// ### Example + /// ```rust + /// # use std::path::{Path, PathBuf}; + /// let path = Path::new("/bin"); + /// let joined_path = path.join("/sh"); + /// assert_eq!(joined_path, PathBuf::from("/sh")); + /// ``` + /// + /// Use instead; + /// ```rust + /// # use std::path::{Path, PathBuf}; + /// let path = Path::new("/bin"); + /// + /// // If this was unintentional, remove the leading separator + /// let joined_path = path.join("sh"); + /// assert_eq!(joined_path, PathBuf::from("/bin/sh")); + /// + /// // If this was intentional, create a new path instead + /// let new = Path::new("/sh"); + /// assert_eq!(new, PathBuf::from("/sh")); + /// ``` + #[clippy::version = "1.76.0"] + pub JOIN_ABSOLUTE_PATHS, + suspicious, + "calls to `Path::join` which will overwrite the original path" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>, expr_span: Span) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); diff --git a/clippy_lints_methods/src/lib.rs b/clippy_lints_methods/src/lib.rs index d93e0d5a5781..a9308a469dd0 100644 --- a/clippy_lints_methods/src/lib.rs +++ b/clippy_lints_methods/src/lib.rs @@ -40,20 +40,17 @@ extern crate declare_clippy_lint; pub mod declared_lints; -// begin lints modules, do not remove this comment, it's used in `update_lints` -// end lints modules, do not remove this comment, it's used in `update_lints` +mod option_map_unwrap_or; +mod result_map_or_else_none; +mod utils; +// begin lints modules, do not remove this comment, it's used in `update_lints` mod bind_instead_of_map; mod bytecount; mod bytes_count_to_len; mod bytes_nth; mod case_sensitive_file_extension_comparisons; mod chars_cmp; -mod chars_cmp_with_unwrap; -mod chars_last_cmp; -mod chars_last_cmp_with_unwrap; -mod chars_next_cmp; -mod chars_next_cmp_with_unwrap; mod clear_with_drain; mod clone_on_copy; mod clone_on_ref_ptr; @@ -131,7 +128,6 @@ mod open_options; mod option_as_ref_cloned; mod option_as_ref_deref; mod option_map_or_none; -mod option_map_unwrap_or; mod or_fun_call; mod or_then_unwrap; mod path_buf_push_overwrite; @@ -141,14 +137,11 @@ mod read_line_without_trim; mod readonly_write_lock; mod redundant_as_str; mod repeat_once; -mod result_map_or_else_none; mod return_and_then; mod search_is_some; mod seek_from_current; mod seek_to_start_instead_of_rewind; mod single_char_add_str; -mod single_char_insert_string; -mod single_char_push_string; mod skip_while_next; mod sliced_string_as_bytes; mod stable_sort_primitive; @@ -170,7 +163,6 @@ mod unnecessary_filter_map; mod unnecessary_first_then_check; mod unnecessary_fold; mod unnecessary_get_then_check; -mod unnecessary_iter_cloned; mod unnecessary_join; mod unnecessary_lazy_eval; mod unnecessary_literal_unwrap; @@ -182,12 +174,12 @@ mod unnecessary_to_owned; mod unwrap_expect_used; mod useless_asref; mod useless_nonzero_new_unchecked; -mod utils; mod vec_resize_to_zero; mod verbose_file_reads; mod waker_clone_wake; mod wrong_self_convention; mod zst_offset; +// end lints modules, do not remove this comment, it's used in `update_lints` use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; @@ -206,226 +198,6 @@ use rustc_middle::ty::{self, TraitRef, Ty}; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol, kw}; -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `cloned()` on an `Iterator` or `Option` where - /// `copied()` could be used instead. - /// - /// ### Why is this bad? - /// `copied()` is better because it guarantees that the type being cloned - /// implements `Copy`. - /// - /// ### Example - /// ```no_run - /// [1, 2, 3].iter().cloned(); - /// ``` - /// Use instead: - /// ```no_run - /// [1, 2, 3].iter().copied(); - /// ``` - #[clippy::version = "1.53.0"] - pub CLONED_INSTEAD_OF_COPIED, - pedantic, - "used `cloned` where `copied` could be used instead" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for consecutive calls to `str::replace` (2 or more) - /// that can be collapsed into a single call. - /// - /// ### Why is this bad? - /// Consecutive `str::replace` calls scan the string multiple times - /// with repetitive code. - /// - /// ### Example - /// ```no_run - /// let hello = "hesuo worpd" - /// .replace('s', "l") - /// .replace("u", "l") - /// .replace('p', "l"); - /// ``` - /// Use instead: - /// ```no_run - /// let hello = "hesuo worpd".replace(['s', 'u', 'p'], "l"); - /// ``` - #[clippy::version = "1.65.0"] - pub COLLAPSIBLE_STR_REPLACE, - perf, - "collapse consecutive calls to str::replace (2 or more) into a single call" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.cloned().()` where call to `.cloned()` can be postponed. - /// - /// ### Why is this bad? - /// It's often inefficient to clone all elements of an iterator, when eventually, only some - /// of them will be consumed. - /// - /// ### Known Problems - /// This `lint` removes the side of effect of cloning items in the iterator. - /// A code that relies on that side-effect could fail. - /// - /// ### Examples - /// ```no_run - /// # let vec = vec!["string".to_string()]; - /// vec.iter().cloned().take(10); - /// vec.iter().cloned().last(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let vec = vec!["string".to_string()]; - /// vec.iter().take(10).cloned(); - /// vec.iter().last().cloned(); - /// ``` - #[clippy::version = "1.60.0"] - pub ITER_OVEREAGER_CLONED, - perf, - "using `cloned()` early with `Iterator::iter()` can lead to some performance inefficiencies" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `Iterator::flat_map()` where `filter_map()` could be - /// used instead. - /// - /// ### Why is this bad? - /// `filter_map()` is known to always produce 0 or 1 output items per input item, - /// rather than however many the inner iterator type produces. - /// Therefore, it maintains the upper bound in `Iterator::size_hint()`, - /// and communicates to the reader that the input items are not being expanded into - /// multiple output items without their having to notice that the mapping function - /// returns an `Option`. - /// - /// ### Example - /// ```no_run - /// let nums: Vec = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect(); - /// ``` - /// Use instead: - /// ```no_run - /// let nums: Vec = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect(); - /// ``` - #[clippy::version = "1.53.0"] - pub FLAT_MAP_OPTION, - pedantic, - "used `flat_map` where `filter_map` could be used instead" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.unwrap()` or `.unwrap_err()` calls on `Result`s and `.unwrap()` call on `Option`s. - /// - /// ### Why restrict this? - /// It is better to handle the `None` or `Err` case, - /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of - /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is - /// `Allow` by default. - /// - /// `result.unwrap()` will let the thread panic on `Err` values. - /// Normally, you want to implement more sophisticated error handling, - /// and propagate errors upwards with `?` operator. - /// - /// Even if you want to panic on errors, not all `Error`s implement good - /// messages on display. Therefore, it may be beneficial to look at the places - /// where they may get displayed. Activate this lint to do just that. - /// - /// ### Examples - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option.unwrap(); - /// result.unwrap(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option.expect("more helpful message"); - /// result.expect("more helpful message"); - /// ``` - /// - /// If [expect_used](#expect_used) is enabled, instead: - /// ```rust,ignore - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option?; - /// - /// // or - /// - /// result?; - /// ``` - #[clippy::version = "1.45.0"] - pub UNWRAP_USED, - restriction, - "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.unwrap()` related calls on `Result`s and `Option`s that are constructed. - /// - /// ### Why is this bad? - /// It is better to write the value directly without the indirection. - /// - /// ### Examples - /// ```no_run - /// let val1 = Some(1).unwrap(); - /// let val2 = Ok::<_, ()>(1).unwrap(); - /// let val3 = Err::<(), _>(1).unwrap_err(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let val1 = 1; - /// let val2 = 1; - /// let val3 = 1; - /// ``` - #[clippy::version = "1.72.0"] - pub UNNECESSARY_LITERAL_UNWRAP, - complexity, - "using `unwrap()` related calls on `Result` and `Option` constructors" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s. - /// - /// ### Why restrict this? - /// Usually it is better to handle the `None` or `Err` case. - /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why - /// this lint is `Allow` by default. - /// - /// `result.expect()` will let the thread panic on `Err` - /// values. Normally, you want to implement more sophisticated error handling, - /// and propagate errors upwards with `?` operator. - /// - /// ### Examples - /// ```rust,ignore - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option.expect("one"); - /// result.expect("one"); - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option?; - /// - /// // or - /// - /// result?; - /// ``` - #[clippy::version = "1.45.0"] - pub EXPECT_USED, - restriction, - "using `.expect()` on `Result` or `Option`, which might be better handled" -} - declare_clippy_lint! { /// ### What it does /// Checks for methods that should live in a trait @@ -458,4182 +230,66 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for methods with certain name prefixes or suffixes, and which - /// do not adhere to standard conventions regarding how `self` is taken. - /// The actual rules are: - /// - /// |Prefix |Postfix |`self` taken | `self` type | - /// |-------|------------|-------------------------------|--------------| - /// |`as_` | none |`&self` or `&mut self` | any | - /// |`from_`| none | none | any | - /// |`into_`| none |`self` | any | - /// |`is_` | none |`&mut self` or `&self` or none | any | - /// |`to_` | `_mut` |`&mut self` | any | - /// |`to_` | not `_mut` |`self` | `Copy` | - /// |`to_` | not `_mut` |`&self` | not `Copy` | - /// - /// Note: Clippy doesn't trigger methods with `to_` prefix in: - /// - Traits definition. - /// Clippy can not tell if a type that implements a trait is `Copy` or not. - /// - Traits implementation, when `&self` is taken. - /// The method signature is controlled by the trait and often `&self` is required for all types that implement the trait - /// (see e.g. the `std::string::ToString` trait). - /// - /// Clippy allows `Pin<&Self>` and `Pin<&mut Self>` if `&self` and `&mut self` is required. - /// - /// Please find more info here: - /// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv + /// Checks for `new` not returning a type that contains `Self`. /// /// ### Why is this bad? - /// Consistency breeds readability. If you follow the - /// conventions, your users won't be surprised that they, e.g., need to supply a - /// mutable reference to a `as_..` function. + /// As a convention, `new` methods are used to make a new + /// instance of a type. /// /// ### Example + /// In an impl block: /// ```no_run - /// # struct X; - /// impl X { - /// fn as_str(self) -> &'static str { - /// // .. - /// # "" + /// # struct Foo; + /// # struct NotAFoo; + /// impl Foo { + /// fn new() -> NotAFoo { + /// # NotAFoo /// } /// } /// ``` /// - /// Use instead: /// ```no_run - /// # struct X; - /// impl X { - /// fn as_str(&self) -> &'static str { - /// // .. - /// # "" + /// # struct Foo; + /// struct Bar(Foo); + /// impl Foo { + /// // Bad. The type name must contain `Self` + /// fn new() -> Bar { + /// # Bar(Foo) /// } /// } /// ``` - #[clippy::version = "pre 1.29.0"] - pub WRONG_SELF_CONVENTION, - style, - "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `ok().expect(..)`. - /// - /// ### Why is this bad? - /// Because you usually call `expect()` on the `Result` - /// directly to get a better error message. - /// - /// ### Example - /// ```no_run - /// # let x = Ok::<_, ()>(()); - /// x.ok().expect("why did I do this again?"); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let x = Ok::<_, ()>(()); - /// x.expect("why did I do this again?"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub OK_EXPECT, - style, - "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.err().expect()` calls on the `Result` type. - /// - /// ### Why is this bad? - /// `.expect_err()` can be called directly to avoid the extra type conversion from `err()`. - /// - /// ### Example - /// ```should_panic - /// let x: Result = Ok(10); - /// x.err().expect("Testing err().expect()"); - /// ``` - /// Use instead: - /// ```should_panic - /// let x: Result = Ok(10); - /// x.expect_err("Testing expect_err"); - /// ``` - #[clippy::version = "1.62.0"] - pub ERR_EXPECT, - style, - r#"using `.err().expect("")` when `.expect_err("")` can be used"# -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usages of the following functions with an argument that constructs a default value - /// (e.g., `Default::default` or `String::new`): - /// - `unwrap_or` - /// - `unwrap_or_else` - /// - `or_insert` - /// - `or_insert_with` - /// - /// ### Why is this bad? - /// Readability. Using `unwrap_or_default` in place of `unwrap_or`/`unwrap_or_else`, or `or_default` - /// in place of `or_insert`/`or_insert_with`, is simpler and more concise. - /// - /// ### Known problems - /// In some cases, the argument of `unwrap_or`, etc. is needed for type inference. The lint uses a - /// heuristic to try to identify such cases. However, the heuristic can produce false negatives. - /// - /// ### Examples - /// ```no_run - /// # let x = Some(1); - /// # let mut map = std::collections::HashMap::::new(); - /// x.unwrap_or(Default::default()); - /// map.entry(42).or_insert_with(String::new); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let x = Some(1); - /// # let mut map = std::collections::HashMap::::new(); - /// x.unwrap_or_default(); - /// map.entry(42).or_default(); - /// ``` - #[clippy::version = "1.56.0"] - pub UNWRAP_OR_DEFAULT, - style, - "using `.unwrap_or`, etc. with an argument that constructs a default value" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or - /// `result.map(_).unwrap_or_else(_)`. - /// - /// ### Why is this bad? - /// Readability, these can be written more concisely (resp.) as - /// `option.map_or(_, _)`, `option.map_or_else(_, _)` and `result.map_or_else(_, _)`. - /// - /// ### Known problems - /// The order of the arguments is not in execution order - /// - /// ### Examples - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// # fn some_function(foo: ()) -> usize { 1 } - /// option.map(|a| a + 1).unwrap_or(0); - /// option.map(|a| a > 10).unwrap_or(false); - /// result.map(|a| a + 1).unwrap_or_else(some_function); - /// ``` /// - /// Use instead: /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// # fn some_function(foo: ()) -> usize { 1 } - /// option.map_or(0, |a| a + 1); - /// option.is_some_and(|a| a > 10); - /// result.map_or_else(some_function, |a| a + 1); + /// # struct Foo; + /// # struct FooError; + /// impl Foo { + /// // Good. Return type contains `Self` + /// fn new() -> Result { + /// # Ok(Foo) + /// } + /// } /// ``` - #[clippy::version = "1.45.0"] - pub MAP_UNWRAP_OR, - pedantic, - "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.map_or(None, _)`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.and_then(_)`. - /// - /// ### Known problems - /// The order of the arguments is not in execution order. /// - /// ### Example + /// Or in a trait definition: /// ```no_run - /// # let opt = Some(1); - /// opt.map_or(None, |a| Some(a + 1)); + /// pub trait Trait { + /// // Bad. The type name must contain `Self` + /// fn new(); + /// } /// ``` /// - /// Use instead: /// ```no_run - /// # let opt = Some(1); - /// opt.and_then(|a| Some(a + 1)); + /// pub trait Trait { + /// // Good. Return type contains `Self` + /// fn new() -> Self; + /// } /// ``` #[clippy::version = "pre 1.29.0"] - pub OPTION_MAP_OR_NONE, - style, - "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.map_or(None, Some)`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.ok()`. - /// - /// ### Example - /// ```no_run - /// # let r: Result = Ok(1); - /// assert_eq!(Some(1), r.map_or(None, Some)); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let r: Result = Ok(1); - /// assert_eq!(Some(1), r.ok()); - /// ``` - #[clippy::version = "1.44.0"] - pub RESULT_MAP_OR_INTO_OPTION, - style, - "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` - /// or `_.or_else(|x| Err(y))`. - /// - /// ### Why is this bad? - /// This can be written more concisely as `_.map(|x| y)` or `_.map_err(|x| y)`. - /// - /// ### Example - /// ```no_run - /// # fn opt() -> Option<&'static str> { Some("42") } - /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } - /// let _ = opt().and_then(|s| Some(s.len())); - /// let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) }); - /// let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) }); - /// ``` - /// - /// The correct use would be: - /// - /// ```no_run - /// # fn opt() -> Option<&'static str> { Some("42") } - /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } - /// let _ = opt().map(|s| s.len()); - /// let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 }); - /// let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 }); - /// ``` - #[clippy::version = "1.45.0"] - pub BIND_INSTEAD_OF_MAP, - complexity, - "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.filter(_).next()`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.find(_)`. - /// - /// ### Example - /// ```no_run - /// # let vec = vec![1]; - /// vec.iter().filter(|x| **x == 0).next(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let vec = vec![1]; - /// vec.iter().find(|x| **x == 0); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FILTER_NEXT, - complexity, - "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.skip_while(condition).next()`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.find(!condition)`. - /// - /// ### Example - /// ```no_run - /// # let vec = vec![1]; - /// vec.iter().skip_while(|x| **x == 0).next(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let vec = vec![1]; - /// vec.iter().find(|x| **x != 0); - /// ``` - #[clippy::version = "1.42.0"] - pub SKIP_WHILE_NEXT, - complexity, - "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option` - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option` - /// - /// ### Example - /// ```no_run - /// let vec = vec![vec![1]]; - /// let opt = Some(5); - /// - /// vec.iter().map(|x| x.iter()).flatten(); - /// opt.map(|x| Some(x * 2)).flatten(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let vec = vec![vec![1]]; - /// # let opt = Some(5); - /// vec.iter().flat_map(|x| x.iter()); - /// opt.and_then(|x| Some(x * 2)); - /// ``` - #[clippy::version = "1.31.0"] - pub MAP_FLATTEN, - complexity, - "using combinations of `flatten` and `map` which can usually be written as a single method call" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.filter(_).map(_)` that can be written more simply - /// as `filter_map(_)`. - /// - /// ### Why is this bad? - /// Redundant code in the `filter` and `map` operations is poor style and - /// less performant. - /// - /// ### Example - /// ```no_run - /// (0_i32..10) - /// .filter(|n| n.checked_add(1).is_some()) - /// .map(|n| n.checked_add(1).unwrap()); - /// ``` - /// - /// Use instead: - /// ```no_run - /// (0_i32..10).filter_map(|n| n.checked_add(1)); - /// ``` - #[clippy::version = "1.51.0"] - pub MANUAL_FILTER_MAP, - complexity, - "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.find(_).map(_)` that can be written more simply - /// as `find_map(_)`. - /// - /// ### Why is this bad? - /// Redundant code in the `find` and `map` operations is poor style and - /// less performant. - /// - /// ### Example - /// ```no_run - /// (0_i32..10) - /// .find(|n| n.checked_add(1).is_some()) - /// .map(|n| n.checked_add(1).unwrap()); - /// ``` - /// - /// Use instead: - /// ```no_run - /// (0_i32..10).find_map(|n| n.checked_add(1)); - /// ``` - #[clippy::version = "1.51.0"] - pub MANUAL_FIND_MAP, - complexity, - "using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.filter_map(_).next()`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.find_map(_)`. - /// - /// ### Example - /// ```no_run - /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next(); - /// ``` - /// Can be written as - /// - /// ```no_run - /// (0..3).find_map(|x| if x == 2 { Some(x) } else { None }); - /// ``` - #[clippy::version = "1.36.0"] - pub FILTER_MAP_NEXT, - pedantic, - "using combination of `filter_map` and `next` which can usually be written as a single method call" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `flat_map(|x| x)`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely by using `flatten`. - /// - /// ### Example - /// ```no_run - /// # let iter = vec![vec![0]].into_iter(); - /// iter.flat_map(|x| x); - /// ``` - /// Can be written as - /// ```no_run - /// # let iter = vec![vec![0]].into_iter(); - /// iter.flatten(); - /// ``` - #[clippy::version = "1.39.0"] - pub FLAT_MAP_IDENTITY, - complexity, - "call to `flat_map` where `flatten` is sufficient" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for an iterator or string search (such as `find()`, - /// `position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as: - /// * `_.any(_)`, or `_.contains(_)` for `is_some()`, - /// * `!_.any(_)`, or `!_.contains(_)` for `is_none()`. - /// - /// ### Example - /// ```no_run - /// let vec = vec![1]; - /// vec.iter().find(|x| **x == 0).is_some(); - /// - /// "hello world".find("world").is_none(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let vec = vec![1]; - /// vec.iter().any(|x| *x == 0); - /// - /// !"hello world".contains("world"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub SEARCH_IS_SOME, - complexity, - "using an iterator or string search followed by `is_some()` or `is_none()`, which is more succinctly expressed as a call to `any()` or `contains()` (with negation in case of `is_none()`)" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.chars().next()` on a `str` to check - /// if it starts with a given char. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.starts_with(_)`. - /// - /// ### Example - /// ```no_run - /// let name = "foo"; - /// if name.chars().next() == Some('_') {}; - /// ``` - /// - /// Use instead: - /// ```no_run - /// let name = "foo"; - /// if name.starts_with('_') {}; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CHARS_NEXT_CMP, - style, - "using `.chars().next()` to check if a string starts with a char" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`, - /// `.or_insert(foo(..))` etc., and suggests to use `.or_else(|| foo(..))`, - /// `.unwrap_or_else(|| foo(..))`, `.unwrap_or_default()` or `.or_default()` - /// etc. instead. - /// - /// ### Why is this bad? - /// The function will always be called. This is only bad if it allocates or - /// does some non-trivial amount of work. - /// - /// ### Known problems - /// If the function has side-effects, not calling it will change the - /// semantic of the program, but you shouldn't rely on that. - /// - /// The lint also cannot figure out whether the function you call is - /// actually expensive to call or not. - /// - /// ### Example - /// ```no_run - /// # let foo = Some(String::new()); - /// foo.unwrap_or(String::from("empty")); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let foo = Some(String::new()); - /// foo.unwrap_or_else(|| String::from("empty")); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub OR_FUN_CALL, - nursery, - "using any `*or` method with a function call, which suggests `*or_else`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.or(…).unwrap()` calls to Options and Results. - /// - /// ### Why is this bad? - /// You should use `.unwrap_or(…)` instead for clarity. - /// - /// ### Example - /// ```no_run - /// # let fallback = "fallback"; - /// // Result - /// # type Error = &'static str; - /// # let result: Result<&str, Error> = Err("error"); - /// let value = result.or::(Ok(fallback)).unwrap(); - /// - /// // Option - /// # let option: Option<&str> = None; - /// let value = option.or(Some(fallback)).unwrap(); - /// ``` - /// Use instead: - /// ```no_run - /// # let fallback = "fallback"; - /// // Result - /// # let result: Result<&str, &str> = Err("error"); - /// let value = result.unwrap_or(fallback); - /// - /// // Option - /// # let option: Option<&str> = None; - /// let value = option.unwrap_or(fallback); - /// ``` - #[clippy::version = "1.61.0"] - pub OR_THEN_UNWRAP, - complexity, - "checks for `.or(…).unwrap()` calls to Options and Results." -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`, - /// etc., and suggests to use `unwrap_or_else` instead - /// - /// ### Why is this bad? - /// The function will always be called. - /// - /// ### Known problems - /// If the function has side-effects, not calling it will - /// change the semantics of the program, but you shouldn't rely on that anyway. - /// - /// ### Example - /// ```no_run - /// # let foo = Some(String::new()); - /// # let err_code = "418"; - /// # let err_msg = "I'm a teapot"; - /// foo.expect(&format!("Err {}: {}", err_code, err_msg)); - /// - /// // or - /// - /// # let foo = Some(String::new()); - /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str()); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let foo = Some(String::new()); - /// # let err_code = "418"; - /// # let err_msg = "I'm a teapot"; - /// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg)); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub EXPECT_FUN_CALL, - perf, - "using any `expect` method with a function call" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.clone()` on a `Copy` type. - /// - /// ### Why is this bad? - /// The only reason `Copy` types implement `Clone` is for - /// generics, not for using the `clone` method on a concrete type. - /// - /// ### Example - /// ```no_run - /// 42u64.clone(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CLONE_ON_COPY, - complexity, - "using `clone` on a `Copy` type" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.clone()` on a ref-counted pointer, - /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified - /// function syntax instead (e.g., `Rc::clone(foo)`). - /// - /// ### Why restrict this? - /// Calling `.clone()` on an `Rc`, `Arc`, or `Weak` - /// can obscure the fact that only the pointer is being cloned, not the underlying - /// data. - /// - /// ### Example - /// ```no_run - /// # use std::rc::Rc; - /// let x = Rc::new(1); - /// - /// x.clone(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::rc::Rc; - /// # let x = Rc::new(1); - /// Rc::clone(&x); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CLONE_ON_REF_PTR, - restriction, - "using `clone` on a ref-counted pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.to_string()` on an `&&T` where - /// `T` implements `ToString` directly (like `&&str` or `&&String`). - /// - /// ### Why is this bad? - /// This bypasses the specialized implementation of - /// `ToString` and instead goes through the more expensive string formatting - /// facilities. - /// - /// ### Example - /// ```no_run - /// // Generic implementation for `T: Display` is used (slow) - /// ["foo", "bar"].iter().map(|s| s.to_string()); - /// - /// // OK, the specialized impl is used - /// ["foo", "bar"].iter().map(|&s| s.to_string()); - /// ``` - #[clippy::version = "1.40.0"] - pub INEFFICIENT_TO_STRING, - pedantic, - "using `to_string` on `&&T` where `T: ToString`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `new` not returning a type that contains `Self`. - /// - /// ### Why is this bad? - /// As a convention, `new` methods are used to make a new - /// instance of a type. - /// - /// ### Example - /// In an impl block: - /// ```no_run - /// # struct Foo; - /// # struct NotAFoo; - /// impl Foo { - /// fn new() -> NotAFoo { - /// # NotAFoo - /// } - /// } - /// ``` - /// - /// ```no_run - /// # struct Foo; - /// struct Bar(Foo); - /// impl Foo { - /// // Bad. The type name must contain `Self` - /// fn new() -> Bar { - /// # Bar(Foo) - /// } - /// } - /// ``` - /// - /// ```no_run - /// # struct Foo; - /// # struct FooError; - /// impl Foo { - /// // Good. Return type contains `Self` - /// fn new() -> Result { - /// # Ok(Foo) - /// } - /// } - /// ``` - /// - /// Or in a trait definition: - /// ```no_run - /// pub trait Trait { - /// // Bad. The type name must contain `Self` - /// fn new(); - /// } - /// ``` - /// - /// ```no_run - /// pub trait Trait { - /// // Good. Return type contains `Self` - /// fn new() -> Self; - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NEW_RET_NO_SELF, + pub NEW_RET_NO_SELF, style, "not returning type containing `Self` in a `new` method" } -declare_clippy_lint! { - /// ### What it does - /// Checks for calling `.step_by(0)` on iterators which panics. - /// - /// ### Why is this bad? - /// This very much looks like an oversight. Use `panic!()` instead if you - /// actually intend to panic. - /// - /// ### Example - /// ```rust,should_panic - /// for x in (0..100).step_by(0) { - /// //.. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ITERATOR_STEP_BY_ZERO, - correctness, - "using `Iterator::step_by(0)`, which will panic at runtime" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for iterators of `Option`s using `.filter(Option::is_some).map(Option::unwrap)` that may - /// be replaced with a `.flatten()` call. - /// - /// ### Why is this bad? - /// `Option` is like a collection of 0-1 things, so `flatten` - /// automatically does this without suspicious-looking `unwrap` calls. - /// - /// ### Example - /// ```no_run - /// let _ = std::iter::empty::>().filter(Option::is_some).map(Option::unwrap); - /// ``` - /// Use instead: - /// ```no_run - /// let _ = std::iter::empty::>().flatten(); - /// ``` - #[clippy::version = "1.53.0"] - pub OPTION_FILTER_MAP, - complexity, - "filtering `Option` for `Some` then force-unwrapping, which can be one type-safe operation" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the use of `iter.nth(0)`. - /// - /// ### Why is this bad? - /// `iter.next()` is equivalent to - /// `iter.nth(0)`, as they both consume the next element, - /// but is more readable. - /// - /// ### Example - /// ```no_run - /// # use std::collections::HashSet; - /// # let mut s = HashSet::new(); - /// # s.insert(1); - /// let x = s.iter().nth(0); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::collections::HashSet; - /// # let mut s = HashSet::new(); - /// # s.insert(1); - /// let x = s.iter().next(); - /// ``` - #[clippy::version = "1.42.0"] - pub ITER_NTH_ZERO, - style, - "replace `iter.nth(0)` with `iter.next()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.iter().nth()`/`.iter_mut().nth()` on standard library types that have - /// equivalent `.get()`/`.get_mut()` methods. - /// - /// ### Why is this bad? - /// `.get()` and `.get_mut()` are equivalent but more concise. - /// - /// ### Example - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// let bad_vec = some_vec.iter().nth(3); - /// let bad_slice = &some_vec[..].iter().nth(3); - /// ``` - /// The correct use would be: - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// let bad_vec = some_vec.get(3); - /// let bad_slice = &some_vec[..].get(3); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ITER_NTH, - style, - "using `.iter().nth()` on a standard library type with O(1) element access" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.skip(x).next()` on iterators. - /// - /// ### Why is this bad? - /// `.nth(x)` is cleaner - /// - /// ### Example - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// let bad_vec = some_vec.iter().skip(3).next(); - /// let bad_slice = &some_vec[..].iter().skip(3).next(); - /// ``` - /// The correct use would be: - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// let bad_vec = some_vec.iter().nth(3); - /// let bad_slice = &some_vec[..].iter().nth(3); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ITER_SKIP_NEXT, - style, - "using `.skip(x).next()` on an iterator" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.drain(..)` on `Vec` and `VecDeque` for iteration. - /// - /// ### Why is this bad? - /// `.into_iter()` is simpler with better performance. - /// - /// ### Example - /// ```no_run - /// # use std::collections::HashSet; - /// let mut foo = vec![0, 1, 2, 3]; - /// let bar: HashSet = foo.drain(..).collect(); - /// ``` - /// Use instead: - /// ```no_run - /// # use std::collections::HashSet; - /// let foo = vec![0, 1, 2, 3]; - /// let bar: HashSet = foo.into_iter().collect(); - /// ``` - #[clippy::version = "1.61.0"] - pub ITER_WITH_DRAIN, - nursery, - "replace `.drain(..)` with `.into_iter()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `x.get(x.len() - 1)` instead of - /// `x.last()`. - /// - /// ### Why is this bad? - /// Using `x.last()` is easier to read and has the same - /// result. - /// - /// Note that using `x[x.len() - 1]` is semantically different from - /// `x.last()`. Indexing into the array will panic on out-of-bounds - /// accesses, while `x.get()` and `x.last()` will return `None`. - /// - /// There is another lint (get_unwrap) that covers the case of using - /// `x.get(index).unwrap()` instead of `x[index]`. - /// - /// ### Example - /// ```no_run - /// let x = vec![2, 3, 5]; - /// let last_element = x.get(x.len() - 1); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let x = vec![2, 3, 5]; - /// let last_element = x.last(); - /// ``` - #[clippy::version = "1.37.0"] - pub GET_LAST_WITH_LEN, - complexity, - "Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.get().unwrap()` (or - /// `.get_mut().unwrap`) on a standard library type which implements `Index` - /// - /// ### Why restrict this? - /// Using the Index trait (`[]`) is more clear and more - /// concise. - /// - /// ### Known problems - /// Not a replacement for error handling: Using either - /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic` - /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a - /// temporary placeholder for dealing with the `Option` type, then this does - /// not mitigate the need for error handling. If there is a chance that `.get()` - /// will be `None` in your program, then it is advisable that the `None` case - /// is handled in a future refactor instead of using `.unwrap()` or the Index - /// trait. - /// - /// ### Example - /// ```no_run - /// let mut some_vec = vec![0, 1, 2, 3]; - /// let last = some_vec.get(3).unwrap(); - /// *some_vec.get_mut(0).unwrap() = 1; - /// ``` - /// The correct use would be: - /// ```no_run - /// let mut some_vec = vec![0, 1, 2, 3]; - /// let last = some_vec[3]; - /// some_vec[0] = 1; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub GET_UNWRAP, - restriction, - "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for occurrences where one vector gets extended instead of append - /// - /// ### Why is this bad? - /// Using `append` instead of `extend` is more concise and faster - /// - /// ### Example - /// ```no_run - /// let mut a = vec![1, 2, 3]; - /// let mut b = vec![4, 5, 6]; - /// - /// a.extend(b.drain(..)); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let mut a = vec![1, 2, 3]; - /// let mut b = vec![4, 5, 6]; - /// - /// a.append(&mut b); - /// ``` - #[clippy::version = "1.55.0"] - pub EXTEND_WITH_DRAIN, - perf, - "using vec.append(&mut vec) to move the full range of a vector to another" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the use of `.extend(s.chars())` where s is a - /// `&str` or `String`. - /// - /// ### Why is this bad? - /// `.push_str(s)` is clearer - /// - /// ### Example - /// ```no_run - /// let abc = "abc"; - /// let def = String::from("def"); - /// let mut s = String::new(); - /// s.extend(abc.chars()); - /// s.extend(def.chars()); - /// ``` - /// The correct use would be: - /// ```no_run - /// let abc = "abc"; - /// let def = String::from("def"); - /// let mut s = String::new(); - /// s.push_str(abc); - /// s.push_str(&def); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub STRING_EXTEND_CHARS, - style, - "using `x.extend(s.chars())` where s is a `&str` or `String`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the use of `.cloned().collect()` on slice to - /// create a `Vec`. - /// - /// ### Why is this bad? - /// `.to_vec()` is clearer - /// - /// ### Example - /// ```no_run - /// let s = [1, 2, 3, 4, 5]; - /// let s2: Vec = s[..].iter().cloned().collect(); - /// ``` - /// The better use would be: - /// ```no_run - /// let s = [1, 2, 3, 4, 5]; - /// let s2: Vec = s.to_vec(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ITER_CLONED_COLLECT, - style, - "using `.cloned().collect()` on slice to create a `Vec`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.chars().last()` or - /// `_.chars().next_back()` on a `str` to check if it ends with a given char. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.ends_with(_)`. - /// - /// ### Example - /// ```no_run - /// # let name = "_"; - /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-'); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let name = "_"; - /// name.ends_with('_') || name.ends_with('-'); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CHARS_LAST_CMP, - style, - "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.as_ref()` or `.as_mut()` where the - /// types before and after the call are the same. - /// - /// ### Why is this bad? - /// The call is unnecessary. - /// - /// ### Example - /// ```no_run - /// # fn do_stuff(x: &[i32]) {} - /// let x: &[i32] = &[1, 2, 3, 4, 5]; - /// do_stuff(x.as_ref()); - /// ``` - /// The correct use would be: - /// ```no_run - /// # fn do_stuff(x: &[i32]) {} - /// let x: &[i32] = &[1, 2, 3, 4, 5]; - /// do_stuff(x); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub USELESS_ASREF, - complexity, - "using `as_ref` where the types before and after the call are the same" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `fold` when a more succinct alternative exists. - /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`, - /// `sum` or `product`. - /// - /// ### Why is this bad? - /// Readability. - /// - /// ### Example - /// ```no_run - /// (0..3).fold(false, |acc, x| acc || x > 2); - /// ``` - /// - /// Use instead: - /// ```no_run - /// (0..3).any(|x| x > 2); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub UNNECESSARY_FOLD, - style, - "using `fold` when a more succinct alternative exists" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `filter_map` calls that could be replaced by `filter` or `map`. - /// More specifically it checks if the closure provided is only performing one of the - /// filter or map operations and suggests the appropriate option. - /// - /// ### Why is this bad? - /// Complexity. The intent is also clearer if only a single - /// operation is being performed. - /// - /// ### Example - /// ```no_run - /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None }); - /// - /// // As there is no transformation of the argument this could be written as: - /// let _ = (0..3).filter(|&x| x > 2); - /// ``` - /// - /// ```no_run - /// let _ = (0..4).filter_map(|x| Some(x + 1)); - /// - /// // As there is no conditional check on the argument this could be written as: - /// let _ = (0..4).map(|x| x + 1); - /// ``` - #[clippy::version = "1.31.0"] - pub UNNECESSARY_FILTER_MAP, - complexity, - "using `filter_map` when a more succinct alternative exists" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `find_map` calls that could be replaced by `find` or `map`. More - /// specifically it checks if the closure provided is only performing one of the - /// find or map operations and suggests the appropriate option. - /// - /// ### Why is this bad? - /// Complexity. The intent is also clearer if only a single - /// operation is being performed. - /// - /// ### Example - /// ```no_run - /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None }); - /// - /// // As there is no transformation of the argument this could be written as: - /// let _ = (0..3).find(|&x| x > 2); - /// ``` - /// - /// ```no_run - /// let _ = (0..4).find_map(|x| Some(x + 1)); - /// - /// // As there is no conditional check on the argument this could be written as: - /// let _ = (0..4).map(|x| x + 1).next(); - /// ``` - #[clippy::version = "1.61.0"] - pub UNNECESSARY_FIND_MAP, - complexity, - "using `find_map` when a more succinct alternative exists" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `into_iter` calls on references which should be replaced by `iter` - /// or `iter_mut`. - /// - /// ### Why is this bad? - /// Readability. Calling `into_iter` on a reference will not move out its - /// content into the resulting iterator, which is confusing. It is better just call `iter` or - /// `iter_mut` directly. - /// - /// ### Example - /// ```no_run - /// # let vec = vec![3, 4, 5]; - /// (&vec).into_iter(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let vec = vec![3, 4, 5]; - /// (&vec).iter(); - /// ``` - #[clippy::version = "1.32.0"] - pub INTO_ITER_ON_REF, - style, - "using `.into_iter()` on a reference" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `map` followed by a `count`. - /// - /// ### Why is this bad? - /// It looks suspicious. Maybe `map` was confused with `filter`. - /// If the `map` call is intentional, this should be rewritten - /// using `inspect`. Or, if you intend to drive the iterator to - /// completion, you can just use `for_each` instead. - /// - /// ### Example - /// ```no_run - /// let _ = (0..3).map(|x| x + 2).count(); - /// ``` - #[clippy::version = "1.39.0"] - pub SUSPICIOUS_MAP, - suspicious, - "suspicious usage of map" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `MaybeUninit::uninit().assume_init()`. - /// - /// ### Why is this bad? - /// For most types, this is undefined behavior. - /// - /// ### Known problems - /// For now, we accept empty tuples and tuples / arrays - /// of `MaybeUninit`. There may be other types that allow uninitialized - /// data, but those are not yet rigorously defined. - /// - /// ### Example - /// ```no_run - /// // Beware the UB - /// use std::mem::MaybeUninit; - /// - /// let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; - /// ``` - /// - /// Note that the following is OK: - /// - /// ```no_run - /// use std::mem::MaybeUninit; - /// - /// let _: [MaybeUninit; 5] = unsafe { - /// MaybeUninit::uninit().assume_init() - /// }; - /// ``` - #[clippy::version = "1.39.0"] - pub UNINIT_ASSUMED_INIT, - correctness, - "`MaybeUninit::uninit().assume_init()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`. - /// - /// ### Why is this bad? - /// These can be written simply with `saturating_add/sub` methods. - /// - /// ### Example - /// ```no_run - /// # let y: u32 = 0; - /// # let x: u32 = 100; - /// let add = x.checked_add(y).unwrap_or(u32::MAX); - /// let sub = x.checked_sub(y).unwrap_or(u32::MIN); - /// ``` - /// - /// can be written using dedicated methods for saturating addition/subtraction as: - /// - /// ```no_run - /// # let y: u32 = 0; - /// # let x: u32 = 100; - /// let add = x.saturating_add(y); - /// let sub = x.saturating_sub(y); - /// ``` - #[clippy::version = "1.39.0"] - pub MANUAL_SATURATING_ARITHMETIC, - style, - "`.checked_add/sub(x).unwrap_or(MAX/MIN)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to - /// zero-sized types - /// - /// ### Why is this bad? - /// This is a no-op, and likely unintended - /// - /// ### Example - /// ```no_run - /// unsafe { (&() as *const ()).offset(1) }; - /// ``` - #[clippy::version = "1.41.0"] - pub ZST_OFFSET, - correctness, - "Check for offset calculations on raw pointers to zero-sized types" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `FileType::is_file()`. - /// - /// ### Why restrict this? - /// When people testing a file type with `FileType::is_file` - /// they are testing whether a path is something they can get bytes from. But - /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover - /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention. - /// - /// ### Example - /// ```no_run - /// # || { - /// let metadata = std::fs::metadata("foo.txt")?; - /// let filetype = metadata.file_type(); - /// - /// if filetype.is_file() { - /// // read file - /// } - /// # Ok::<_, std::io::Error>(()) - /// # }; - /// ``` - /// - /// should be written as: - /// - /// ```no_run - /// # || { - /// let metadata = std::fs::metadata("foo.txt")?; - /// let filetype = metadata.file_type(); - /// - /// if !filetype.is_dir() { - /// // read file - /// } - /// # Ok::<_, std::io::Error>(()) - /// # }; - /// ``` - #[clippy::version = "1.42.0"] - pub FILETYPE_IS_FILE, - restriction, - "`FileType::is_file` is not recommended to test for readable file type" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.as_ref().map(Deref::deref)` or its aliases (such as String::as_str). - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.as_deref()`. - /// - /// ### Example - /// ```no_run - /// # let opt = Some("".to_string()); - /// opt.as_ref().map(String::as_str) - /// # ; - /// ``` - /// Can be written as - /// ```no_run - /// # let opt = Some("".to_string()); - /// opt.as_deref() - /// # ; - /// ``` - #[clippy::version = "1.42.0"] - pub OPTION_AS_REF_DEREF, - complexity, - "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `iter().next()` on a Slice or an Array - /// - /// ### Why is this bad? - /// These can be shortened into `.get()` - /// - /// ### Example - /// ```no_run - /// # let a = [1, 2, 3]; - /// # let b = vec![1, 2, 3]; - /// a[2..].iter().next(); - /// b.iter().next(); - /// ``` - /// should be written as: - /// ```no_run - /// # let a = [1, 2, 3]; - /// # let b = vec![1, 2, 3]; - /// a.get(2); - /// b.get(0); - /// ``` - #[clippy::version = "1.46.0"] - pub ITER_NEXT_SLICE, - style, - "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Warns when using `push_str`/`insert_str` with a single-character string literal - /// where `push`/`insert` with a `char` would work fine. - /// - /// ### Why is this bad? - /// It's less clear that we are pushing a single character. - /// - /// ### Example - /// ```no_run - /// # let mut string = String::new(); - /// string.insert_str(0, "R"); - /// string.push_str("R"); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let mut string = String::new(); - /// string.insert(0, 'R'); - /// string.push('R'); - /// ``` - #[clippy::version = "1.49.0"] - pub SINGLE_CHAR_ADD_STR, - style, - "`push_str()` or `insert_str()` used with a single-character string literal as parameter" -} - -declare_clippy_lint! { - /// ### What it does - /// As the counterpart to `or_fun_call`, this lint looks for unnecessary - /// lazily evaluated closures on `Option` and `Result`. - /// - /// This lint suggests changing the following functions, when eager evaluation results in - /// simpler code: - /// - `unwrap_or_else` to `unwrap_or` - /// - `and_then` to `and` - /// - `or_else` to `or` - /// - `get_or_insert_with` to `get_or_insert` - /// - `ok_or_else` to `ok_or` - /// - `then` to `then_some` (for msrv >= 1.62.0) - /// - /// ### Why is this bad? - /// Using eager evaluation is shorter and simpler in some cases. - /// - /// ### Known problems - /// It is possible, but not recommended for `Deref` and `Index` to have - /// side effects. Eagerly evaluating them can change the semantics of the program. - /// - /// ### Example - /// ```no_run - /// let opt: Option = None; - /// - /// opt.unwrap_or_else(|| 42); - /// ``` - /// Use instead: - /// ```no_run - /// let opt: Option = None; - /// - /// opt.unwrap_or(42); - /// ``` - #[clippy::version = "1.48.0"] - pub UNNECESSARY_LAZY_EVALUATIONS, - style, - "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.map(_).collect::()`. - /// - /// ### Why is this bad? - /// Using `try_for_each` instead is more readable and idiomatic. - /// - /// ### Example - /// ```no_run - /// (0..3).map(|t| Err(t)).collect::>(); - /// ``` - /// Use instead: - /// ```no_run - /// (0..3).try_for_each(|t| Err(t)); - /// ``` - #[clippy::version = "1.49.0"] - pub MAP_COLLECT_RESULT_UNIT, - style, - "using `.map(_).collect::()`, which can be replaced with `try_for_each`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `from_iter()` function calls on types that implement the `FromIterator` - /// trait. - /// - /// ### Why is this bad? - /// If it's needed to create a collection from the contents of an iterator, the `Iterator::collect(_)` - /// method is preferred. However, when it's needed to specify the container type, - /// `Vec::from_iter(_)` can be more readable than using a turbofish (e.g. `_.collect::>()`). See - /// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html) - /// - /// ### Example - /// ```no_run - /// let five_fives = std::iter::repeat(5).take(5); - /// - /// let v = Vec::from_iter(five_fives); - /// - /// assert_eq!(v, vec![5, 5, 5, 5, 5]); - /// ``` - /// Use instead: - /// ```no_run - /// let five_fives = std::iter::repeat(5).take(5); - /// - /// let v: Vec = five_fives.collect(); - /// - /// assert_eq!(v, vec![5, 5, 5, 5, 5]); - /// ``` - /// but prefer to use - /// ```no_run - /// let numbers: Vec = FromIterator::from_iter(1..=5); - /// ``` - /// instead of - /// ```no_run - /// let numbers = (1..=5).collect::>(); - /// ``` - #[clippy::version = "1.49.0"] - pub FROM_ITER_INSTEAD_OF_COLLECT, - pedantic, - "use `.collect()` instead of `::from_iter()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `inspect().for_each()`. - /// - /// ### Why is this bad? - /// It is the same as performing the computation - /// inside `inspect` at the beginning of the closure in `for_each`. - /// - /// ### Example - /// ```no_run - /// [1,2,3,4,5].iter() - /// .inspect(|&x| println!("inspect the number: {}", x)) - /// .for_each(|&x| { - /// assert!(x >= 0); - /// }); - /// ``` - /// Can be written as - /// ```no_run - /// [1,2,3,4,5].iter() - /// .for_each(|&x| { - /// println!("inspect the number: {}", x); - /// assert!(x >= 0); - /// }); - /// ``` - #[clippy::version = "1.51.0"] - pub INSPECT_FOR_EACH, - complexity, - "using `.inspect().for_each()`, which can be replaced with `.for_each()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `filter_map(|x| x)`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely by using `flatten`. - /// - /// ### Example - /// ```no_run - /// # let iter = vec![Some(1)].into_iter(); - /// iter.filter_map(|x| x); - /// ``` - /// Use instead: - /// ```no_run - /// # let iter = vec![Some(1)].into_iter(); - /// iter.flatten(); - /// ``` - #[clippy::version = "1.52.0"] - pub FILTER_MAP_IDENTITY, - complexity, - "call to `filter_map` where `flatten` is sufficient" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for instances of `map(f)` where `f` is the identity function. - /// - /// ### Why is this bad? - /// It can be written more concisely without the call to `map`. - /// - /// ### Example - /// ```no_run - /// let x = [1, 2, 3]; - /// let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect(); - /// ``` - /// Use instead: - /// ```no_run - /// let x = [1, 2, 3]; - /// let y: Vec<_> = x.iter().map(|x| 2*x).collect(); - /// ``` - #[clippy::version = "1.47.0"] - pub MAP_IDENTITY, - complexity, - "using iterator.map(|x| x)" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the use of `.bytes().nth()`. - /// - /// ### Why is this bad? - /// `.as_bytes().get()` is more efficient and more - /// readable. - /// - /// ### Example - /// ```no_run - /// "Hello".bytes().nth(3); - /// ``` - /// - /// Use instead: - /// ```no_run - /// "Hello".as_bytes().get(3); - /// ``` - #[clippy::version = "1.52.0"] - pub BYTES_NTH, - style, - "replace `.bytes().nth()` with `.as_bytes().get()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer. - /// - /// ### Why is this bad? - /// These methods do the same thing as `_.clone()` but may be confusing as - /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned. - /// - /// ### Example - /// ```no_run - /// let a = vec![1, 2, 3]; - /// let b = a.to_vec(); - /// let c = a.to_owned(); - /// ``` - /// Use instead: - /// ```no_run - /// let a = vec![1, 2, 3]; - /// let b = a.clone(); - /// let c = a.clone(); - /// ``` - #[clippy::version = "1.52.0"] - pub IMPLICIT_CLONE, - pedantic, - "implicitly cloning a value by invoking a function on its dereferenced type" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the use of `.iter().count()`. - /// - /// ### Why is this bad? - /// `.len()` is more efficient and more - /// readable. - /// - /// ### Example - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// - /// some_vec.iter().count(); - /// &some_vec[..].iter().count(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// - /// some_vec.len(); - /// &some_vec[..].len(); - /// ``` - #[clippy::version = "1.52.0"] - pub ITER_COUNT, - complexity, - "replace `.iter().count()` with `.len()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `_.to_owned()`, on a `Cow<'_, _>`. - /// - /// ### Why is this bad? - /// Calling `to_owned()` on a `Cow` creates a clone of the `Cow` - /// itself, without taking ownership of the `Cow` contents (i.e. - /// it's equivalent to calling `Cow::clone`). - /// The similarly named `into_owned` method, on the other hand, - /// clones the `Cow` contents, effectively turning any `Cow::Borrowed` - /// into a `Cow::Owned`. - /// - /// Given the potential ambiguity, consider replacing `to_owned` - /// with `clone` for better readability or, if getting a `Cow::Owned` - /// was the original intent, using `into_owned` instead. - /// - /// ### Example - /// ```no_run - /// # use std::borrow::Cow; - /// let s = "Hello world!"; - /// let cow = Cow::Borrowed(s); - /// - /// let data = cow.to_owned(); - /// assert!(matches!(data, Cow::Borrowed(_))) - /// ``` - /// Use instead: - /// ```no_run - /// # use std::borrow::Cow; - /// let s = "Hello world!"; - /// let cow = Cow::Borrowed(s); - /// - /// let data = cow.clone(); - /// assert!(matches!(data, Cow::Borrowed(_))) - /// ``` - /// or - /// ```no_run - /// # use std::borrow::Cow; - /// let s = "Hello world!"; - /// let cow = Cow::Borrowed(s); - /// - /// let _data: String = cow.into_owned(); - /// ``` - #[clippy::version = "1.65.0"] - pub SUSPICIOUS_TO_OWNED, - suspicious, - "calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to [`splitn`] - /// (https://doc.rust-lang.org/std/primitive.str.html#method.splitn) and - /// related functions with either zero or one splits. - /// - /// ### Why is this bad? - /// These calls don't actually split the value and are - /// likely to be intended as a different number. - /// - /// ### Example - /// ```no_run - /// # let s = ""; - /// for x in s.splitn(1, ":") { - /// // .. - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let s = ""; - /// for x in s.splitn(2, ":") { - /// // .. - /// } - /// ``` - #[clippy::version = "1.54.0"] - pub SUSPICIOUS_SPLITN, - correctness, - "checks for `.splitn(0, ..)` and `.splitn(1, ..)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for manual implementations of `str::repeat` - /// - /// ### Why is this bad? - /// These are both harder to read, as well as less performant. - /// - /// ### Example - /// ```no_run - /// let x: String = std::iter::repeat('x').take(10).collect(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let x: String = "x".repeat(10); - /// ``` - #[clippy::version = "1.54.0"] - pub MANUAL_STR_REPEAT, - perf, - "manual implementation of `str::repeat`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `str::splitn(2, _)` - /// - /// ### Why is this bad? - /// `split_once` is both clearer in intent and slightly more efficient. - /// - /// ### Example - /// ```rust,ignore - /// let s = "key=value=add"; - /// let (key, value) = s.splitn(2, '=').next_tuple()?; - /// let value = s.splitn(2, '=').nth(1)?; - /// - /// let mut parts = s.splitn(2, '='); - /// let key = parts.next()?; - /// let value = parts.next()?; - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// let s = "key=value=add"; - /// let (key, value) = s.split_once('=')?; - /// let value = s.split_once('=')?.1; - /// - /// let (key, value) = s.split_once('=')?; - /// ``` - /// - /// ### Limitations - /// The multiple statement variant currently only detects `iter.next()?`/`iter.next().unwrap()` - /// in two separate `let` statements that immediately follow the `splitn()` - #[clippy::version = "1.57.0"] - pub MANUAL_SPLIT_ONCE, - complexity, - "replace `.splitn(2, pat)` with `.split_once(pat)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `str::splitn` (or `str::rsplitn`) where using `str::split` would be the same. - /// ### Why is this bad? - /// The function `split` is simpler and there is no performance difference in these cases, considering - /// that both functions return a lazy iterator. - /// ### Example - /// ```no_run - /// let str = "key=value=add"; - /// let _ = str.splitn(3, '=').next().unwrap(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let str = "key=value=add"; - /// let _ = str.split('=').next().unwrap(); - /// ``` - #[clippy::version = "1.59.0"] - pub NEEDLESS_SPLITN, - complexity, - "usages of `str::splitn` that can be replaced with `str::split`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) - /// and other `to_owned`-like functions. - /// - /// ### Why is this bad? - /// The unnecessary calls result in useless allocations. - /// - /// ### Known problems - /// `unnecessary_to_owned` can falsely trigger if `IntoIterator::into_iter` is applied to an - /// owned copy of a resource and the resource is later used mutably. See - /// [#8148](https://github.com/rust-lang/rust-clippy/issues/8148). - /// - /// ### Example - /// ```no_run - /// let path = std::path::Path::new("x"); - /// foo(&path.to_string_lossy().to_string()); - /// fn foo(s: &str) {} - /// ``` - /// Use instead: - /// ```no_run - /// let path = std::path::Path::new("x"); - /// foo(&path.to_string_lossy()); - /// fn foo(s: &str) {} - /// ``` - #[clippy::version = "1.59.0"] - pub UNNECESSARY_TO_OWNED, - perf, - "unnecessary calls to `to_owned`-like functions" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.collect::>().join("")` on iterators. - /// - /// ### Why is this bad? - /// `.collect::()` is more concise and might be more performant - /// - /// ### Example - /// ```no_run - /// let vector = vec!["hello", "world"]; - /// let output = vector.iter().map(|item| item.to_uppercase()).collect::>().join(""); - /// println!("{}", output); - /// ``` - /// The correct use would be: - /// ```no_run - /// let vector = vec!["hello", "world"]; - /// let output = vector.iter().map(|item| item.to_uppercase()).collect::(); - /// println!("{}", output); - /// ``` - /// ### Known problems - /// While `.collect::()` is sometimes more performant, there are cases where - /// using `.collect::()` over `.collect::>().join("")` - /// will prevent loop unrolling and will result in a negative performance impact. - /// - /// Additionally, differences have been observed between aarch64 and x86_64 assembly output, - /// with aarch64 tending to producing faster assembly in more cases when using `.collect::()` - #[clippy::version = "1.61.0"] - pub UNNECESSARY_JOIN, - pedantic, - "using `.collect::>().join(\"\")` on an iterator" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for no-op uses of `Option::{as_deref, as_deref_mut}`, - /// for example, `Option<&T>::as_deref()` returns the same type. - /// - /// ### Why is this bad? - /// Redundant code and improving readability. - /// - /// ### Example - /// ```no_run - /// let a = Some(&1); - /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32> - /// ``` - /// - /// Use instead: - /// ```no_run - /// let a = Some(&1); - /// let b = a; - /// ``` - #[clippy::version = "1.57.0"] - pub NEEDLESS_OPTION_AS_DEREF, - complexity, - "no-op use of `deref` or `deref_mut` method to `Option`." -} - -declare_clippy_lint! { - /// ### What it does - /// Finds usages of [`char::is_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that - /// can be replaced with [`is_ascii_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or - /// [`is_ascii_hexdigit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit). - /// - /// ### Why is this bad? - /// `is_digit(..)` is slower and requires specifying the radix. - /// - /// ### Example - /// ```no_run - /// let c: char = '6'; - /// c.is_digit(10); - /// c.is_digit(16); - /// ``` - /// Use instead: - /// ```no_run - /// let c: char = '6'; - /// c.is_ascii_digit(); - /// c.is_ascii_hexdigit(); - /// ``` - #[clippy::version = "1.62.0"] - pub IS_DIGIT_ASCII_RADIX, - style, - "use of `char::is_digit(..)` with literal radix of 10 or 16" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calling `take` function after `as_ref`. - /// - /// ### Why is this bad? - /// Redundant code. `take` writes `None` to its argument. - /// In this case the modification is useless as it's a temporary that cannot be read from afterwards. - /// - /// ### Example - /// ```no_run - /// let x = Some(3); - /// x.as_ref().take(); - /// ``` - /// Use instead: - /// ```no_run - /// let x = Some(3); - /// x.as_ref(); - /// ``` - #[clippy::version = "1.62.0"] - pub NEEDLESS_OPTION_TAKE, - complexity, - "using `.as_ref().take()` on a temporary value" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `replace` statements which have no effect. - /// - /// ### Why is this bad? - /// It's either a mistake or confusing. - /// - /// ### Example - /// ```no_run - /// "1234".replace("12", "12"); - /// "1234".replacen("12", "12", 1); - /// ``` - #[clippy::version = "1.63.0"] - pub NO_EFFECT_REPLACE, - suspicious, - "replace with no effect" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for unnecessary method chains that can be simplified into `if .. else ..`. - /// - /// ### Why is this bad? - /// This can be written more clearly with `if .. else ..` - /// - /// ### Limitations - /// This lint currently only looks for usages of - /// `.{then, then_some}(..).{unwrap_or, unwrap_or_else, unwrap_or_default}(..)`, but will be expanded - /// to account for similar patterns. - /// - /// ### Example - /// ```no_run - /// let x = true; - /// x.then_some("a").unwrap_or("b"); - /// ``` - /// Use instead: - /// ```no_run - /// let x = true; - /// if x { "a" } else { "b" }; - /// ``` - #[clippy::version = "1.64.0"] - pub OBFUSCATED_IF_ELSE, - style, - "use of `.then_some(..).unwrap_or(..)` can be written \ - more clearly with `if .. else ..`" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for calls to `iter`, `iter_mut` or `into_iter` on collections containing a single item - /// - /// ### Why is this bad? - /// - /// It is simpler to use the once function from the standard library: - /// - /// ### Example - /// - /// ```no_run - /// let a = [123].iter(); - /// let b = Some(123).into_iter(); - /// ``` - /// Use instead: - /// ```no_run - /// use std::iter; - /// let a = iter::once(&123); - /// let b = iter::once(123); - /// ``` - /// - /// ### Known problems - /// - /// The type of the resulting iterator might become incompatible with its usage - #[clippy::version = "1.65.0"] - pub ITER_ON_SINGLE_ITEMS, - nursery, - "Iterator for array of length 1" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for calls to `iter`, `iter_mut` or `into_iter` on empty collections - /// - /// ### Why is this bad? - /// - /// It is simpler to use the empty function from the standard library: - /// - /// ### Example - /// - /// ```no_run - /// use std::{slice, option}; - /// let a: slice::Iter = [].iter(); - /// let f: option::IntoIter = None.into_iter(); - /// ``` - /// Use instead: - /// ```no_run - /// use std::iter; - /// let a: iter::Empty = iter::empty(); - /// let b: iter::Empty = iter::empty(); - /// ``` - /// - /// ### Known problems - /// - /// The type of the resulting iterator might become incompatible with its usage - #[clippy::version = "1.65.0"] - pub ITER_ON_EMPTY_COLLECTIONS, - nursery, - "Iterator for empty array" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for naive byte counts - /// - /// ### Why is this bad? - /// The [`bytecount`](https://crates.io/crates/bytecount) - /// crate has methods to count your bytes faster, especially for large slices. - /// - /// ### Known problems - /// If you have predominantly small slices, the - /// `bytecount::count(..)` method may actually be slower. However, if you can - /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be - /// faster in those cases. - /// - /// ### Example - /// ```no_run - /// # let vec = vec![1_u8]; - /// let count = vec.iter().filter(|x| **x == 0u8).count(); - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// # let vec = vec![1_u8]; - /// let count = bytecount::count(&vec, 0u8); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NAIVE_BYTECOUNT, - pedantic, - "use of naive `.filter(|&x| x == y).count()` to count byte values" -} - -declare_clippy_lint! { - /// ### What it does - /// It checks for `str::bytes().count()` and suggests replacing it with - /// `str::len()`. - /// - /// ### Why is this bad? - /// `str::bytes().count()` is longer and may not be as performant as using - /// `str::len()`. - /// - /// ### Example - /// ```no_run - /// "hello".bytes().count(); - /// String::from("hello").bytes().count(); - /// ``` - /// Use instead: - /// ```no_run - /// "hello".len(); - /// String::from("hello").len(); - /// ``` - #[clippy::version = "1.62.0"] - pub BYTES_COUNT_TO_LEN, - complexity, - "Using `bytes().count()` when `len()` performs the same functionality" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `ends_with` with possible file extensions - /// and suggests to use a case-insensitive approach instead. - /// - /// ### Why is this bad? - /// `ends_with` is case-sensitive and may not detect files with a valid extension. - /// - /// ### Example - /// ```no_run - /// fn is_rust_file(filename: &str) -> bool { - /// filename.ends_with(".rs") - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn is_rust_file(filename: &str) -> bool { - /// let filename = std::path::Path::new(filename); - /// filename.extension() - /// .map_or(false, |ext| ext.eq_ignore_ascii_case("rs")) - /// } - /// ``` - #[clippy::version = "1.51.0"] - pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, - pedantic, - "Checks for calls to ends_with with case-sensitive file extensions" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `x.get(0)` instead of - /// `x.first()` or `x.front()`. - /// - /// ### Why is this bad? - /// Using `x.first()` for `Vec`s and slices or `x.front()` - /// for `VecDeque`s is easier to read and has the same result. - /// - /// ### Example - /// ```no_run - /// let x = vec![2, 3, 5]; - /// let first_element = x.get(0); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let x = vec![2, 3, 5]; - /// let first_element = x.first(); - /// ``` - #[clippy::version = "1.63.0"] - pub GET_FIRST, - style, - "Using `x.get(0)` when `x.first()` or `x.front()` is simpler" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Finds patterns that reimplement `Option::ok_or`. - /// - /// ### Why is this bad? - /// - /// Concise code helps focusing on behavior instead of boilerplate. - /// - /// ### Examples - /// ```no_run - /// let foo: Option = None; - /// foo.map_or(Err("error"), |v| Ok(v)); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let foo: Option = None; - /// foo.ok_or("error"); - /// ``` - #[clippy::version = "1.49.0"] - pub MANUAL_OK_OR, - style, - "finds patterns that can be encoded more concisely with `Option::ok_or`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `map(|x| x.clone())` or - /// dereferencing closures for `Copy` types, on `Iterator` or `Option`, - /// and suggests `cloned()` or `copied()` instead - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely - /// - /// ### Example - /// ```no_run - /// let x = vec![42, 43]; - /// let y = x.iter(); - /// let z = y.map(|i| *i); - /// ``` - /// - /// The correct use would be: - /// - /// ```no_run - /// let x = vec![42, 43]; - /// let y = x.iter(); - /// let z = y.cloned(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MAP_CLONE, - style, - "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for instances of `map_err(|_| Some::Enum)` - /// - /// ### Why restrict this? - /// This `map_err` throws away the original error rather than allowing the enum to - /// contain and report the cause of the error. - /// - /// ### Example - /// Before: - /// ```no_run - /// use std::fmt; - /// - /// #[derive(Debug)] - /// enum Error { - /// Indivisible, - /// Remainder(u8), - /// } - /// - /// impl fmt::Display for Error { - /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - /// match self { - /// Error::Indivisible => write!(f, "could not divide input by three"), - /// Error::Remainder(remainder) => write!( - /// f, - /// "input is not divisible by three, remainder = {}", - /// remainder - /// ), - /// } - /// } - /// } - /// - /// impl std::error::Error for Error {} - /// - /// fn divisible_by_3(input: &str) -> Result<(), Error> { - /// input - /// .parse::() - /// .map_err(|_| Error::Indivisible) - /// .map(|v| v % 3) - /// .and_then(|remainder| { - /// if remainder == 0 { - /// Ok(()) - /// } else { - /// Err(Error::Remainder(remainder as u8)) - /// } - /// }) - /// } - /// ``` - /// - /// After: - /// ```rust - /// use std::{fmt, num::ParseIntError}; - /// - /// #[derive(Debug)] - /// enum Error { - /// Indivisible(ParseIntError), - /// Remainder(u8), - /// } - /// - /// impl fmt::Display for Error { - /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - /// match self { - /// Error::Indivisible(_) => write!(f, "could not divide input by three"), - /// Error::Remainder(remainder) => write!( - /// f, - /// "input is not divisible by three, remainder = {}", - /// remainder - /// ), - /// } - /// } - /// } - /// - /// impl std::error::Error for Error { - /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - /// match self { - /// Error::Indivisible(source) => Some(source), - /// _ => None, - /// } - /// } - /// } - /// - /// fn divisible_by_3(input: &str) -> Result<(), Error> { - /// input - /// .parse::() - /// .map_err(Error::Indivisible) - /// .map(|v| v % 3) - /// .and_then(|remainder| { - /// if remainder == 0 { - /// Ok(()) - /// } else { - /// Err(Error::Remainder(remainder as u8)) - /// } - /// }) - /// } - /// ``` - #[clippy::version = "1.48.0"] - pub MAP_ERR_IGNORE, - restriction, - "`map_err` should not ignore the original error" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `&mut Mutex::lock` calls - /// - /// ### Why is this bad? - /// `Mutex::lock` is less efficient than - /// calling `Mutex::get_mut`. In addition you also have a statically - /// guarantee that the mutex isn't locked, instead of just a runtime - /// guarantee. - /// - /// ### Example - /// ```no_run - /// use std::sync::{Arc, Mutex}; - /// - /// let mut value_rc = Arc::new(Mutex::new(42_u8)); - /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); - /// - /// let mut value = value_mutex.lock().unwrap(); - /// *value += 1; - /// ``` - /// Use instead: - /// ```no_run - /// use std::sync::{Arc, Mutex}; - /// - /// let mut value_rc = Arc::new(Mutex::new(42_u8)); - /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); - /// - /// let value = value_mutex.get_mut().unwrap(); - /// *value += 1; - /// ``` - #[clippy::version = "1.49.0"] - pub MUT_MUTEX_LOCK, - style, - "`&mut Mutex::lock` does unnecessary locking" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for duplicate open options as well as combinations - /// that make no sense. - /// - /// ### Why is this bad? - /// In the best case, the code will be harder to read than - /// necessary. I don't know the worst case. - /// - /// ### Example - /// ```no_run - /// use std::fs::OpenOptions; - /// - /// OpenOptions::new().read(true).truncate(true); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NONSENSICAL_OPEN_OPTIONS, - correctness, - "nonsensical combination of options for opening a file" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the suspicious use of `OpenOptions::create()` - /// without an explicit `OpenOptions::truncate()`. - /// - /// ### Why is this bad? - /// `create()` alone will either create a new file or open an - /// existing file. If the file already exists, it will be - /// overwritten when written to, but the file will not be - /// truncated by default. - /// If less data is written to the file - /// than it already contains, the remainder of the file will - /// remain unchanged, and the end of the file will contain old - /// data. - /// In most cases, one should either use `create_new` to ensure - /// the file is created from scratch, or ensure `truncate` is - /// called so that the truncation behaviour is explicit. `truncate(true)` - /// will ensure the file is entirely overwritten with new data, whereas - /// `truncate(false)` will explicitly keep the default behavior. - /// - /// ### Example - /// ```rust,no_run - /// use std::fs::OpenOptions; - /// - /// OpenOptions::new().create(true); - /// ``` - /// Use instead: - /// ```rust,no_run - /// use std::fs::OpenOptions; - /// - /// OpenOptions::new().create(true).truncate(true); - /// ``` - #[clippy::version = "1.77.0"] - pub SUSPICIOUS_OPEN_OPTIONS, - suspicious, - "suspicious combination of options for opening a file" -} - -declare_clippy_lint! { - /// ### What it does - ///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push) - /// calls on `PathBuf` that can cause overwrites. - /// - /// ### Why is this bad? - /// Calling `push` with a root path at the start can overwrite the - /// previous defined path. - /// - /// ### Example - /// ```no_run - /// use std::path::PathBuf; - /// - /// let mut x = PathBuf::from("/foo"); - /// x.push("/bar"); - /// assert_eq!(x, PathBuf::from("/bar")); - /// ``` - /// Could be written: - /// - /// ```no_run - /// use std::path::PathBuf; - /// - /// let mut x = PathBuf::from("/foo"); - /// x.push("bar"); - /// assert_eq!(x, PathBuf::from("/foo/bar")); - /// ``` - #[clippy::version = "1.36.0"] - pub PATH_BUF_PUSH_OVERWRITE, - nursery, - "calling `push` with file system root on `PathBuf` can overwrite it" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for zipping a collection with the range of - /// `0.._.len()`. - /// - /// ### Why is this bad? - /// The code is better expressed with `.enumerate()`. - /// - /// ### Example - /// ```no_run - /// # let x = vec![1]; - /// let _ = x.iter().zip(0..x.len()); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let x = vec![1]; - /// let _ = x.iter().enumerate(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub RANGE_ZIP_WITH_LEN, - complexity, - "zipping iterator with a range when `enumerate()` would do" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.repeat(1)` and suggest the following method for each types. - /// - `.to_string()` for `str` - /// - `.clone()` for `String` - /// - `.to_vec()` for `slice` - /// - /// The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if - /// they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306)) - /// - /// ### Why is this bad? - /// For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning - /// the string is the intention behind this, `clone()` should be used. - /// - /// ### Example - /// ```no_run - /// fn main() { - /// let x = String::from("hello world").repeat(1); - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn main() { - /// let x = String::from("hello world").clone(); - /// } - /// ``` - #[clippy::version = "1.47.0"] - pub REPEAT_ONCE, - complexity, - "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` " -} - -declare_clippy_lint! { - /// ### What it does - /// When sorting primitive values (integers, bools, chars, as well - /// as arrays, slices, and tuples of such items), it is typically better to - /// use an unstable sort than a stable sort. - /// - /// ### Why is this bad? - /// Typically, using a stable sort consumes more memory and cpu cycles. - /// Because values which compare equal are identical, preserving their - /// relative order (the guarantee that a stable sort provides) means - /// nothing, while the extra costs still apply. - /// - /// ### Known problems - /// - /// As pointed out in - /// [issue #8241](https://github.com/rust-lang/rust-clippy/issues/8241), - /// a stable sort can instead be significantly faster for certain scenarios - /// (eg. when a sorted vector is extended with new data and resorted). - /// - /// For more information and benchmarking results, please refer to the - /// issue linked above. - /// - /// ### Example - /// ```no_run - /// let mut vec = vec![2, 1, 3]; - /// vec.sort(); - /// ``` - /// Use instead: - /// ```no_run - /// let mut vec = vec![2, 1, 3]; - /// vec.sort_unstable(); - /// ``` - #[clippy::version = "1.47.0"] - pub STABLE_SORT_PRIMITIVE, - pedantic, - "use of sort() when sort_unstable() is equivalent" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for calls to `.type_id()` on a `Box`. - /// - /// ### Why is this bad? - /// This almost certainly does not do what the user expects and can lead to subtle bugs. - /// Calling `.type_id()` on a `Box` returns a fixed `TypeId` of the `Box` itself, - /// rather than returning the `TypeId` of the underlying type behind the trait object. - /// - /// For `Box` specifically (and trait objects that have `Any` as its supertrait), - /// this lint will provide a suggestion, which is to dereference the receiver explicitly - /// to go from `Box` to `dyn Any`. - /// This makes sure that `.type_id()` resolves to a dynamic call on the trait object - /// and not on the box. - /// - /// If the fixed `TypeId` of the `Box` is the intended behavior, it's better to be explicit about it - /// and write `TypeId::of::>()`: - /// this makes it clear that a fixed `TypeId` is returned and not the `TypeId` of the implementor. - /// - /// ### Example - /// ```rust,ignore - /// use std::any::{Any, TypeId}; - /// - /// let any_box: Box = Box::new(42_i32); - /// assert_eq!(any_box.type_id(), TypeId::of::()); // ⚠️ this fails! - /// ``` - /// Use instead: - /// ```no_run - /// use std::any::{Any, TypeId}; - /// - /// let any_box: Box = Box::new(42_i32); - /// assert_eq!((*any_box).type_id(), TypeId::of::()); - /// // ^ dereference first, to call `type_id` on `dyn Any` - /// ``` - #[clippy::version = "1.73.0"] - pub TYPE_ID_ON_BOX, - suspicious, - "calling `.type_id()` on a boxed trait object" -} - -declare_clippy_lint! { - /// ### What it does - /// Detects `().hash(_)`. - /// - /// ### Why is this bad? - /// Hashing a unit value doesn't do anything as the implementation of `Hash` for `()` is a no-op. - /// - /// ### Example - /// ```no_run - /// # use std::hash::Hash; - /// # use std::collections::hash_map::DefaultHasher; - /// # enum Foo { Empty, WithValue(u8) } - /// # use Foo::*; - /// # let mut state = DefaultHasher::new(); - /// # let my_enum = Foo::Empty; - /// match my_enum { - /// Empty => ().hash(&mut state), - /// WithValue(x) => x.hash(&mut state), - /// } - /// ``` - /// Use instead: - /// ```no_run - /// # use std::hash::Hash; - /// # use std::collections::hash_map::DefaultHasher; - /// # enum Foo { Empty, WithValue(u8) } - /// # use Foo::*; - /// # let mut state = DefaultHasher::new(); - /// # let my_enum = Foo::Empty; - /// match my_enum { - /// Empty => 0_u8.hash(&mut state), - /// WithValue(x) => x.hash(&mut state), - /// } - /// ``` - #[clippy::version = "1.58.0"] - pub UNIT_HASH, - correctness, - "hashing a unit value, which does nothing" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `Vec::sort_by` passing in a closure - /// which compares the two arguments, either directly or indirectly. - /// - /// ### Why is this bad? - /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if - /// possible) than to use `Vec::sort_by` and a more complicated - /// closure. - /// - /// ### Known problems - /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already - /// imported by a use statement, then it will need to be added manually. - /// - /// ### Example - /// ```no_run - /// # struct A; - /// # impl A { fn foo(&self) {} } - /// # let mut vec: Vec = Vec::new(); - /// vec.sort_by(|a, b| a.foo().cmp(&b.foo())); - /// ``` - /// Use instead: - /// ```no_run - /// # struct A; - /// # impl A { fn foo(&self) {} } - /// # let mut vec: Vec = Vec::new(); - /// vec.sort_by_key(|a| a.foo()); - /// ``` - #[clippy::version = "1.46.0"] - pub UNNECESSARY_SORT_BY, - complexity, - "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer" -} - -declare_clippy_lint! { - /// ### What it does - /// Finds occurrences of `Vec::resize(0, an_int)` - /// - /// ### Why is this bad? - /// This is probably an argument inversion mistake. - /// - /// ### Example - /// ```no_run - /// vec![1, 2, 3, 4, 5].resize(0, 5) - /// ``` - /// - /// Use instead: - /// ```no_run - /// vec![1, 2, 3, 4, 5].clear() - /// ``` - #[clippy::version = "1.46.0"] - pub VEC_RESIZE_TO_ZERO, - correctness, - "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of File::read_to_end and File::read_to_string. - /// - /// ### Why restrict this? - /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. - /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) - /// - /// ### Example - /// ```rust,no_run - /// # use std::io::Read; - /// # use std::fs::File; - /// let mut f = File::open("foo.txt").unwrap(); - /// let mut bytes = Vec::new(); - /// f.read_to_end(&mut bytes).unwrap(); - /// ``` - /// Can be written more concisely as - /// ```rust,no_run - /// # use std::fs; - /// let mut bytes = fs::read("foo.txt").unwrap(); - /// ``` - #[clippy::version = "1.44.0"] - pub VERBOSE_FILE_READS, - restriction, - "use of `File::read_to_end` or `File::read_to_string`" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for iterating a map (`HashMap` or `BTreeMap`) and - /// ignoring either the keys or values. - /// - /// ### Why is this bad? - /// - /// Readability. There are `keys` and `values` methods that - /// can be used to express that we only need the keys or the values. - /// - /// ### Example - /// - /// ```no_run - /// # use std::collections::HashMap; - /// let map: HashMap = HashMap::new(); - /// let values = map.iter().map(|(_, value)| value).collect::>(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::collections::HashMap; - /// let map: HashMap = HashMap::new(); - /// let values = map.values().collect::>(); - /// ``` - #[clippy::version = "1.66.0"] - pub ITER_KV_MAP, - complexity, - "iterating on map using `iter` when `keys` or `values` would do" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks if the `seek` method of the `Seek` trait is called with `SeekFrom::Current(0)`, - /// and if it is, suggests using `stream_position` instead. - /// - /// ### Why is this bad? - /// - /// Readability. Use dedicated method. - /// - /// ### Example - /// - /// ```rust,no_run - /// use std::fs::File; - /// use std::io::{self, Write, Seek, SeekFrom}; - /// - /// fn main() -> io::Result<()> { - /// let mut f = File::create("foo.txt")?; - /// f.write_all(b"Hello")?; - /// eprintln!("Written {} bytes", f.seek(SeekFrom::Current(0))?); - /// - /// Ok(()) - /// } - /// ``` - /// Use instead: - /// ```rust,no_run - /// use std::fs::File; - /// use std::io::{self, Write, Seek, SeekFrom}; - /// - /// fn main() -> io::Result<()> { - /// let mut f = File::create("foo.txt")?; - /// f.write_all(b"Hello")?; - /// eprintln!("Written {} bytes", f.stream_position()?); - /// - /// Ok(()) - /// } - /// ``` - #[clippy::version = "1.67.0"] - pub SEEK_FROM_CURRENT, - complexity, - "use dedicated method for seek from current position" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for jumps to the start of a stream that implements `Seek` - /// and uses the `seek` method providing `Start` as parameter. - /// - /// ### Why is this bad? - /// - /// Readability. There is a specific method that was implemented for - /// this exact scenario. - /// - /// ### Example - /// ```no_run - /// # use std::io; - /// fn foo(t: &mut T) { - /// t.seek(io::SeekFrom::Start(0)); - /// } - /// ``` - /// Use instead: - /// ```no_run - /// # use std::io; - /// fn foo(t: &mut T) { - /// t.rewind(); - /// } - /// ``` - #[clippy::version = "1.67.0"] - pub SEEK_TO_START_INSTEAD_OF_REWIND, - complexity, - "jumping to the start of stream using `seek` method" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for functions collecting an iterator when collect - /// is not needed. - /// - /// ### Why is this bad? - /// `collect` causes the allocation of a new data structure, - /// when this allocation may not be needed. - /// - /// ### Example - /// ```no_run - /// # let iterator = vec![1].into_iter(); - /// let len = iterator.collect::>().len(); - /// ``` - /// Use instead: - /// ```no_run - /// # let iterator = vec![1].into_iter(); - /// let len = iterator.count(); - /// ``` - #[clippy::version = "1.30.0"] - pub NEEDLESS_COLLECT, - nursery, - "collecting an iterator when collect is not needed" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for `Command::arg()` invocations that look like they - /// should be multiple arguments instead, such as `arg("-t ext2")`. - /// - /// ### Why is this bad? - /// - /// `Command::arg()` does not split arguments by space. An argument like `arg("-t ext2")` - /// will be passed as a single argument to the command, - /// which is likely not what was intended. - /// - /// ### Example - /// ```no_run - /// std::process::Command::new("echo").arg("-n hello").spawn().unwrap(); - /// ``` - /// Use instead: - /// ```no_run - /// std::process::Command::new("echo").args(["-n", "hello"]).spawn().unwrap(); - /// ``` - #[clippy::version = "1.69.0"] - pub SUSPICIOUS_COMMAND_ARG_SPACE, - suspicious, - "single command line argument that looks like it should be multiple arguments" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.drain(..)` for the sole purpose of clearing a container. - /// - /// ### Why is this bad? - /// This creates an unnecessary iterator that is dropped immediately. - /// - /// Calling `.clear()` also makes the intent clearer. - /// - /// ### Example - /// ```no_run - /// let mut v = vec![1, 2, 3]; - /// v.drain(..); - /// ``` - /// Use instead: - /// ```no_run - /// let mut v = vec![1, 2, 3]; - /// v.clear(); - /// ``` - #[clippy::version = "1.70.0"] - pub CLEAR_WITH_DRAIN, - nursery, - "calling `drain` in order to `clear` a container" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.rev().next()` on a `DoubleEndedIterator` - /// - /// ### Why is this bad? - /// `.next_back()` is cleaner. - /// - /// ### Example - /// ```no_run - /// # let foo = [0; 10]; - /// foo.iter().rev().next(); - /// ``` - /// Use instead: - /// ```no_run - /// # let foo = [0; 10]; - /// foo.iter().next_back(); - /// ``` - #[clippy::version = "1.71.0"] - pub MANUAL_NEXT_BACK, - style, - "manual reverse iteration of `DoubleEndedIterator`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `.drain()` that clear the collection, immediately followed by a call to `.collect()`. - /// - /// > "Collection" in this context refers to any type with a `drain` method: - /// > `Vec`, `VecDeque`, `BinaryHeap`, `HashSet`,`HashMap`, `String` - /// - /// ### Why is this bad? - /// Using `mem::take` is faster as it avoids the allocation. - /// When using `mem::take`, the old collection is replaced with an empty one and ownership of - /// the old collection is returned. - /// - /// ### Known issues - /// `mem::take(&mut vec)` is almost equivalent to `vec.drain(..).collect()`, except that - /// it also moves the **capacity**. The user might have explicitly written it this way - /// to keep the capacity on the original `Vec`. - /// - /// ### Example - /// ```no_run - /// fn remove_all(v: &mut Vec) -> Vec { - /// v.drain(..).collect() - /// } - /// ``` - /// Use instead: - /// ```no_run - /// use std::mem; - /// fn remove_all(v: &mut Vec) -> Vec { - /// mem::take(v) - /// } - /// ``` - #[clippy::version = "1.72.0"] - pub DRAIN_COLLECT, - perf, - "calling `.drain(..).collect()` to move all elements into a new collection" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `Iterator::fold` with a type that implements `Try`. - /// - /// ### Why is this bad? - /// The code should use `try_fold` instead, which short-circuits on failure, thus opening the - /// door for additional optimizations not possible with `fold` as rustc can guarantee the - /// function is never called on `None`, `Err`, etc., alleviating otherwise necessary checks. It's - /// also slightly more idiomatic. - /// - /// ### Known issues - /// This lint doesn't take into account whether a function does something on the failure case, - /// i.e., whether short-circuiting will affect behavior. Refactoring to `try_fold` is not - /// desirable in those cases. - /// - /// ### Example - /// ```no_run - /// vec![1, 2, 3].iter().fold(Some(0i32), |sum, i| sum?.checked_add(*i)); - /// ``` - /// Use instead: - /// ```no_run - /// vec![1, 2, 3].iter().try_fold(0i32, |sum, i| sum.checked_add(*i)); - /// ``` - #[clippy::version = "1.72.0"] - pub MANUAL_TRY_FOLD, - perf, - "checks for usage of `Iterator::fold` with a type that implements `Try`" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for calls to [`Stdin::read_line`] to read a line from the standard input - /// into a string, then later attempting to use that string for an operation that will never - /// work for strings with a trailing newline character in it (e.g. parsing into a `i32`). - /// - /// ### Why is this bad? - /// The operation will always fail at runtime no matter what the user enters, thus - /// making it a useless operation. - /// - /// ### Example - /// ```rust,ignore - /// let mut input = String::new(); - /// std::io::stdin().read_line(&mut input).expect("Failed to read a line"); - /// let num: i32 = input.parse().expect("Not a number!"); - /// assert_eq!(num, 42); // we never even get here! - /// ``` - /// Use instead: - /// ```rust,ignore - /// let mut input = String::new(); - /// std::io::stdin().read_line(&mut input).expect("Failed to read a line"); - /// let num: i32 = input.trim_end().parse().expect("Not a number!"); - /// // ^^^^^^^^^^^ remove the trailing newline - /// assert_eq!(num, 42); - /// ``` - #[clippy::version = "1.73.0"] - pub READ_LINE_WITHOUT_TRIM, - correctness, - "calling `Stdin::read_line`, then trying to parse it without first trimming" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.chars().any(|i| i == c)`. - /// - /// ### Why is this bad? - /// It's significantly slower than using a pattern instead, like - /// `matches!(c, '\\' | '.' | '+')`. - /// - /// Despite this being faster, this is not `perf` as this is pretty common, and is a rather nice - /// way to check if a `char` is any in a set. In any case, this `restriction` lint is available - /// for situations where that additional performance is absolutely necessary. - /// - /// ### Example - /// ```no_run - /// # let c = 'c'; - /// "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); - /// ``` - /// Use instead: - /// ```no_run - /// # let c = 'c'; - /// matches!(c, '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); - /// ``` - #[clippy::version = "1.73.0"] - pub STRING_LIT_CHARS_ANY, - restriction, - "checks for `.chars().any(|i| i == c)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.map(|_| format!(..)).collect::()`. - /// - /// ### Why is this bad? - /// This allocates a new string for every element in the iterator. - /// This can be done more efficiently by creating the `String` once and appending to it in `Iterator::fold`, - /// using either the `write!` macro which supports exactly the same syntax as the `format!` macro, - /// or concatenating with `+` in case the iterator yields `&str`/`String`. - /// - /// Note also that `write!`-ing into a `String` can never fail, despite the return type of `write!` being `std::fmt::Result`, - /// so it can be safely ignored or unwrapped. - /// - /// ### Example - /// ```no_run - /// fn hex_encode(bytes: &[u8]) -> String { - /// bytes.iter().map(|b| format!("{b:02X}")).collect() - /// } - /// ``` - /// Use instead: - /// ```no_run - /// use std::fmt::Write; - /// fn hex_encode(bytes: &[u8]) -> String { - /// bytes.iter().fold(String::new(), |mut output, b| { - /// let _ = write!(output, "{b:02X}"); - /// output - /// }) - /// } - /// ``` - #[clippy::version = "1.73.0"] - pub FORMAT_COLLECT, - pedantic, - "`format!`ing every element in a collection, then collecting the strings into a new `String`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.skip(0)` on iterators. - /// - /// ### Why is this bad? - /// This was likely intended to be `.skip(1)` to skip the first element, as `.skip(0)` does - /// nothing. If not, the call should be removed. - /// - /// ### Example - /// ```no_run - /// let v = vec![1, 2, 3]; - /// let x = v.iter().skip(0).collect::>(); - /// let y = v.iter().collect::>(); - /// assert_eq!(x, y); - /// ``` - #[clippy::version = "1.73.0"] - pub ITER_SKIP_ZERO, - correctness, - "disallows `.skip(0)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `bool::then` in `Iterator::filter_map`. - /// - /// ### Why is this bad? - /// This can be written with `filter` then `map` instead, which would reduce nesting and - /// separates the filtering from the transformation phase. This comes with no cost to - /// performance and is just cleaner. - /// - /// ### Limitations - /// Does not lint `bool::then_some`, as it eagerly evaluates its arguments rather than lazily. - /// This can create differing behavior, so better safe than sorry. - /// - /// ### Example - /// ```no_run - /// # fn really_expensive_fn(i: i32) -> i32 { i } - /// # let v = vec![]; - /// _ = v.into_iter().filter_map(|i| (i % 2 == 0).then(|| really_expensive_fn(i))); - /// ``` - /// Use instead: - /// ```no_run - /// # fn really_expensive_fn(i: i32) -> i32 { i } - /// # let v = vec![]; - /// _ = v.into_iter().filter(|i| i % 2 == 0).map(|i| really_expensive_fn(i)); - /// ``` - #[clippy::version = "1.73.0"] - pub FILTER_MAP_BOOL_THEN, - style, - "checks for usage of `bool::then` in `Iterator::filter_map`" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for calls to `RwLock::write` where the lock is only used for reading. - /// - /// ### Why is this bad? - /// The write portion of `RwLock` is exclusive, meaning that no other thread - /// can access the lock while this writer is active. - /// - /// ### Example - /// ```no_run - /// use std::sync::RwLock; - /// fn assert_is_zero(lock: &RwLock) { - /// let num = lock.write().unwrap(); - /// assert_eq!(*num, 0); - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// use std::sync::RwLock; - /// fn assert_is_zero(lock: &RwLock) { - /// let num = lock.read().unwrap(); - /// assert_eq!(*num, 0); - /// } - /// ``` - #[clippy::version = "1.73.0"] - pub READONLY_WRITE_LOCK, - perf, - "acquiring a write lock when a read lock would work" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for iterator combinator calls such as `.take(x)` or `.skip(x)` - /// where `x` is greater than the amount of items that an iterator will produce. - /// - /// ### Why is this bad? - /// Taking or skipping more items than there are in an iterator either creates an iterator - /// with all items from the original iterator or an iterator with no items at all. - /// This is most likely not what the user intended to do. - /// - /// ### Example - /// ```no_run - /// for _ in [1, 2, 3].iter().take(4) {} - /// ``` - /// Use instead: - /// ```no_run - /// for _ in [1, 2, 3].iter() {} - /// ``` - #[clippy::version = "1.74.0"] - pub ITER_OUT_OF_BOUNDS, - suspicious, - "calls to `.take()` or `.skip()` that are out of bounds" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for calls to `Path::ends_with` calls where the argument looks like a file extension. - /// - /// By default, Clippy has a short list of known filenames that start with a dot - /// but aren't necessarily file extensions (e.g. the `.git` folder), which are allowed by default. - /// The `allowed-dotfiles` configuration can be used to allow additional - /// file extensions that Clippy should not lint. - /// - /// ### Why is this bad? - /// This doesn't actually compare file extensions. Rather, `ends_with` compares the given argument - /// to the last **component** of the path and checks if it matches exactly. - /// - /// ### Known issues - /// File extensions are often at most three characters long, so this only lints in those cases - /// in an attempt to avoid false positives. - /// Any extension names longer than that are assumed to likely be real path components and are - /// therefore ignored. - /// - /// ### Example - /// ```no_run - /// # use std::path::Path; - /// fn is_markdown(path: &Path) -> bool { - /// path.ends_with(".md") - /// } - /// ``` - /// Use instead: - /// ```no_run - /// # use std::path::Path; - /// fn is_markdown(path: &Path) -> bool { - /// path.extension().is_some_and(|ext| ext == "md") - /// } - /// ``` - #[clippy::version = "1.74.0"] - pub PATH_ENDS_WITH_EXT, - suspicious, - "attempting to compare file extensions using `Path::ends_with`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `as_str()` on a `String` chained with a method available on the `String` itself. - /// - /// ### Why is this bad? - /// The `as_str()` conversion is pointless and can be removed for simplicity and cleanliness. - /// - /// ### Example - /// ```no_run - /// let owned_string = "This is a string".to_owned(); - /// owned_string.as_str().as_bytes() - /// # ; - /// ``` - /// - /// Use instead: - /// ```no_run - /// let owned_string = "This is a string".to_owned(); - /// owned_string.as_bytes() - /// # ; - /// ``` - #[clippy::version = "1.74.0"] - pub REDUNDANT_AS_STR, - complexity, - "`as_str` used to call a method on `str` that is also available on `String`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `waker.clone().wake()` - /// - /// ### Why is this bad? - /// Cloning the waker is not necessary, `wake_by_ref()` enables the same operation - /// without extra cloning/dropping. - /// - /// ### Example - /// ```rust,ignore - /// waker.clone().wake(); - /// ``` - /// Should be written - /// ```rust,ignore - /// waker.wake_by_ref(); - /// ``` - #[clippy::version = "1.75.0"] - pub WAKER_CLONE_WAKE, - perf, - "cloning a `Waker` only to wake it" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `TryInto::try_into` and `TryFrom::try_from` when their infallible counterparts - /// could be used. - /// - /// ### Why is this bad? - /// In those cases, the `TryInto` and `TryFrom` trait implementation is a blanket impl that forwards - /// to `Into` or `From`, which always succeeds. - /// The returned `Result<_, Infallible>` requires error handling to get the contained value - /// even though the conversion can never fail. - /// - /// ### Example - /// ```rust - /// let _: Result = 1i32.try_into(); - /// let _: Result = <_>::try_from(1i32); - /// ``` - /// Use `from`/`into` instead: - /// ```rust - /// let _: i64 = 1i32.into(); - /// let _: i64 = <_>::from(1i32); - /// ``` - #[clippy::version = "1.75.0"] - pub UNNECESSARY_FALLIBLE_CONVERSIONS, - style, - "calling the `try_from` and `try_into` trait methods when `From`/`Into` is implemented" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `Path::join` that start with a path separator (`\\` or `/`). - /// - /// ### Why is this bad? - /// If the argument to `Path::join` starts with a separator, it will overwrite - /// the original path. If this is intentional, prefer using `Path::new` instead. - /// - /// Note the behavior is platform dependent. A leading `\\` will be accepted - /// on unix systems as part of the file name - /// - /// See [`Path::join`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join) - /// - /// ### Example - /// ```rust - /// # use std::path::{Path, PathBuf}; - /// let path = Path::new("/bin"); - /// let joined_path = path.join("/sh"); - /// assert_eq!(joined_path, PathBuf::from("/sh")); - /// ``` - /// - /// Use instead; - /// ```rust - /// # use std::path::{Path, PathBuf}; - /// let path = Path::new("/bin"); - /// - /// // If this was unintentional, remove the leading separator - /// let joined_path = path.join("sh"); - /// assert_eq!(joined_path, PathBuf::from("/bin/sh")); - /// - /// // If this was intentional, create a new path instead - /// let new = Path::new("/sh"); - /// assert_eq!(new, PathBuf::from("/sh")); - /// ``` - #[clippy::version = "1.76.0"] - pub JOIN_ABSOLUTE_PATHS, - suspicious, - "calls to `Path::join` which will overwrite the original path" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for iterators of `Result`s using `.filter(Result::is_ok).map(Result::unwrap)` that may - /// be replaced with a `.flatten()` call. - /// - /// ### Why is this bad? - /// `Result` implements `IntoIterator`. This means that `Result` can be flattened - /// automatically without suspicious-looking `unwrap` calls. - /// - /// ### Example - /// ```no_run - /// let _ = std::iter::empty::>().filter(Result::is_ok).map(Result::unwrap); - /// ``` - /// Use instead: - /// ```no_run - /// let _ = std::iter::empty::>().flatten(); - /// ``` - #[clippy::version = "1.77.0"] - pub RESULT_FILTER_MAP, - complexity, - "filtering `Result` for `Ok` then force-unwrapping, which can be one type-safe operation" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.filter(Option::is_some)` that may be replaced with a `.flatten()` call. - /// This lint will require additional changes to the follow-up calls as it affects the type. - /// - /// ### Why is this bad? - /// This pattern is often followed by manual unwrapping of the `Option`. The simplification - /// results in more readable and succinct code without the need for manual unwrapping. - /// - /// ### Example - /// ```no_run - /// vec![Some(1)].into_iter().filter(Option::is_some); - /// - /// ``` - /// Use instead: - /// ```no_run - /// vec![Some(1)].into_iter().flatten(); - /// ``` - #[clippy::version = "1.77.0"] - pub ITER_FILTER_IS_SOME, - pedantic, - "filtering an iterator over `Option`s for `Some` can be achieved with `flatten`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.filter(Result::is_ok)` that may be replaced with a `.flatten()` call. - /// This lint will require additional changes to the follow-up calls as it affects the type. - /// - /// ### Why is this bad? - /// This pattern is often followed by manual unwrapping of `Result`. The simplification - /// results in more readable and succinct code without the need for manual unwrapping. - /// - /// ### Example - /// ```no_run - /// vec![Ok::(1)].into_iter().filter(Result::is_ok); - /// - /// ``` - /// Use instead: - /// ```no_run - /// vec![Ok::(1)].into_iter().flatten(); - /// ``` - #[clippy::version = "1.77.0"] - pub ITER_FILTER_IS_OK, - pedantic, - "filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type. - /// - /// ### Why is this bad? - /// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`. - /// - /// ### Example - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option.map(|a| a > 10).unwrap_or_default(); - /// result.map(|a| a > 10).unwrap_or_default(); - /// ``` - /// Use instead: - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option.is_some_and(|a| a > 10); - /// result.is_ok_and(|a| a > 10); - /// ``` - #[clippy::version = "1.77.0"] - pub MANUAL_IS_VARIANT_AND, - pedantic, - "using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for usages of `str.trim().split("\n")` and `str.trim().split("\r\n")`. - /// - /// ### Why is this bad? - /// - /// Hard-coding the line endings makes the code less compatible. `str.lines` should be used instead. - /// - /// ### Example - /// ```no_run - /// "some\ntext\nwith\nnewlines\n".trim().split('\n'); - /// ``` - /// Use instead: - /// ```no_run - /// "some\ntext\nwith\nnewlines\n".lines(); - /// ``` - /// - /// ### Known Problems - /// - /// This lint cannot detect if the split is intentionally restricted to a single type of newline (`"\n"` or - /// `"\r\n"`), for example during the parsing of a specific file format in which precisely one newline type is - /// valid. - #[clippy::version = "1.77.0"] - pub STR_SPLIT_AT_NEWLINE, - pedantic, - "splitting a trimmed string at hard-coded newlines" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.as_ref().cloned()` and `.as_mut().cloned()` on `Option`s - /// - /// ### Why is this bad? - /// This can be written more concisely by cloning the `Option` directly. - /// - /// ### Example - /// ```no_run - /// fn foo(bar: &Option>) -> Option> { - /// bar.as_ref().cloned() - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn foo(bar: &Option>) -> Option> { - /// bar.clone() - /// } - /// ``` - #[clippy::version = "1.77.0"] - pub OPTION_AS_REF_CLONED, - pedantic, - "cloning an `Option` via `as_ref().cloned()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for unnecessary calls to `min()` or `max()` in the following cases - /// - Either both side is constant - /// - One side is clearly larger than the other, like i32::MIN and an i32 variable - /// - /// ### Why is this bad? - /// - /// In the aforementioned cases it is not necessary to call `min()` or `max()` - /// to compare values, it may even cause confusion. - /// - /// ### Example - /// ```no_run - /// let _ = 0.min(7_u32); - /// ``` - /// Use instead: - /// ```no_run - /// let _ = 0; - /// ``` - #[clippy::version = "1.81.0"] - pub UNNECESSARY_MIN_OR_MAX, - complexity, - "using 'min()/max()' when there is no need for it" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.map_or_else()` "map closure" for `Result` type. - /// - /// ### Why is this bad? - /// This can be written more concisely by using `unwrap_or_else()`. - /// - /// ### Example - /// ```no_run - /// # fn handle_error(_: ()) -> u32 { 0 } - /// let x: Result = Ok(0); - /// let y = x.map_or_else(|err| handle_error(err), |n| n); - /// ``` - /// Use instead: - /// ```no_run - /// # fn handle_error(_: ()) -> u32 { 0 } - /// let x: Result = Ok(0); - /// let y = x.unwrap_or_else(|err| handle_error(err)); - /// ``` - #[clippy::version = "1.78.0"] - pub UNNECESSARY_RESULT_MAP_OR_ELSE, - suspicious, - "making no use of the \"map closure\" when calling `.map_or_else(|err| handle_error(err), |n| n)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the manual creation of C strings (a string with a `NUL` byte at the end), either - /// through one of the `CStr` constructor functions, or more plainly by calling `.as_ptr()` - /// on a (byte) string literal with a hardcoded `\0` byte at the end. - /// - /// ### Why is this bad? - /// This can be written more concisely using `c"str"` literals and is also less error-prone, - /// because the compiler checks for interior `NUL` bytes and the terminating `NUL` byte is inserted automatically. - /// - /// ### Example - /// ```no_run - /// # use std::ffi::CStr; - /// # mod libc { pub unsafe fn puts(_: *const i8) {} } - /// fn needs_cstr(_: &CStr) {} - /// - /// needs_cstr(CStr::from_bytes_with_nul(b"Hello\0").unwrap()); - /// unsafe { libc::puts("World\0".as_ptr().cast()) } - /// ``` - /// Use instead: - /// ```no_run - /// # use std::ffi::CStr; - /// # mod libc { pub unsafe fn puts(_: *const i8) {} } - /// fn needs_cstr(_: &CStr) {} - /// - /// needs_cstr(c"Hello"); - /// unsafe { libc::puts(c"World".as_ptr()) } - /// ``` - #[clippy::version = "1.78.0"] - pub MANUAL_C_STR_LITERALS, - complexity, - r#"creating a `CStr` through functions when `c""` literals can be used"# -} - -declare_clippy_lint! { - /// ### What it does - /// Checks the usage of `.get().is_some()` or `.get().is_none()` on std map types. - /// - /// ### Why is this bad? - /// It can be done in one call with `.contains()`/`.contains_key()`. - /// - /// ### Example - /// ```no_run - /// # use std::collections::HashSet; - /// let s: HashSet = HashSet::new(); - /// if s.get("a").is_some() { - /// // code - /// } - /// ``` - /// Use instead: - /// ```no_run - /// # use std::collections::HashSet; - /// let s: HashSet = HashSet::new(); - /// if s.contains("a") { - /// // code - /// } - /// ``` - #[clippy::version = "1.78.0"] - pub UNNECESSARY_GET_THEN_CHECK, - suspicious, - "calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`" -} - -declare_clippy_lint! { - /// ### What it does - /// It identifies calls to `.is_empty()` on constant values. - /// - /// ### Why is this bad? - /// String literals and constant values are known at compile time. Checking if they - /// are empty will always return the same value. This might not be the intention of - /// the expression. - /// - /// ### Example - /// ```no_run - /// let value = ""; - /// if value.is_empty() { - /// println!("the string is empty"); - /// } - /// ``` - /// Use instead: - /// ```no_run - /// println!("the string is empty"); - /// ``` - #[clippy::version = "1.79.0"] - pub CONST_IS_EMPTY, - suspicious, - "is_empty() called on strings known at compile time" -} - -declare_clippy_lint! { - /// ### What it does - /// Converts some constructs mapping an Enum value for equality comparison. - /// - /// ### Why is this bad? - /// Calls such as `opt.map_or(false, |val| val == 5)` are needlessly long and cumbersome, - /// and can be reduced to, for example, `opt == Some(5)` assuming `opt` implements `PartialEq`. - /// Also, calls such as `opt.map_or(true, |val| val == 5)` can be reduced to - /// `opt.is_none_or(|val| val == 5)`. - /// This lint offers readability and conciseness improvements. - /// - /// ### Example - /// ```no_run - /// pub fn a(x: Option) -> (bool, bool) { - /// ( - /// x.map_or(false, |n| n == 5), - /// x.map_or(true, |n| n > 5), - /// ) - /// } - /// ``` - /// Use instead: - /// ```no_run - /// pub fn a(x: Option) -> (bool, bool) { - /// ( - /// x == Some(5), - /// x.is_none_or(|n| n > 5), - /// ) - /// } - /// ``` - #[clippy::version = "1.84.0"] - pub UNNECESSARY_MAP_OR, - style, - "reduce unnecessary calls to `.map_or(bool, …)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks if an iterator is used to check if a string is ascii. - /// - /// ### Why is this bad? - /// The `str` type already implements the `is_ascii` method. - /// - /// ### Example - /// ```no_run - /// "foo".chars().all(|c| c.is_ascii()); - /// ``` - /// Use instead: - /// ```no_run - /// "foo".is_ascii(); - /// ``` - #[clippy::version = "1.81.0"] - pub NEEDLESS_CHARACTER_ITERATION, - suspicious, - "is_ascii() called on a char iterator" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for uses of `map` which return the original item. - /// - /// ### Why is this bad? - /// `inspect` is both clearer in intent and shorter. - /// - /// ### Example - /// ```no_run - /// let x = Some(0).map(|x| { println!("{x}"); x }); - /// ``` - /// Use instead: - /// ```no_run - /// let x = Some(0).inspect(|x| println!("{x}")); - /// ``` - #[clippy::version = "1.81.0"] - pub MANUAL_INSPECT, - complexity, - "use of `map` returning the original item" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks the usage of `.first().is_some()` or `.first().is_none()` to check if a slice is - /// empty. - /// - /// ### Why is this bad? - /// Using `.is_empty()` is shorter and better communicates the intention. - /// - /// ### Example - /// ```no_run - /// let v = vec![1, 2, 3]; - /// if v.first().is_none() { - /// // The vector is empty... - /// } - /// ``` - /// Use instead: - /// ```no_run - /// let v = vec![1, 2, 3]; - /// if v.is_empty() { - /// // The vector is empty... - /// } - /// ``` - #[clippy::version = "1.83.0"] - pub UNNECESSARY_FIRST_THEN_CHECK, - complexity, - "calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`" -} - -declare_clippy_lint! { - /// ### What it does - /// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`. - /// - /// ### Why is this bad? - /// The `len()` and `is_empty()` methods are also directly available on strings, and they - /// return identical results. In particular, `len()` on a string returns the number of - /// bytes. - /// - /// ### Example - /// ``` - /// let len = "some string".as_bytes().len(); - /// let b = "some string".as_bytes().is_empty(); - /// ``` - /// Use instead: - /// ``` - /// let len = "some string".len(); - /// let b = "some string".is_empty(); - /// ``` - #[clippy::version = "1.84.0"] - pub NEEDLESS_AS_BYTES, - complexity, - "detect useless calls to `as_bytes()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`. - /// - /// ### Why is this bad? - /// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`. - /// - /// ### Example - /// ``` - /// # let mut v = [""]; - /// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a); - /// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity); - /// ``` - /// Use instead: - /// ``` - /// # let mut v = [""]; - /// let e1 = v.iter().all(|s| s.is_empty()); - /// let e2 = v.iter().any(|s| s.is_empty()); - /// ``` - #[clippy::version = "1.84.0"] - pub MAP_ALL_ANY_IDENTITY, - complexity, - "combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for `Iterator::map` over ranges without using the parameter which - /// could be more clearly expressed using `std::iter::repeat(...).take(...)` - /// or `std::iter::repeat_n`. - /// - /// ### Why is this bad? - /// - /// It expresses the intent more clearly to `take` the correct number of times - /// from a generating function than to apply a closure to each number in a - /// range only to discard them. - /// - /// ### Example - /// - /// ```no_run - /// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect(); - /// ``` - /// Use instead: - /// ```no_run - /// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect(); - /// ``` - /// - /// ### Known Issues - /// - /// This lint may suggest replacing a `Map` with a `Take`. - /// The former implements some traits that the latter does not, such as - /// `DoubleEndedIterator`. - #[clippy::version = "1.84.0"] - pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, - restriction, - "map of a trivial closure (not dependent on parameter) over a range" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for `Iterator::last` being called on a `DoubleEndedIterator`, which can be replaced - /// with `DoubleEndedIterator::next_back`. - /// - /// ### Why is this bad? - /// - /// `Iterator::last` is implemented by consuming the iterator, which is unnecessary if - /// the iterator is a `DoubleEndedIterator`. Since Rust traits do not allow specialization, - /// `Iterator::last` cannot be optimized for `DoubleEndedIterator`. - /// - /// ### Example - /// ```no_run - /// let last_arg = "echo hello world".split(' ').last(); - /// ``` - /// Use instead: - /// ```no_run - /// let last_arg = "echo hello world".split(' ').next_back(); - /// ``` - #[clippy::version = "1.86.0"] - pub DOUBLE_ENDED_ITERATOR_LAST, - perf, - "using `Iterator::last` on a `DoubleEndedIterator`" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for `NonZero*::new_unchecked()` being used in a `const` context. - /// - /// ### Why is this bad? - /// - /// Using `NonZero*::new_unchecked()` is an `unsafe` function and requires an `unsafe` context. When used in a - /// context evaluated at compilation time, `NonZero*::new().unwrap()` will provide the same result with identical - /// runtime performances while not requiring `unsafe`. - /// - /// ### Example - /// ```no_run - /// use std::num::NonZeroUsize; - /// const PLAYERS: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(3) }; - /// ``` - /// Use instead: - /// ```no_run - /// use std::num::NonZeroUsize; - /// const PLAYERS: NonZeroUsize = NonZeroUsize::new(3).unwrap(); - /// ``` - #[clippy::version = "1.86.0"] - pub USELESS_NONZERO_NEW_UNCHECKED, - complexity, - "using `NonZero::new_unchecked()` in a `const` context" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for `repeat().take()` that can be replaced with `repeat_n()`. - /// - /// ### Why is this bad? - /// - /// Using `repeat_n()` is more concise and clearer. Also, `repeat_n()` is sometimes faster than `repeat().take()` when the type of the element is non-trivial to clone because the original value can be reused for the last `.next()` call rather than always cloning. - /// - /// ### Example - /// ```no_run - /// let _ = std::iter::repeat(10).take(3); - /// ``` - /// Use instead: - /// ```no_run - /// let _ = std::iter::repeat_n(10, 3); - /// ``` - #[clippy::version = "1.86.0"] - pub MANUAL_REPEAT_N, - style, - "detect `repeat().take()` that can be replaced with `repeat_n()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for string slices immediately followed by `as_bytes`. - /// - /// ### Why is this bad? - /// It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic. - /// - /// ### Known problems - /// In some cases, the UTF-8 validation and potential panic from string slicing may be required for - /// the code's correctness. If you need to ensure the slice boundaries fall on valid UTF-8 character - /// boundaries, the original form (`s[1..5].as_bytes()`) should be preferred. - /// - /// ### Example - /// ```rust - /// let s = "Lorem ipsum"; - /// s[1..5].as_bytes(); - /// ``` - /// Use instead: - /// ```rust - /// let s = "Lorem ipsum"; - /// &s.as_bytes()[1..5]; - /// ``` - #[clippy::version = "1.86.0"] - pub SLICED_STRING_AS_BYTES, - perf, - "slicing a string and immediately calling as_bytes is less efficient and can lead to panics" -} - -declare_clippy_lint! { - /// ### What it does - /// Detect functions that end with `Option::and_then` or `Result::and_then`, and suggest using - /// the `?` operator instead. - /// - /// ### Why is this bad? - /// The `and_then` method is used to chain a computation that returns an `Option` or a `Result`. - /// This can be replaced with the `?` operator, which is more concise and idiomatic. - /// - /// ### Example - /// - /// ```no_run - /// fn test(opt: Option) -> Option { - /// opt.and_then(|n| { - /// if n > 1 { - /// Some(n + 1) - /// } else { - /// None - /// } - /// }) - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn test(opt: Option) -> Option { - /// let n = opt?; - /// if n > 1 { - /// Some(n + 1) - /// } else { - /// None - /// } - /// } - /// ``` - #[clippy::version = "1.86.0"] - pub RETURN_AND_THEN, - restriction, - "using `Option::and_then` or `Result::and_then` to chain a computation that returns an `Option` or a `Result`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `Read::bytes` on types which don't implement `BufRead`. - /// - /// ### Why is this bad? - /// The default implementation calls `read` for each byte, which can be very inefficient for data that’s not in memory, such as `File`. - /// - /// ### Example - /// ```no_run - /// use std::io::Read; - /// use std::fs::File; - /// let file = File::open("./bytes.txt").unwrap(); - /// file.bytes(); - /// ``` - /// Use instead: - /// ```no_run - /// use std::io::{BufReader, Read}; - /// use std::fs::File; - /// let file = BufReader::new(File::open("./bytes.txt").unwrap()); - /// file.bytes(); - /// ``` - #[clippy::version = "1.87.0"] - pub UNBUFFERED_BYTES, - perf, - "calling .bytes() is very inefficient when data is not in memory" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `iter().any()` on slices when it can be replaced with `contains()` and suggests doing so. - /// - /// ### Why is this bad? - /// `contains()` is more concise and idiomatic, while also being faster in some cases. - /// - /// ### Example - /// ```no_run - /// fn foo(values: &[u8]) -> bool { - /// values.iter().any(|&v| v == 10) - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn foo(values: &[u8]) -> bool { - /// values.contains(&10) - /// } - /// ``` - #[clippy::version = "1.87.0"] - pub MANUAL_CONTAINS, - perf, - "unnecessary `iter().any()` on slices that can be replaced with `contains()`" -} - -declare_clippy_lint! { - /// This lint warns on calling `io::Error::new(..)` with a kind of - /// `io::ErrorKind::Other`. - /// - /// ### Why is this bad? - /// Since Rust 1.74, there's the `io::Error::other(_)` shortcut. - /// - /// ### Example - /// ```no_run - /// use std::io; - /// let _ = io::Error::new(io::ErrorKind::Other, "bad".to_string()); - /// ``` - /// Use instead: - /// ```no_run - /// let _ = std::io::Error::other("bad".to_string()); - /// ``` - #[clippy::version = "1.87.0"] - pub IO_OTHER_ERROR, - style, - "calling `std::io::Error::new(std::io::ErrorKind::Other, _)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `std::mem::swap` with temporary values. - /// - /// ### Why is this bad? - /// Storing a new value in place of a temporary value which will - /// be dropped right after the `swap` is an inefficient way of performing - /// an assignment. The same result can be achieved by using a regular - /// assignment. - /// - /// ### Examples - /// ```no_run - /// fn replace_string(s: &mut String) { - /// std::mem::swap(s, &mut String::from("replaced")); - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn replace_string(s: &mut String) { - /// *s = String::from("replaced"); - /// } - /// ``` - /// - /// Also, swapping two temporary values has no effect, as they will - /// both be dropped right after swapping them. This is likely an indication - /// of a bug. For example, the following code swaps the references to - /// the last element of the vectors, instead of swapping the elements - /// themselves: - /// - /// ```no_run - /// fn bug(v1: &mut [i32], v2: &mut [i32]) { - /// // Incorrect: swapping temporary references (`&mut &mut` passed to swap) - /// std::mem::swap(&mut v1.last_mut().unwrap(), &mut v2.last_mut().unwrap()); - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn correct(v1: &mut [i32], v2: &mut [i32]) { - /// std::mem::swap(v1.last_mut().unwrap(), v2.last_mut().unwrap()); - /// } - /// ``` - #[clippy::version = "1.88.0"] - pub SWAP_WITH_TEMPORARY, - complexity, - "detect swap with a temporary value" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for IP addresses that could be replaced with predefined constants such as - /// `Ipv4Addr::new(127, 0, 0, 1)` instead of using the appropriate constants. - /// - /// ### Why is this bad? - /// Using specific IP addresses like `127.0.0.1` or `::1` is less clear and less maintainable than using the - /// predefined constants `Ipv4Addr::LOCALHOST` or `Ipv6Addr::LOCALHOST`. These constants improve code - /// readability, make the intent explicit, and are less error-prone. - /// - /// ### Example - /// ```no_run - /// use std::net::{Ipv4Addr, Ipv6Addr}; - /// - /// // IPv4 loopback - /// let addr_v4 = Ipv4Addr::new(127, 0, 0, 1); - /// - /// // IPv6 loopback - /// let addr_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); - /// ``` - /// Use instead: - /// ```no_run - /// use std::net::{Ipv4Addr, Ipv6Addr}; - /// - /// // IPv4 loopback - /// let addr_v4 = Ipv4Addr::LOCALHOST; - /// - /// // IPv6 loopback - /// let addr_v6 = Ipv6Addr::LOCALHOST; - /// ``` - #[clippy::version = "1.89.0"] - pub IP_CONSTANT, - pedantic, - "hardcoded localhost IP address" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `Iterator::cloned` where the original value could be used - /// instead. - /// - /// ### Why is this bad? - /// It is not always possible for the compiler to eliminate useless allocations and - /// deallocations generated by redundant `clone()`s. - /// - /// ### Example - /// ```no_run - /// let x = vec![String::new()]; - /// let _ = x.iter().cloned().map(|x| x.len()); - /// ``` - /// Use instead: - /// ```no_run - /// let x = vec![String::new()]; - /// let _ = x.iter().map(|x| x.len()); - /// ``` - #[clippy::version = "1.90.0"] - pub REDUNDANT_ITER_CLONED, - perf, - "detects redundant calls to `Iterator::cloned`" -} - #[expect(clippy::struct_excessive_bools)] struct Methods { avoid_breaking_exported_api: bool, @@ -4665,155 +321,155 @@ impl Methods { } impl_lint_pass!(Methods => [ - UNWRAP_USED, - EXPECT_USED, + unwrap_expect_used::UNWRAP_USED, + unwrap_expect_used::EXPECT_USED, SHOULD_IMPLEMENT_TRAIT, - WRONG_SELF_CONVENTION, - OK_EXPECT, - UNWRAP_OR_DEFAULT, - MAP_UNWRAP_OR, - RESULT_MAP_OR_INTO_OPTION, - OPTION_MAP_OR_NONE, - BIND_INSTEAD_OF_MAP, - OR_FUN_CALL, - OR_THEN_UNWRAP, - EXPECT_FUN_CALL, - CHARS_NEXT_CMP, - CHARS_LAST_CMP, - CLONE_ON_COPY, - CLONE_ON_REF_PTR, - COLLAPSIBLE_STR_REPLACE, - CONST_IS_EMPTY, - ITER_OVEREAGER_CLONED, - CLONED_INSTEAD_OF_COPIED, - FLAT_MAP_OPTION, - INEFFICIENT_TO_STRING, + wrong_self_convention::WRONG_SELF_CONVENTION, + ok_expect::OK_EXPECT, + or_fun_call::UNWRAP_OR_DEFAULT, + map_unwrap_or::MAP_UNWRAP_OR, + option_map_or_none::RESULT_MAP_OR_INTO_OPTION, + option_map_or_none::OPTION_MAP_OR_NONE, + bind_instead_of_map::BIND_INSTEAD_OF_MAP, + or_fun_call::OR_FUN_CALL, + or_then_unwrap::OR_THEN_UNWRAP, + expect_fun_call::EXPECT_FUN_CALL, + chars_cmp::CHARS_NEXT_CMP, + chars_cmp::CHARS_LAST_CMP, + clone_on_copy::CLONE_ON_COPY, + clone_on_ref_ptr::CLONE_ON_REF_PTR, + collapsible_str_replace::COLLAPSIBLE_STR_REPLACE, + is_empty::CONST_IS_EMPTY, + iter_overeager_cloned::ITER_OVEREAGER_CLONED, + cloned_instead_of_copied::CLONED_INSTEAD_OF_COPIED, + flat_map_option::FLAT_MAP_OPTION, + inefficient_to_string::INEFFICIENT_TO_STRING, NEW_RET_NO_SELF, - SINGLE_CHAR_ADD_STR, - SEARCH_IS_SOME, - FILTER_NEXT, - SKIP_WHILE_NEXT, - FILTER_MAP_IDENTITY, - MAP_IDENTITY, - MANUAL_FILTER_MAP, - MANUAL_FIND_MAP, - OPTION_FILTER_MAP, - FILTER_MAP_NEXT, - FLAT_MAP_IDENTITY, - MAP_FLATTEN, - ITERATOR_STEP_BY_ZERO, - ITER_NEXT_SLICE, - ITER_COUNT, - ITER_NTH, - ITER_NTH_ZERO, - BYTES_NTH, - ITER_SKIP_NEXT, - GET_UNWRAP, - GET_LAST_WITH_LEN, - STRING_EXTEND_CHARS, - ITER_CLONED_COLLECT, - ITER_WITH_DRAIN, - TYPE_ID_ON_BOX, - USELESS_ASREF, - UNNECESSARY_FOLD, - UNNECESSARY_FILTER_MAP, - UNNECESSARY_FIND_MAP, - INTO_ITER_ON_REF, - SUSPICIOUS_MAP, - UNINIT_ASSUMED_INIT, - MANUAL_SATURATING_ARITHMETIC, - ZST_OFFSET, - FILETYPE_IS_FILE, - OPTION_AS_REF_DEREF, - UNNECESSARY_LAZY_EVALUATIONS, - MAP_COLLECT_RESULT_UNIT, - FROM_ITER_INSTEAD_OF_COLLECT, - INSPECT_FOR_EACH, - IMPLICIT_CLONE, - SUSPICIOUS_TO_OWNED, - SUSPICIOUS_SPLITN, - MANUAL_STR_REPEAT, - EXTEND_WITH_DRAIN, - MANUAL_SPLIT_ONCE, - NEEDLESS_SPLITN, - UNNECESSARY_TO_OWNED, - UNNECESSARY_JOIN, - ERR_EXPECT, - NEEDLESS_OPTION_AS_DEREF, - IS_DIGIT_ASCII_RADIX, - NEEDLESS_OPTION_TAKE, - NO_EFFECT_REPLACE, - OBFUSCATED_IF_ELSE, - ITER_ON_SINGLE_ITEMS, - ITER_ON_EMPTY_COLLECTIONS, - NAIVE_BYTECOUNT, - BYTES_COUNT_TO_LEN, - CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, - GET_FIRST, - MANUAL_OK_OR, - MAP_CLONE, - MAP_ERR_IGNORE, - MUT_MUTEX_LOCK, - NONSENSICAL_OPEN_OPTIONS, - SUSPICIOUS_OPEN_OPTIONS, - PATH_BUF_PUSH_OVERWRITE, - RANGE_ZIP_WITH_LEN, - REPEAT_ONCE, - STABLE_SORT_PRIMITIVE, - UNIT_HASH, - READ_LINE_WITHOUT_TRIM, - UNNECESSARY_SORT_BY, - VEC_RESIZE_TO_ZERO, - VERBOSE_FILE_READS, - ITER_KV_MAP, - SEEK_FROM_CURRENT, - SEEK_TO_START_INSTEAD_OF_REWIND, - NEEDLESS_COLLECT, - SUSPICIOUS_COMMAND_ARG_SPACE, - CLEAR_WITH_DRAIN, - MANUAL_NEXT_BACK, - UNNECESSARY_LITERAL_UNWRAP, - DRAIN_COLLECT, - MANUAL_TRY_FOLD, - FORMAT_COLLECT, - STRING_LIT_CHARS_ANY, - ITER_SKIP_ZERO, - FILTER_MAP_BOOL_THEN, - READONLY_WRITE_LOCK, - ITER_OUT_OF_BOUNDS, - PATH_ENDS_WITH_EXT, - REDUNDANT_AS_STR, - WAKER_CLONE_WAKE, - UNNECESSARY_FALLIBLE_CONVERSIONS, - JOIN_ABSOLUTE_PATHS, - RESULT_FILTER_MAP, - ITER_FILTER_IS_SOME, - ITER_FILTER_IS_OK, - MANUAL_IS_VARIANT_AND, - STR_SPLIT_AT_NEWLINE, - OPTION_AS_REF_CLONED, - UNNECESSARY_RESULT_MAP_OR_ELSE, - MANUAL_C_STR_LITERALS, - UNNECESSARY_GET_THEN_CHECK, - UNNECESSARY_FIRST_THEN_CHECK, - NEEDLESS_CHARACTER_ITERATION, - MANUAL_INSPECT, - UNNECESSARY_MIN_OR_MAX, - NEEDLESS_AS_BYTES, - MAP_ALL_ANY_IDENTITY, - MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, - UNNECESSARY_MAP_OR, - DOUBLE_ENDED_ITERATOR_LAST, - USELESS_NONZERO_NEW_UNCHECKED, - MANUAL_REPEAT_N, - SLICED_STRING_AS_BYTES, - RETURN_AND_THEN, - UNBUFFERED_BYTES, - MANUAL_CONTAINS, - IO_OTHER_ERROR, - SWAP_WITH_TEMPORARY, - IP_CONSTANT, - REDUNDANT_ITER_CLONED, + single_char_add_str::SINGLE_CHAR_ADD_STR, + search_is_some::SEARCH_IS_SOME, + filter_next::FILTER_NEXT, + skip_while_next::SKIP_WHILE_NEXT, + filter_map_identity::FILTER_MAP_IDENTITY, + map_identity::MAP_IDENTITY, + filter_map::MANUAL_FILTER_MAP, + filter_map::MANUAL_FIND_MAP, + filter_map::OPTION_FILTER_MAP, + filter_map_next::FILTER_MAP_NEXT, + flat_map_identity::FLAT_MAP_IDENTITY, + map_flatten::MAP_FLATTEN, + iterator_step_by_zero::ITERATOR_STEP_BY_ZERO, + iter_next_slice::ITER_NEXT_SLICE, + iter_count::ITER_COUNT, + iter_nth::ITER_NTH, + iter_nth_zero::ITER_NTH_ZERO, + bytes_nth::BYTES_NTH, + iter_skip_next::ITER_SKIP_NEXT, + get_unwrap::GET_UNWRAP, + get_last_with_len::GET_LAST_WITH_LEN, + string_extend_chars::STRING_EXTEND_CHARS, + iter_cloned_collect::ITER_CLONED_COLLECT, + iter_with_drain::ITER_WITH_DRAIN, + type_id_on_box::TYPE_ID_ON_BOX, + useless_asref::USELESS_ASREF, + unnecessary_fold::UNNECESSARY_FOLD, + unnecessary_filter_map::UNNECESSARY_FILTER_MAP, + unnecessary_filter_map::UNNECESSARY_FIND_MAP, + into_iter_on_ref::INTO_ITER_ON_REF, + suspicious_map::SUSPICIOUS_MAP, + uninit_assumed_init::UNINIT_ASSUMED_INIT, + manual_saturating_arithmetic::MANUAL_SATURATING_ARITHMETIC, + zst_offset::ZST_OFFSET, + filetype_is_file::FILETYPE_IS_FILE, + option_as_ref_deref::OPTION_AS_REF_DEREF, + unnecessary_lazy_eval::UNNECESSARY_LAZY_EVALUATIONS, + map_collect_result_unit::MAP_COLLECT_RESULT_UNIT, + from_iter_instead_of_collect::FROM_ITER_INSTEAD_OF_COLLECT, + inspect_for_each::INSPECT_FOR_EACH, + implicit_clone::IMPLICIT_CLONE, + suspicious_to_owned::SUSPICIOUS_TO_OWNED, + suspicious_splitn::SUSPICIOUS_SPLITN, + manual_str_repeat::MANUAL_STR_REPEAT, + extend_with_drain::EXTEND_WITH_DRAIN, + str_splitn::MANUAL_SPLIT_ONCE, + str_splitn::NEEDLESS_SPLITN, + unnecessary_to_owned::UNNECESSARY_TO_OWNED, + unnecessary_join::UNNECESSARY_JOIN, + err_expect::ERR_EXPECT, + needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF, + is_digit_ascii_radix::IS_DIGIT_ASCII_RADIX, + needless_option_take::NEEDLESS_OPTION_TAKE, + no_effect_replace::NO_EFFECT_REPLACE, + obfuscated_if_else::OBFUSCATED_IF_ELSE, + iter_on_single_or_empty_collections::ITER_ON_SINGLE_ITEMS, + iter_on_single_or_empty_collections::ITER_ON_EMPTY_COLLECTIONS, + bytecount::NAIVE_BYTECOUNT, + bytes_count_to_len::BYTES_COUNT_TO_LEN, + case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + get_first::GET_FIRST, + manual_ok_or::MANUAL_OK_OR, + map_clone::MAP_CLONE, + map_err_ignore::MAP_ERR_IGNORE, + mut_mutex_lock::MUT_MUTEX_LOCK, + open_options::NONSENSICAL_OPEN_OPTIONS, + open_options::SUSPICIOUS_OPEN_OPTIONS, + path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE, + range_zip_with_len::RANGE_ZIP_WITH_LEN, + repeat_once::REPEAT_ONCE, + stable_sort_primitive::STABLE_SORT_PRIMITIVE, + unit_hash::UNIT_HASH, + read_line_without_trim::READ_LINE_WITHOUT_TRIM, + unnecessary_sort_by::UNNECESSARY_SORT_BY, + vec_resize_to_zero::VEC_RESIZE_TO_ZERO, + verbose_file_reads::VERBOSE_FILE_READS, + iter_kv_map::ITER_KV_MAP, + seek_from_current::SEEK_FROM_CURRENT, + seek_to_start_instead_of_rewind::SEEK_TO_START_INSTEAD_OF_REWIND, + needless_collect::NEEDLESS_COLLECT, + suspicious_command_arg_space::SUSPICIOUS_COMMAND_ARG_SPACE, + clear_with_drain::CLEAR_WITH_DRAIN, + manual_next_back::MANUAL_NEXT_BACK, + unnecessary_literal_unwrap::UNNECESSARY_LITERAL_UNWRAP, + drain_collect::DRAIN_COLLECT, + manual_try_fold::MANUAL_TRY_FOLD, + format_collect::FORMAT_COLLECT, + string_lit_chars_any::STRING_LIT_CHARS_ANY, + iter_skip_zero::ITER_SKIP_ZERO, + filter_map_bool_then::FILTER_MAP_BOOL_THEN, + readonly_write_lock::READONLY_WRITE_LOCK, + iter_out_of_bounds::ITER_OUT_OF_BOUNDS, + path_ends_with_ext::PATH_ENDS_WITH_EXT, + redundant_as_str::REDUNDANT_AS_STR, + waker_clone_wake::WAKER_CLONE_WAKE, + unnecessary_fallible_conversions::UNNECESSARY_FALLIBLE_CONVERSIONS, + join_absolute_paths::JOIN_ABSOLUTE_PATHS, + filter_map::RESULT_FILTER_MAP, + iter_filter::ITER_FILTER_IS_SOME, + iter_filter::ITER_FILTER_IS_OK, + manual_is_variant_and::MANUAL_IS_VARIANT_AND, + str_split::STR_SPLIT_AT_NEWLINE, + option_as_ref_cloned::OPTION_AS_REF_CLONED, + unnecessary_result_map_or_else::UNNECESSARY_RESULT_MAP_OR_ELSE, + manual_c_str_literals::MANUAL_C_STR_LITERALS, + unnecessary_get_then_check::UNNECESSARY_GET_THEN_CHECK, + unnecessary_first_then_check::UNNECESSARY_FIRST_THEN_CHECK, + needless_character_iteration::NEEDLESS_CHARACTER_ITERATION, + manual_inspect::MANUAL_INSPECT, + unnecessary_min_or_max::UNNECESSARY_MIN_OR_MAX, + needless_as_bytes::NEEDLESS_AS_BYTES, + map_all_any_identity::MAP_ALL_ANY_IDENTITY, + map_with_unused_argument_over_ranges::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, + unnecessary_map_or::UNNECESSARY_MAP_OR, + double_ended_iterator_last::DOUBLE_ENDED_ITERATOR_LAST, + useless_nonzero_new_unchecked::USELESS_NONZERO_NEW_UNCHECKED, + manual_repeat_n::MANUAL_REPEAT_N, + sliced_string_as_bytes::SLICED_STRING_AS_BYTES, + return_and_then::RETURN_AND_THEN, + unbuffered_bytes::UNBUFFERED_BYTES, + manual_contains::MANUAL_CONTAINS, + io_other_error::IO_OTHER_ERROR, + swap_with_temporary::SWAP_WITH_TEMPORARY, + ip_constant::IP_CONSTANT, + iter_overeager_cloned::REDUNDANT_ITER_CLONED, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -5690,10 +1346,10 @@ fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExpr }; } - lint_with_both_lhs_and_rhs!(chars_next_cmp::check, cx, info); - lint_with_both_lhs_and_rhs!(chars_last_cmp::check, cx, info); - lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info); - lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info); + lint_with_both_lhs_and_rhs!(chars_cmp::check_next, cx, info); + lint_with_both_lhs_and_rhs!(chars_cmp::check_last, cx, info); + lint_with_both_lhs_and_rhs!(chars_cmp::check_next_unwrap, cx, info); + lint_with_both_lhs_and_rhs!(chars_cmp::check_last_unwrap, cx, info); } const FN_HEADER: hir::FnHeader = hir::FnHeader { diff --git a/clippy_lints_methods/src/manual_c_str_literals.rs b/clippy_lints_methods/src/manual_c_str_literals.rs index a8445b68dd60..453c2857eea1 100644 --- a/clippy_lints_methods/src/manual_c_str_literals.rs +++ b/clippy_lints_methods/src/manual_c_str_literals.rs @@ -9,7 +9,39 @@ use rustc_lint::LateContext; use rustc_span::edition::Edition::Edition2021; use rustc_span::{Span, Symbol}; -use super::MANUAL_C_STR_LITERALS; +declare_clippy_lint! { + /// ### What it does + /// Checks for the manual creation of C strings (a string with a `NUL` byte at the end), either + /// through one of the `CStr` constructor functions, or more plainly by calling `.as_ptr()` + /// on a (byte) string literal with a hardcoded `\0` byte at the end. + /// + /// ### Why is this bad? + /// This can be written more concisely using `c"str"` literals and is also less error-prone, + /// because the compiler checks for interior `NUL` bytes and the terminating `NUL` byte is inserted automatically. + /// + /// ### Example + /// ```no_run + /// # use std::ffi::CStr; + /// # mod libc { pub unsafe fn puts(_: *const i8) {} } + /// fn needs_cstr(_: &CStr) {} + /// + /// needs_cstr(CStr::from_bytes_with_nul(b"Hello\0").unwrap()); + /// unsafe { libc::puts("World\0".as_ptr().cast()) } + /// ``` + /// Use instead: + /// ```no_run + /// # use std::ffi::CStr; + /// # mod libc { pub unsafe fn puts(_: *const i8) {} } + /// fn needs_cstr(_: &CStr) {} + /// + /// needs_cstr(c"Hello"); + /// unsafe { libc::puts(c"World".as_ptr()) } + /// ``` + #[clippy::version = "1.78.0"] + pub MANUAL_C_STR_LITERALS, + complexity, + r#"creating a `CStr` through functions when `c""` literals can be used"# +} /// Checks: /// - `b"...".as_ptr()` diff --git a/clippy_lints_methods/src/manual_contains.rs b/clippy_lints_methods/src/manual_contains.rs index 8ba0f835d3dd..01b9731021c5 100644 --- a/clippy_lints_methods/src/manual_contains.rs +++ b/clippy_lints_methods/src/manual_contains.rs @@ -11,7 +11,30 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self}; use rustc_span::source_map::Spanned; -use super::MANUAL_CONTAINS; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `iter().any()` on slices when it can be replaced with `contains()` and suggests doing so. + /// + /// ### Why is this bad? + /// `contains()` is more concise and idiomatic, while also being faster in some cases. + /// + /// ### Example + /// ```no_run + /// fn foo(values: &[u8]) -> bool { + /// values.iter().any(|&v| v == 10) + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn foo(values: &[u8]) -> bool { + /// values.contains(&10) + /// } + /// ``` + #[clippy::version = "1.87.0"] + pub MANUAL_CONTAINS, + perf, + "unnecessary `iter().any()` on slices that can be replaced with `contains()`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { let mut app = Applicability::MachineApplicable; diff --git a/clippy_lints_methods/src/manual_inspect.rs b/clippy_lints_methods/src/manual_inspect.rs index 21f2ce8b7c90..71c240261faf 100644 --- a/clippy_lints_methods/src/manual_inspect.rs +++ b/clippy_lints_methods/src/manual_inspect.rs @@ -11,7 +11,26 @@ use rustc_lint::LateContext; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_span::{DUMMY_SP, Span, Symbol}; -use super::MANUAL_INSPECT; +declare_clippy_lint! { + /// ### What it does + /// Checks for uses of `map` which return the original item. + /// + /// ### Why is this bad? + /// `inspect` is both clearer in intent and shorter. + /// + /// ### Example + /// ```no_run + /// let x = Some(0).map(|x| { println!("{x}"); x }); + /// ``` + /// Use instead: + /// ```no_run + /// let x = Some(0).inspect(|x| println!("{x}")); + /// ``` + #[clippy::version = "1.81.0"] + pub MANUAL_INSPECT, + complexity, + "use of `map` returning the original item" +} #[expect(clippy::too_many_lines)] pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: Symbol, name_span: Span, msrv: Msrv) { diff --git a/clippy_lints_methods/src/manual_is_variant_and.rs b/clippy_lints_methods/src/manual_is_variant_and.rs index 4a61c223d2c1..6939b95d61f3 100644 --- a/clippy_lints_methods/src/manual_is_variant_and.rs +++ b/clippy_lints_methods/src/manual_is_variant_and.rs @@ -10,7 +10,32 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{BytePos, Span, sym}; -use super::MANUAL_IS_VARIANT_AND; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type. + /// + /// ### Why is this bad? + /// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`. + /// + /// ### Example + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.map(|a| a > 10).unwrap_or_default(); + /// result.map(|a| a > 10).unwrap_or_default(); + /// ``` + /// Use instead: + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.is_some_and(|a| a > 10); + /// result.is_ok_and(|a| a > 10); + /// ``` + #[clippy::version = "1.77.0"] + pub MANUAL_IS_VARIANT_AND, + pedantic, + "using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints_methods/src/manual_next_back.rs b/clippy_lints_methods/src/manual_next_back.rs index 9a03559b2237..1c4ac2f45327 100644 --- a/clippy_lints_methods/src/manual_next_back.rs +++ b/clippy_lints_methods/src/manual_next_back.rs @@ -6,6 +6,29 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::symbol::sym; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.rev().next()` on a `DoubleEndedIterator` + /// + /// ### Why is this bad? + /// `.next_back()` is cleaner. + /// + /// ### Example + /// ```no_run + /// # let foo = [0; 10]; + /// foo.iter().rev().next(); + /// ``` + /// Use instead: + /// ```no_run + /// # let foo = [0; 10]; + /// foo.iter().next_back(); + /// ``` + #[clippy::version = "1.71.0"] + pub MANUAL_NEXT_BACK, + style, + "manual reverse iteration of `DoubleEndedIterator`" +} + pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, @@ -25,7 +48,7 @@ pub(super) fn check<'tcx>( { span_lint_and_sugg( cx, - super::MANUAL_NEXT_BACK, + MANUAL_NEXT_BACK, expr.span.with_lo(rev_recv.span.hi()), "manual backwards iteration", "use", diff --git a/clippy_lints_methods/src/manual_ok_or.rs b/clippy_lints_methods/src/manual_ok_or.rs index c286c5faaed3..41b05c381785 100644 --- a/clippy_lints_methods/src/manual_ok_or.rs +++ b/clippy_lints_methods/src/manual_ok_or.rs @@ -8,7 +8,31 @@ use rustc_hir::{Expr, ExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::symbol::sym; -use super::MANUAL_OK_OR; +declare_clippy_lint! { + /// ### What it does + /// + /// Finds patterns that reimplement `Option::ok_or`. + /// + /// ### Why is this bad? + /// + /// Concise code helps focusing on behavior instead of boilerplate. + /// + /// ### Examples + /// ```no_run + /// let foo: Option = None; + /// foo.map_or(Err("error"), |v| Ok(v)); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let foo: Option = None; + /// foo.ok_or("error"); + /// ``` + #[clippy::version = "1.49.0"] + pub MANUAL_OK_OR, + style, + "finds patterns that can be encoded more concisely with `Option::ok_or`" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/manual_repeat_n.rs b/clippy_lints_methods/src/manual_repeat_n.rs index 83b57cca17bf..266db40d05d5 100644 --- a/clippy_lints_methods/src/manual_repeat_n.rs +++ b/clippy_lints_methods/src/manual_repeat_n.rs @@ -7,7 +7,28 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::sym; -use super::MANUAL_REPEAT_N; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `repeat().take()` that can be replaced with `repeat_n()`. + /// + /// ### Why is this bad? + /// + /// Using `repeat_n()` is more concise and clearer. Also, `repeat_n()` is sometimes faster than `repeat().take()` when the type of the element is non-trivial to clone because the original value can be reused for the last `.next()` call rather than always cloning. + /// + /// ### Example + /// ```no_run + /// let _ = std::iter::repeat(10).take(3); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = std::iter::repeat_n(10, 3); + /// ``` + #[clippy::version = "1.86.0"] + pub MANUAL_REPEAT_N, + style, + "detect `repeat().take()` that can be replaced with `repeat_n()`" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/manual_saturating_arithmetic.rs b/clippy_lints_methods/src/manual_saturating_arithmetic.rs index c785b23bc9c4..4a57d45aee27 100644 --- a/clippy_lints_methods/src/manual_saturating_arithmetic.rs +++ b/clippy_lints_methods/src/manual_saturating_arithmetic.rs @@ -8,6 +8,35 @@ use rustc_hir::def::Res; use rustc_lint::LateContext; use rustc_middle::ty::layout::LayoutOf; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`. + /// + /// ### Why is this bad? + /// These can be written simply with `saturating_add/sub` methods. + /// + /// ### Example + /// ```no_run + /// # let y: u32 = 0; + /// # let x: u32 = 100; + /// let add = x.checked_add(y).unwrap_or(u32::MAX); + /// let sub = x.checked_sub(y).unwrap_or(u32::MIN); + /// ``` + /// + /// can be written using dedicated methods for saturating addition/subtraction as: + /// + /// ```no_run + /// # let y: u32 = 0; + /// # let x: u32 = 100; + /// let add = x.saturating_add(y); + /// let sub = x.saturating_sub(y); + /// ``` + #[clippy::version = "1.39.0"] + pub MANUAL_SATURATING_ARITHMETIC, + style, + "`.checked_add/sub(x).unwrap_or(MAX/MIN)`" +} + pub fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -48,7 +77,7 @@ pub fn check( let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( cx, - super::MANUAL_SATURATING_ARITHMETIC, + MANUAL_SATURATING_ARITHMETIC, expr.span, "manual saturating arithmetic", format!("consider using `saturating_{arith}`"), diff --git a/clippy_lints_methods/src/manual_str_repeat.rs b/clippy_lints_methods/src/manual_str_repeat.rs index 8167e4f96053..1dbecb6187ab 100644 --- a/clippy_lints_methods/src/manual_str_repeat.rs +++ b/clippy_lints_methods/src/manual_str_repeat.rs @@ -11,7 +11,27 @@ use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::sym; use std::borrow::Cow; -use super::MANUAL_STR_REPEAT; +declare_clippy_lint! { + /// ### What it does + /// Checks for manual implementations of `str::repeat` + /// + /// ### Why is this bad? + /// These are both harder to read, as well as less performant. + /// + /// ### Example + /// ```no_run + /// let x: String = std::iter::repeat('x').take(10).collect(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let x: String = "x".repeat(10); + /// ``` + #[clippy::version = "1.54.0"] + pub MANUAL_STR_REPEAT, + perf, + "manual implementation of `str::repeat`" +} enum RepeatKind { String, diff --git a/clippy_lints_methods/src/manual_try_fold.rs b/clippy_lints_methods/src/manual_try_fold.rs index 23dba47f60f4..6400e5ea4eb6 100644 --- a/clippy_lints_methods/src/manual_try_fold.rs +++ b/clippy_lints_methods/src/manual_try_fold.rs @@ -9,7 +9,34 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LintContext}; use rustc_span::{Span, sym}; -use super::MANUAL_TRY_FOLD; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `Iterator::fold` with a type that implements `Try`. + /// + /// ### Why is this bad? + /// The code should use `try_fold` instead, which short-circuits on failure, thus opening the + /// door for additional optimizations not possible with `fold` as rustc can guarantee the + /// function is never called on `None`, `Err`, etc., alleviating otherwise necessary checks. It's + /// also slightly more idiomatic. + /// + /// ### Known issues + /// This lint doesn't take into account whether a function does something on the failure case, + /// i.e., whether short-circuiting will affect behavior. Refactoring to `try_fold` is not + /// desirable in those cases. + /// + /// ### Example + /// ```no_run + /// vec![1, 2, 3].iter().fold(Some(0i32), |sum, i| sum?.checked_add(*i)); + /// ``` + /// Use instead: + /// ```no_run + /// vec![1, 2, 3].iter().try_fold(0i32, |sum, i| sum.checked_add(*i)); + /// ``` + #[clippy::version = "1.72.0"] + pub MANUAL_TRY_FOLD, + perf, + "checks for usage of `Iterator::fold` with a type that implements `Try`" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/map_all_any_identity.rs b/clippy_lints_methods/src/map_all_any_identity.rs index ac11baa2d54c..1fc9bc345f4c 100644 --- a/clippy_lints_methods/src/map_all_any_identity.rs +++ b/clippy_lints_methods/src/map_all_any_identity.rs @@ -6,7 +6,30 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::MAP_ALL_ANY_IDENTITY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`. + /// + /// ### Why is this bad? + /// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`. + /// + /// ### Example + /// ``` + /// # let mut v = [""]; + /// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a); + /// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity); + /// ``` + /// Use instead: + /// ``` + /// # let mut v = [""]; + /// let e1 = v.iter().all(|s| s.is_empty()); + /// let e2 = v.iter().any(|s| s.is_empty()); + /// ``` + #[clippy::version = "1.84.0"] + pub MAP_ALL_ANY_IDENTITY, + complexity, + "combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call" +} #[allow(clippy::too_many_arguments)] pub(super) fn check( diff --git a/clippy_lints_methods/src/map_clone.rs b/clippy_lints_methods/src/map_clone.rs index 333a33f7527d..85e0ac7e1e24 100644 --- a/clippy_lints_methods/src/map_clone.rs +++ b/clippy_lints_methods/src/map_clone.rs @@ -13,7 +13,34 @@ use rustc_middle::ty::adjustment::Adjust; use rustc_span::symbol::Ident; use rustc_span::{Span, sym}; -use super::MAP_CLONE; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `map(|x| x.clone())` or + /// dereferencing closures for `Copy` types, on `Iterator` or `Option`, + /// and suggests `cloned()` or `copied()` instead + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely + /// + /// ### Example + /// ```no_run + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.map(|i| *i); + /// ``` + /// + /// The correct use would be: + /// + /// ```no_run + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.cloned(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MAP_CLONE, + style, + "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" +} // If this `map` is called on an `Option` or a `Result` and the previous call is `as_ref`, we don't // run this lint because it would overlap with `useless_asref` which provides a better suggestion diff --git a/clippy_lints_methods/src/map_collect_result_unit.rs b/clippy_lints_methods/src/map_collect_result_unit.rs index e944eac91e7a..e4cbd7cc9883 100644 --- a/clippy_lints_methods/src/map_collect_result_unit.rs +++ b/clippy_lints_methods/src/map_collect_result_unit.rs @@ -7,7 +7,26 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::symbol::sym; -use super::MAP_COLLECT_RESULT_UNIT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.map(_).collect::()`. + /// + /// ### Why is this bad? + /// Using `try_for_each` instead is more readable and idiomatic. + /// + /// ### Example + /// ```no_run + /// (0..3).map(|t| Err(t)).collect::>(); + /// ``` + /// Use instead: + /// ```no_run + /// (0..3).try_for_each(|t| Err(t)); + /// ``` + #[clippy::version = "1.49.0"] + pub MAP_COLLECT_RESULT_UNIT, + style, + "using `.map(_).collect::()`, which can be replaced with `try_for_each`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, iter: &hir::Expr<'_>, map_fn: &hir::Expr<'_>) { // return of collect `Result<(),_>` diff --git a/clippy_lints_methods/src/map_err_ignore.rs b/clippy_lints_methods/src/map_err_ignore.rs index 5d0d4dae35fa..b03a8b2cf8bf 100644 --- a/clippy_lints_methods/src/map_err_ignore.rs +++ b/clippy_lints_methods/src/map_err_ignore.rs @@ -4,7 +4,106 @@ use rustc_hir::{CaptureBy, Closure, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::sym; -use super::MAP_ERR_IGNORE; +declare_clippy_lint! { + /// ### What it does + /// Checks for instances of `map_err(|_| Some::Enum)` + /// + /// ### Why restrict this? + /// This `map_err` throws away the original error rather than allowing the enum to + /// contain and report the cause of the error. + /// + /// ### Example + /// Before: + /// ```no_run + /// use std::fmt; + /// + /// #[derive(Debug)] + /// enum Error { + /// Indivisible, + /// Remainder(u8), + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// match self { + /// Error::Indivisible => write!(f, "could not divide input by three"), + /// Error::Remainder(remainder) => write!( + /// f, + /// "input is not divisible by three, remainder = {}", + /// remainder + /// ), + /// } + /// } + /// } + /// + /// impl std::error::Error for Error {} + /// + /// fn divisible_by_3(input: &str) -> Result<(), Error> { + /// input + /// .parse::() + /// .map_err(|_| Error::Indivisible) + /// .map(|v| v % 3) + /// .and_then(|remainder| { + /// if remainder == 0 { + /// Ok(()) + /// } else { + /// Err(Error::Remainder(remainder as u8)) + /// } + /// }) + /// } + /// ``` + /// + /// After: + /// ```rust + /// use std::{fmt, num::ParseIntError}; + /// + /// #[derive(Debug)] + /// enum Error { + /// Indivisible(ParseIntError), + /// Remainder(u8), + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// match self { + /// Error::Indivisible(_) => write!(f, "could not divide input by three"), + /// Error::Remainder(remainder) => write!( + /// f, + /// "input is not divisible by three, remainder = {}", + /// remainder + /// ), + /// } + /// } + /// } + /// + /// impl std::error::Error for Error { + /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + /// match self { + /// Error::Indivisible(source) => Some(source), + /// _ => None, + /// } + /// } + /// } + /// + /// fn divisible_by_3(input: &str) -> Result<(), Error> { + /// input + /// .parse::() + /// .map_err(Error::Indivisible) + /// .map(|v| v % 3) + /// .and_then(|remainder| { + /// if remainder == 0 { + /// Ok(()) + /// } else { + /// Err(Error::Remainder(remainder as u8)) + /// } + /// }) + /// } + /// ``` + #[clippy::version = "1.48.0"] + pub MAP_ERR_IGNORE, + restriction, + "`map_err` should not ignore the original error" +} pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) diff --git a/clippy_lints_methods/src/map_flatten.rs b/clippy_lints_methods/src/map_flatten.rs index f7bb8c1d696d..f9017dd041bf 100644 --- a/clippy_lints_methods/src/map_flatten.rs +++ b/clippy_lints_methods/src/map_flatten.rs @@ -9,7 +9,35 @@ use rustc_middle::ty; use rustc_span::Span; use rustc_span::symbol::sym; -use super::MAP_FLATTEN; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option` + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option` + /// + /// ### Example + /// ```no_run + /// let vec = vec![vec![1]]; + /// let opt = Some(5); + /// + /// vec.iter().map(|x| x.iter()).flatten(); + /// opt.map(|x| Some(x * 2)).flatten(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let vec = vec![vec![1]]; + /// # let opt = Some(5); + /// vec.iter().flat_map(|x| x.iter()); + /// opt.and_then(|x| Some(x * 2)); + /// ``` + #[clippy::version = "1.31.0"] + pub MAP_FLATTEN, + complexity, + "using combinations of `flatten` and `map` which can usually be written as a single method call" +} /// lint use of `map().flatten()` for `Iterators` and 'Options' pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) { diff --git a/clippy_lints_methods/src/map_identity.rs b/clippy_lints_methods/src/map_identity.rs index 98def66ca149..4928684d4ebc 100644 --- a/clippy_lints_methods/src/map_identity.rs +++ b/clippy_lints_methods/src/map_identity.rs @@ -7,7 +7,28 @@ use rustc_hir::{self as hir, Node, PatKind}; use rustc_lint::LateContext; use rustc_span::{Span, Symbol, sym}; -use super::MAP_IDENTITY; +declare_clippy_lint! { + /// ### What it does + /// Checks for instances of `map(f)` where `f` is the identity function. + /// + /// ### Why is this bad? + /// It can be written more concisely without the call to `map`. + /// + /// ### Example + /// ```no_run + /// let x = [1, 2, 3]; + /// let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// let x = [1, 2, 3]; + /// let y: Vec<_> = x.iter().map(|x| 2*x).collect(); + /// ``` + #[clippy::version = "1.47.0"] + pub MAP_IDENTITY, + complexity, + "using iterator.map(|x| x)" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints_methods/src/map_unwrap_or.rs b/clippy_lints_methods/src/map_unwrap_or.rs index df5a0de3392b..7f38d2520ecc 100644 --- a/clippy_lints_methods/src/map_unwrap_or.rs +++ b/clippy_lints_methods/src/map_unwrap_or.rs @@ -8,7 +8,42 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::symbol::sym; -use super::MAP_UNWRAP_OR; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or + /// `result.map(_).unwrap_or_else(_)`. + /// + /// ### Why is this bad? + /// Readability, these can be written more concisely (resp.) as + /// `option.map_or(_, _)`, `option.map_or_else(_, _)` and `result.map_or_else(_, _)`. + /// + /// ### Known problems + /// The order of the arguments is not in execution order + /// + /// ### Examples + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// # fn some_function(foo: ()) -> usize { 1 } + /// option.map(|a| a + 1).unwrap_or(0); + /// option.map(|a| a > 10).unwrap_or(false); + /// result.map(|a| a + 1).unwrap_or_else(some_function); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// # fn some_function(foo: ()) -> usize { 1 } + /// option.map_or(0, |a| a + 1); + /// option.is_some_and(|a| a > 10); + /// result.map_or_else(some_function, |a| a + 1); + /// ``` + #[clippy::version = "1.45.0"] + pub MAP_UNWRAP_OR, + pedantic, + "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`" +} /// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s /// diff --git a/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs b/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs index 22400b02c293..faba908f4dda 100644 --- a/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs +++ b/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs @@ -1,4 +1,3 @@ -use crate::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; @@ -12,6 +11,40 @@ use rustc_hir::{Body, Closure, Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::Span; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Iterator::map` over ranges without using the parameter which + /// could be more clearly expressed using `std::iter::repeat(...).take(...)` + /// or `std::iter::repeat_n`. + /// + /// ### Why is this bad? + /// + /// It expresses the intent more clearly to `take` the correct number of times + /// from a generating function than to apply a closure to each number in a + /// range only to discard them. + /// + /// ### Example + /// + /// ```no_run + /// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect(); + /// ``` + /// + /// ### Known Issues + /// + /// This lint may suggest replacing a `Map` with a `Take`. + /// The former implements some traits that the latter does not, such as + /// `DoubleEndedIterator`. + #[clippy::version = "1.84.0"] + pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, + restriction, + "map of a trivial closure (not dependent on parameter) over a range" +} + fn extract_count_with_applicability( cx: &LateContext<'_>, range: higher::Range<'_>, diff --git a/clippy_lints_methods/src/mut_mutex_lock.rs b/clippy_lints_methods/src/mut_mutex_lock.rs index 320523aceb67..9ace5023f9a4 100644 --- a/clippy_lints_methods/src/mut_mutex_lock.rs +++ b/clippy_lints_methods/src/mut_mutex_lock.rs @@ -6,7 +6,41 @@ use rustc_hir::{Expr, Mutability}; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::MUT_MUTEX_LOCK; +declare_clippy_lint! { + /// ### What it does + /// Checks for `&mut Mutex::lock` calls + /// + /// ### Why is this bad? + /// `Mutex::lock` is less efficient than + /// calling `Mutex::get_mut`. In addition you also have a statically + /// guarantee that the mutex isn't locked, instead of just a runtime + /// guarantee. + /// + /// ### Example + /// ```no_run + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let mut value = value_mutex.lock().unwrap(); + /// *value += 1; + /// ``` + /// Use instead: + /// ```no_run + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let value = value_mutex.get_mut().unwrap(); + /// *value += 1; + /// ``` + #[clippy::version = "1.49.0"] + pub MUT_MUTEX_LOCK, + style, + "`&mut Mutex::lock` does unnecessary locking" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) { if matches!(expr_custom_deref_adjustment(cx, recv), None | Some(Mutability::Mut)) diff --git a/clippy_lints_methods/src/needless_as_bytes.rs b/clippy_lints_methods/src/needless_as_bytes.rs index 635d06330e05..119fabc1b110 100644 --- a/clippy_lints_methods/src/needless_as_bytes.rs +++ b/clippy_lints_methods/src/needless_as_bytes.rs @@ -6,7 +6,30 @@ use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; -use super::NEEDLESS_AS_BYTES; +declare_clippy_lint! { + /// ### What it does + /// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`. + /// + /// ### Why is this bad? + /// The `len()` and `is_empty()` methods are also directly available on strings, and they + /// return identical results. In particular, `len()` on a string returns the number of + /// bytes. + /// + /// ### Example + /// ``` + /// let len = "some string".as_bytes().len(); + /// let b = "some string".as_bytes().is_empty(); + /// ``` + /// Use instead: + /// ``` + /// let len = "some string".len(); + /// let b = "some string".is_empty(); + /// ``` + #[clippy::version = "1.84.0"] + pub NEEDLESS_AS_BYTES, + complexity, + "detect useless calls to `as_bytes()`" +} pub fn check(cx: &LateContext<'_>, prev_method: Symbol, method: Symbol, prev_recv: &Expr<'_>, span: Span) { let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs(); diff --git a/clippy_lints_methods/src/needless_character_iteration.rs b/clippy_lints_methods/src/needless_character_iteration.rs index 71c1576cd57d..97b8b474b939 100644 --- a/clippy_lints_methods/src/needless_character_iteration.rs +++ b/clippy_lints_methods/src/needless_character_iteration.rs @@ -1,14 +1,33 @@ +use super::utils::get_last_chain_binding_hir_id; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::{is_path_diagnostic_item, path_to_local_id, peel_blocks, sym}; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind, HirId, StmtKind, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; -use super::NEEDLESS_CHARACTER_ITERATION; -use super::utils::get_last_chain_binding_hir_id; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::SpanRangeExt; -use clippy_utils::{is_path_diagnostic_item, path_to_local_id, peel_blocks, sym}; +declare_clippy_lint! { + /// ### What it does + /// Checks if an iterator is used to check if a string is ascii. + /// + /// ### Why is this bad? + /// The `str` type already implements the `is_ascii` method. + /// + /// ### Example + /// ```no_run + /// "foo".chars().all(|c| c.is_ascii()); + /// ``` + /// Use instead: + /// ```no_run + /// "foo".is_ascii(); + /// ``` + #[clippy::version = "1.81.0"] + pub NEEDLESS_CHARACTER_ITERATION, + suspicious, + "is_ascii() called on a char iterator" +} fn peels_expr_ref<'a, 'tcx>(mut expr: &'a Expr<'tcx>) -> &'a Expr<'tcx> { while let ExprKind::AddrOf(_, _, e) = expr.kind { diff --git a/clippy_lints_methods/src/needless_collect.rs b/clippy_lints_methods/src/needless_collect.rs index 0075bf166cc1..0f560a7e52de 100644 --- a/clippy_lints_methods/src/needless_collect.rs +++ b/clippy_lints_methods/src/needless_collect.rs @@ -1,6 +1,3 @@ -use std::ops::ControlFlow; - -use super::NEEDLESS_COLLECT; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; @@ -22,6 +19,32 @@ use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, AssocTag, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty}; use rustc_span::Span; use rustc_span::symbol::Ident; +use std::ops::ControlFlow; + +declare_clippy_lint! { + /// ### What it does + /// Checks for functions collecting an iterator when collect + /// is not needed. + /// + /// ### Why is this bad? + /// `collect` causes the allocation of a new data structure, + /// when this allocation may not be needed. + /// + /// ### Example + /// ```no_run + /// # let iterator = vec![1].into_iter(); + /// let len = iterator.collect::>().len(); + /// ``` + /// Use instead: + /// ```no_run + /// # let iterator = vec![1].into_iter(); + /// let len = iterator.count(); + /// ``` + #[clippy::version = "1.30.0"] + pub NEEDLESS_COLLECT, + nursery, + "collecting an iterator when collect is not needed" +} const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; diff --git a/clippy_lints_methods/src/needless_option_as_deref.rs b/clippy_lints_methods/src/needless_option_as_deref.rs index d77d044340dc..73692fcc1ecf 100644 --- a/clippy_lints_methods/src/needless_option_as_deref.rs +++ b/clippy_lints_methods/src/needless_option_as_deref.rs @@ -9,7 +9,30 @@ use rustc_hir::def::Res; use rustc_lint::LateContext; use rustc_span::Symbol; -use super::NEEDLESS_OPTION_AS_DEREF; +declare_clippy_lint! { + /// ### What it does + /// Checks for no-op uses of `Option::{as_deref, as_deref_mut}`, + /// for example, `Option<&T>::as_deref()` returns the same type. + /// + /// ### Why is this bad? + /// Redundant code and improving readability. + /// + /// ### Example + /// ```no_run + /// let a = Some(&1); + /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32> + /// ``` + /// + /// Use instead: + /// ```no_run + /// let a = Some(&1); + /// let b = a; + /// ``` + #[clippy::version = "1.57.0"] + pub NEEDLESS_OPTION_AS_DEREF, + complexity, + "no-op use of `deref` or `deref_mut` method to `Option`." +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, name: Symbol) { let typeck = cx.typeck_results(); diff --git a/clippy_lints_methods/src/needless_option_take.rs b/clippy_lints_methods/src/needless_option_take.rs index 1544a12e6ba8..45589df16c3c 100644 --- a/clippy_lints_methods/src/needless_option_take.rs +++ b/clippy_lints_methods/src/needless_option_take.rs @@ -5,7 +5,29 @@ use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::{Symbol, sym}; -use super::NEEDLESS_OPTION_TAKE; +declare_clippy_lint! { + /// ### What it does + /// Checks for calling `take` function after `as_ref`. + /// + /// ### Why is this bad? + /// Redundant code. `take` writes `None` to its argument. + /// In this case the modification is useless as it's a temporary that cannot be read from afterwards. + /// + /// ### Example + /// ```no_run + /// let x = Some(3); + /// x.as_ref().take(); + /// ``` + /// Use instead: + /// ```no_run + /// let x = Some(3); + /// x.as_ref(); + /// ``` + #[clippy::version = "1.62.0"] + pub NEEDLESS_OPTION_TAKE, + complexity, + "using `.as_ref().take()` on a temporary value" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { // Checks if expression type is equal to sym::Option and if the expr is not a syntactic place diff --git a/clippy_lints_methods/src/no_effect_replace.rs b/clippy_lints_methods/src/no_effect_replace.rs index 32f32f1b2167..a258f8e2b72a 100644 --- a/clippy_lints_methods/src/no_effect_replace.rs +++ b/clippy_lints_methods/src/no_effect_replace.rs @@ -5,7 +5,23 @@ use rustc_ast::LitKind; use rustc_hir::{ExprKind, LangItem}; use rustc_lint::LateContext; -use super::NO_EFFECT_REPLACE; +declare_clippy_lint! { + /// ### What it does + /// Checks for `replace` statements which have no effect. + /// + /// ### Why is this bad? + /// It's either a mistake or confusing. + /// + /// ### Example + /// ```no_run + /// "1234".replace("12", "12"); + /// "1234".replacen("12", "12", 1); + /// ``` + #[clippy::version = "1.63.0"] + pub NO_EFFECT_REPLACE, + suspicious, + "replace with no effect" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/obfuscated_if_else.rs b/clippy_lints_methods/src/obfuscated_if_else.rs index 604b48656aea..380a4735c506 100644 --- a/clippy_lints_methods/src/obfuscated_if_else.rs +++ b/clippy_lints_methods/src/obfuscated_if_else.rs @@ -1,4 +1,3 @@ -use super::OBFUSCATED_IF_ELSE; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::source::snippet_with_applicability; @@ -10,6 +9,35 @@ use rustc_hir::ExprKind; use rustc_lint::LateContext; use rustc_span::Symbol; +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary method chains that can be simplified into `if .. else ..`. + /// + /// ### Why is this bad? + /// This can be written more clearly with `if .. else ..` + /// + /// ### Limitations + /// This lint currently only looks for usages of + /// `.{then, then_some}(..).{unwrap_or, unwrap_or_else, unwrap_or_default}(..)`, but will be expanded + /// to account for similar patterns. + /// + /// ### Example + /// ```no_run + /// let x = true; + /// x.then_some("a").unwrap_or("b"); + /// ``` + /// Use instead: + /// ```no_run + /// let x = true; + /// if x { "a" } else { "b" }; + /// ``` + #[clippy::version = "1.64.0"] + pub OBFUSCATED_IF_ELSE, + style, + "use of `.then_some(..).unwrap_or(..)` can be written \ + more clearly with `if .. else ..`" +} + pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, diff --git a/clippy_lints_methods/src/ok_expect.rs b/clippy_lints_methods/src/ok_expect.rs index e10bc0216e5f..8dce01898e63 100644 --- a/clippy_lints_methods/src/ok_expect.rs +++ b/clippy_lints_methods/src/ok_expect.rs @@ -5,7 +5,30 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; use rustc_span::sym; -use super::OK_EXPECT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `ok().expect(..)`. + /// + /// ### Why is this bad? + /// Because you usually call `expect()` on the `Result` + /// directly to get a better error message. + /// + /// ### Example + /// ```no_run + /// # let x = Ok::<_, ()>(()); + /// x.ok().expect("why did I do this again?"); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let x = Ok::<_, ()>(()); + /// x.expect("why did I do this again?"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub OK_EXPECT, + style, + "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result" +} /// lint use of `ok().expect()` for `Result`s pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { diff --git a/clippy_lints_methods/src/open_options.rs b/clippy_lints_methods/src/open_options.rs index fd368024177a..6ebd3ed0585e 100644 --- a/clippy_lints_methods/src/open_options.rs +++ b/clippy_lints_methods/src/open_options.rs @@ -10,7 +10,64 @@ use rustc_middle::ty::Ty; use rustc_span::Span; use rustc_span::source_map::Spanned; -use super::{NONSENSICAL_OPEN_OPTIONS, SUSPICIOUS_OPEN_OPTIONS}; +declare_clippy_lint! { + /// ### What it does + /// Checks for duplicate open options as well as combinations + /// that make no sense. + /// + /// ### Why is this bad? + /// In the best case, the code will be harder to read than + /// necessary. I don't know the worst case. + /// + /// ### Example + /// ```no_run + /// use std::fs::OpenOptions; + /// + /// OpenOptions::new().read(true).truncate(true); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub NONSENSICAL_OPEN_OPTIONS, + correctness, + "nonsensical combination of options for opening a file" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for the suspicious use of `OpenOptions::create()` + /// without an explicit `OpenOptions::truncate()`. + /// + /// ### Why is this bad? + /// `create()` alone will either create a new file or open an + /// existing file. If the file already exists, it will be + /// overwritten when written to, but the file will not be + /// truncated by default. + /// If less data is written to the file + /// than it already contains, the remainder of the file will + /// remain unchanged, and the end of the file will contain old + /// data. + /// In most cases, one should either use `create_new` to ensure + /// the file is created from scratch, or ensure `truncate` is + /// called so that the truncation behaviour is explicit. `truncate(true)` + /// will ensure the file is entirely overwritten with new data, whereas + /// `truncate(false)` will explicitly keep the default behavior. + /// + /// ### Example + /// ```rust,no_run + /// use std::fs::OpenOptions; + /// + /// OpenOptions::new().create(true); + /// ``` + /// Use instead: + /// ```rust,no_run + /// use std::fs::OpenOptions; + /// + /// OpenOptions::new().create(true).truncate(true); + /// ``` + #[clippy::version = "1.77.0"] + pub SUSPICIOUS_OPEN_OPTIONS, + suspicious, + "suspicious combination of options for opening a file" +} fn is_open_options(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { is_type_diagnostic_item(cx, ty, sym::FsOpenOptions) || paths::TOKIO_IO_OPEN_OPTIONS.matches_ty(cx, ty) diff --git a/clippy_lints_methods/src/option_as_ref_cloned.rs b/clippy_lints_methods/src/option_as_ref_cloned.rs index 3c38deca6cd1..05ae955526c2 100644 --- a/clippy_lints_methods/src/option_as_ref_cloned.rs +++ b/clippy_lints_methods/src/option_as_ref_cloned.rs @@ -1,3 +1,4 @@ +use super::method_call; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sym; use clippy_utils::ty::is_type_diagnostic_item; @@ -6,7 +7,30 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::Span; -use super::{OPTION_AS_REF_CLONED, method_call}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.as_ref().cloned()` and `.as_mut().cloned()` on `Option`s + /// + /// ### Why is this bad? + /// This can be written more concisely by cloning the `Option` directly. + /// + /// ### Example + /// ```no_run + /// fn foo(bar: &Option>) -> Option> { + /// bar.as_ref().cloned() + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn foo(bar: &Option>) -> Option> { + /// bar.clone() + /// } + /// ``` + #[clippy::version = "1.77.0"] + pub OPTION_AS_REF_CLONED, + pedantic, + "cloning an `Option` via `as_ref().cloned()`" +} pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) { if let Some((method @ (sym::as_ref | sym::as_mut), as_ref_recv, [], as_ref_ident_span, _)) = diff --git a/clippy_lints_methods/src/option_as_ref_deref.rs b/clippy_lints_methods/src/option_as_ref_deref.rs index 63ee922acfa0..41b86b21b038 100644 --- a/clippy_lints_methods/src/option_as_ref_deref.rs +++ b/clippy_lints_methods/src/option_as_ref_deref.rs @@ -9,7 +9,31 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Symbol, sym}; -use super::OPTION_AS_REF_DEREF; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.as_ref().map(Deref::deref)` or its aliases (such as String::as_str). + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.as_deref()`. + /// + /// ### Example + /// ```no_run + /// # let opt = Some("".to_string()); + /// opt.as_ref().map(String::as_str) + /// # ; + /// ``` + /// Can be written as + /// ```no_run + /// # let opt = Some("".to_string()); + /// opt.as_deref() + /// # ; + /// ``` + #[clippy::version = "1.42.0"] + pub OPTION_AS_REF_DEREF, + complexity, + "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`" +} /// lint use of `_.as_ref().map(Deref::deref)` for `Option`s pub(super) fn check( diff --git a/clippy_lints_methods/src/option_map_or_none.rs b/clippy_lints_methods/src/option_map_or_none.rs index 1a273f77fb7d..15753a4e8f52 100644 --- a/clippy_lints_methods/src/option_map_or_none.rs +++ b/clippy_lints_methods/src/option_map_or_none.rs @@ -8,7 +8,58 @@ use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; use rustc_span::symbol::sym; -use super::{OPTION_MAP_OR_NONE, RESULT_MAP_OR_INTO_OPTION}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.map_or(None, _)`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.and_then(_)`. + /// + /// ### Known problems + /// The order of the arguments is not in execution order. + /// + /// ### Example + /// ```no_run + /// # let opt = Some(1); + /// opt.map_or(None, |a| Some(a + 1)); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let opt = Some(1); + /// opt.and_then(|a| Some(a + 1)); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub OPTION_MAP_OR_NONE, + style, + "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.map_or(None, Some)`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.ok()`. + /// + /// ### Example + /// ```no_run + /// # let r: Result = Ok(1); + /// assert_eq!(Some(1), r.map_or(None, Some)); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let r: Result = Ok(1); + /// assert_eq!(Some(1), r.ok()); + /// ``` + #[clippy::version = "1.44.0"] + pub RESULT_MAP_OR_INTO_OPTION, + style, + "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`" +} // The expression inside a closure may or may not have surrounding braces // which causes problems when generating a suggestion. diff --git a/clippy_lints_methods/src/option_map_unwrap_or.rs b/clippy_lints_methods/src/option_map_unwrap_or.rs index 4ba8e0109042..0b98b0bfbe33 100644 --- a/clippy_lints_methods/src/option_map_unwrap_or.rs +++ b/clippy_lints_methods/src/option_map_unwrap_or.rs @@ -12,7 +12,7 @@ use rustc_middle::hir::nested_filter; use rustc_span::{Span, sym}; use std::ops::ControlFlow; -use super::MAP_UNWRAP_OR; +use crate::map_unwrap_or::MAP_UNWRAP_OR; /// lint use of `map().unwrap_or()` for `Option`s #[expect(clippy::too_many_arguments)] diff --git a/clippy_lints_methods/src/or_fun_call.rs b/clippy_lints_methods/src/or_fun_call.rs index 6ce7dd3d4d0a..d4d372273fa2 100644 --- a/clippy_lints_methods/src/or_fun_call.rs +++ b/clippy_lints_methods/src/or_fun_call.rs @@ -14,7 +14,78 @@ use rustc_middle::ty; use rustc_span::{Span, Symbol}; use {rustc_ast as ast, rustc_hir as hir}; -use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT}; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`, + /// `.or_insert(foo(..))` etc., and suggests to use `.or_else(|| foo(..))`, + /// `.unwrap_or_else(|| foo(..))`, `.unwrap_or_default()` or `.or_default()` + /// etc. instead. + /// + /// ### Why is this bad? + /// The function will always be called. This is only bad if it allocates or + /// does some non-trivial amount of work. + /// + /// ### Known problems + /// If the function has side-effects, not calling it will change the + /// semantic of the program, but you shouldn't rely on that. + /// + /// The lint also cannot figure out whether the function you call is + /// actually expensive to call or not. + /// + /// ### Example + /// ```no_run + /// # let foo = Some(String::new()); + /// foo.unwrap_or(String::from("empty")); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let foo = Some(String::new()); + /// foo.unwrap_or_else(|| String::from("empty")); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub OR_FUN_CALL, + nursery, + "using any `*or` method with a function call, which suggests `*or_else`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usages of the following functions with an argument that constructs a default value + /// (e.g., `Default::default` or `String::new`): + /// - `unwrap_or` + /// - `unwrap_or_else` + /// - `or_insert` + /// - `or_insert_with` + /// + /// ### Why is this bad? + /// Readability. Using `unwrap_or_default` in place of `unwrap_or`/`unwrap_or_else`, or `or_default` + /// in place of `or_insert`/`or_insert_with`, is simpler and more concise. + /// + /// ### Known problems + /// In some cases, the argument of `unwrap_or`, etc. is needed for type inference. The lint uses a + /// heuristic to try to identify such cases. However, the heuristic can produce false negatives. + /// + /// ### Examples + /// ```no_run + /// # let x = Some(1); + /// # let mut map = std::collections::HashMap::::new(); + /// x.unwrap_or(Default::default()); + /// map.entry(42).or_insert_with(String::new); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let x = Some(1); + /// # let mut map = std::collections::HashMap::::new(); + /// x.unwrap_or_default(); + /// map.entry(42).or_default(); + /// ``` + #[clippy::version = "1.56.0"] + pub UNWRAP_OR_DEFAULT, + style, + "using `.unwrap_or`, etc. with an argument that constructs a default value" +} /// Checks for the `OR_FUN_CALL` lint. #[expect(clippy::too_many_lines)] diff --git a/clippy_lints_methods/src/or_then_unwrap.rs b/clippy_lints_methods/src/or_then_unwrap.rs index 3e64e15dc860..69031b6abc50 100644 --- a/clippy_lints_methods/src/or_then_unwrap.rs +++ b/clippy_lints_methods/src/or_then_unwrap.rs @@ -8,7 +8,41 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::OR_THEN_UNWRAP; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.or(…).unwrap()` calls to Options and Results. + /// + /// ### Why is this bad? + /// You should use `.unwrap_or(…)` instead for clarity. + /// + /// ### Example + /// ```no_run + /// # let fallback = "fallback"; + /// // Result + /// # type Error = &'static str; + /// # let result: Result<&str, Error> = Err("error"); + /// let value = result.or::(Ok(fallback)).unwrap(); + /// + /// // Option + /// # let option: Option<&str> = None; + /// let value = option.or(Some(fallback)).unwrap(); + /// ``` + /// Use instead: + /// ```no_run + /// # let fallback = "fallback"; + /// // Result + /// # let result: Result<&str, &str> = Err("error"); + /// let value = result.unwrap_or(fallback); + /// + /// // Option + /// # let option: Option<&str> = None; + /// let value = option.unwrap_or(fallback); + /// ``` + #[clippy::version = "1.61.0"] + pub OR_THEN_UNWRAP, + complexity, + "checks for `.or(…).unwrap()` calls to Options and Results." +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/path_buf_push_overwrite.rs b/clippy_lints_methods/src/path_buf_push_overwrite.rs index 38d9c5f16778..5a4d6c5afcad 100644 --- a/clippy_lints_methods/src/path_buf_push_overwrite.rs +++ b/clippy_lints_methods/src/path_buf_push_overwrite.rs @@ -7,7 +7,37 @@ use rustc_lint::LateContext; use rustc_span::symbol::sym; use std::path::{Component, Path}; -use super::PATH_BUF_PUSH_OVERWRITE; +declare_clippy_lint! { + /// ### What it does + ///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push) + /// calls on `PathBuf` that can cause overwrites. + /// + /// ### Why is this bad? + /// Calling `push` with a root path at the start can overwrite the + /// previous defined path. + /// + /// ### Example + /// ```no_run + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("/bar"); + /// assert_eq!(x, PathBuf::from("/bar")); + /// ``` + /// Could be written: + /// + /// ```no_run + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("bar"); + /// assert_eq!(x, PathBuf::from("/foo/bar")); + /// ``` + #[clippy::version = "1.36.0"] + pub PATH_BUF_PUSH_OVERWRITE, + nursery, + "calling `push` with file system root on `PathBuf` can overwrite it" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) diff --git a/clippy_lints_methods/src/path_ends_with_ext.rs b/clippy_lints_methods/src/path_ends_with_ext.rs index d3f513e7abd2..3693673c29dc 100644 --- a/clippy_lints_methods/src/path_ends_with_ext.rs +++ b/clippy_lints_methods/src/path_ends_with_ext.rs @@ -1,4 +1,3 @@ -use super::PATH_ENDS_WITH_EXT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet; @@ -11,6 +10,45 @@ use rustc_lint::LateContext; use rustc_span::sym; use std::fmt::Write; +declare_clippy_lint! { + /// ### What it does + /// Looks for calls to `Path::ends_with` calls where the argument looks like a file extension. + /// + /// By default, Clippy has a short list of known filenames that start with a dot + /// but aren't necessarily file extensions (e.g. the `.git` folder), which are allowed by default. + /// The `allowed-dotfiles` configuration can be used to allow additional + /// file extensions that Clippy should not lint. + /// + /// ### Why is this bad? + /// This doesn't actually compare file extensions. Rather, `ends_with` compares the given argument + /// to the last **component** of the path and checks if it matches exactly. + /// + /// ### Known issues + /// File extensions are often at most three characters long, so this only lints in those cases + /// in an attempt to avoid false positives. + /// Any extension names longer than that are assumed to likely be real path components and are + /// therefore ignored. + /// + /// ### Example + /// ```no_run + /// # use std::path::Path; + /// fn is_markdown(path: &Path) -> bool { + /// path.ends_with(".md") + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # use std::path::Path; + /// fn is_markdown(path: &Path) -> bool { + /// path.extension().is_some_and(|ext| ext == "md") + /// } + /// ``` + #[clippy::version = "1.74.0"] + pub PATH_ENDS_WITH_EXT, + suspicious, + "attempting to compare file extensions using `Path::ends_with`" +} + pub const DEFAULT_ALLOWED_DOTFILES: &[&str] = &[ "git", "svn", "gem", "npm", "vim", "env", "rnd", "ssh", "vnc", "smb", "nvm", "bin", ]; diff --git a/clippy_lints_methods/src/range_zip_with_len.rs b/clippy_lints_methods/src/range_zip_with_len.rs index 3a5e32172086..e310164e61e2 100644 --- a/clippy_lints_methods/src/range_zip_with_len.rs +++ b/clippy_lints_methods/src/range_zip_with_len.rs @@ -6,7 +6,30 @@ use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::sym; -use super::RANGE_ZIP_WITH_LEN; +declare_clippy_lint! { + /// ### What it does + /// Checks for zipping a collection with the range of + /// `0.._.len()`. + /// + /// ### Why is this bad? + /// The code is better expressed with `.enumerate()`. + /// + /// ### Example + /// ```no_run + /// # let x = vec![1]; + /// let _ = x.iter().zip(0..x.len()); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let x = vec![1]; + /// let _ = x.iter().enumerate(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub RANGE_ZIP_WITH_LEN, + complexity, + "zipping iterator with a range when `enumerate()` would do" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, zip_arg: &'tcx Expr<'_>) { if is_trait_method(cx, expr, sym::Iterator) diff --git a/clippy_lints_methods/src/read_line_without_trim.rs b/clippy_lints_methods/src/read_line_without_trim.rs index 407f2e80aff2..0fe0094b14fb 100644 --- a/clippy_lints_methods/src/read_line_without_trim.rs +++ b/clippy_lints_methods/src/read_line_without_trim.rs @@ -12,7 +12,36 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; -use super::READ_LINE_WITHOUT_TRIM; +declare_clippy_lint! { + /// ### What it does + /// Looks for calls to [`Stdin::read_line`] to read a line from the standard input + /// into a string, then later attempting to use that string for an operation that will never + /// work for strings with a trailing newline character in it (e.g. parsing into a `i32`). + /// + /// ### Why is this bad? + /// The operation will always fail at runtime no matter what the user enters, thus + /// making it a useless operation. + /// + /// ### Example + /// ```rust,ignore + /// let mut input = String::new(); + /// std::io::stdin().read_line(&mut input).expect("Failed to read a line"); + /// let num: i32 = input.parse().expect("Not a number!"); + /// assert_eq!(num, 42); // we never even get here! + /// ``` + /// Use instead: + /// ```rust,ignore + /// let mut input = String::new(); + /// std::io::stdin().read_line(&mut input).expect("Failed to read a line"); + /// let num: i32 = input.trim_end().parse().expect("Not a number!"); + /// // ^^^^^^^^^^^ remove the trailing newline + /// assert_eq!(num, 42); + /// ``` + #[clippy::version = "1.73.0"] + pub READ_LINE_WITHOUT_TRIM, + correctness, + "calling `Stdin::read_line`, then trying to parse it without first trimming" +} fn expr_is_string_literal_without_trailing_newline(expr: &Expr<'_>) -> bool { if let ExprKind::Lit(lit) = expr.kind diff --git a/clippy_lints_methods/src/readonly_write_lock.rs b/clippy_lints_methods/src/readonly_write_lock.rs index 40b6becd4532..c5898775e80b 100644 --- a/clippy_lints_methods/src/readonly_write_lock.rs +++ b/clippy_lints_methods/src/readonly_write_lock.rs @@ -1,4 +1,3 @@ -use super::READONLY_WRITE_LOCK; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::mir::{enclosing_mir, visit_local_usage}; use clippy_utils::source::snippet; @@ -9,6 +8,37 @@ use rustc_lint::LateContext; use rustc_middle::mir::{Location, START_BLOCK}; use rustc_span::sym; +declare_clippy_lint! { + /// ### What it does + /// Looks for calls to `RwLock::write` where the lock is only used for reading. + /// + /// ### Why is this bad? + /// The write portion of `RwLock` is exclusive, meaning that no other thread + /// can access the lock while this writer is active. + /// + /// ### Example + /// ```no_run + /// use std::sync::RwLock; + /// fn assert_is_zero(lock: &RwLock) { + /// let num = lock.write().unwrap(); + /// assert_eq!(*num, 0); + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// use std::sync::RwLock; + /// fn assert_is_zero(lock: &RwLock) { + /// let num = lock.read().unwrap(); + /// assert_eq!(*num, 0); + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub READONLY_WRITE_LOCK, + perf, + "acquiring a write lock when a read lock would work" +} + fn is_unwrap_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let ExprKind::MethodCall(path, receiver, [], _) = expr.kind && path.ident.name == sym::unwrap diff --git a/clippy_lints_methods/src/redundant_as_str.rs b/clippy_lints_methods/src/redundant_as_str.rs index 24de1979c631..47945a43244a 100644 --- a/clippy_lints_methods/src/redundant_as_str.rs +++ b/clippy_lints_methods/src/redundant_as_str.rs @@ -1,4 +1,3 @@ -use super::REDUNDANT_AS_STR; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; @@ -6,6 +5,32 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::Span; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `as_str()` on a `String` chained with a method available on the `String` itself. + /// + /// ### Why is this bad? + /// The `as_str()` conversion is pointless and can be removed for simplicity and cleanliness. + /// + /// ### Example + /// ```no_run + /// let owned_string = "This is a string".to_owned(); + /// owned_string.as_str().as_bytes() + /// # ; + /// ``` + /// + /// Use instead: + /// ```no_run + /// let owned_string = "This is a string".to_owned(); + /// owned_string.as_bytes() + /// # ; + /// ``` + #[clippy::version = "1.74.0"] + pub REDUNDANT_AS_STR, + complexity, + "`as_str` used to call a method on `str` that is also available on `String`" +} + pub(super) fn check( cx: &LateContext<'_>, _expr: &Expr<'_>, diff --git a/clippy_lints_methods/src/repeat_once.rs b/clippy_lints_methods/src/repeat_once.rs index 7837517ed5d8..5f49efad1a5e 100644 --- a/clippy_lints_methods/src/repeat_once.rs +++ b/clippy_lints_methods/src/repeat_once.rs @@ -6,7 +6,37 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; -use super::REPEAT_ONCE; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.repeat(1)` and suggest the following method for each types. + /// - `.to_string()` for `str` + /// - `.clone()` for `String` + /// - `.to_vec()` for `slice` + /// + /// The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if + /// they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306)) + /// + /// ### Why is this bad? + /// For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning + /// the string is the intention behind this, `clone()` should be used. + /// + /// ### Example + /// ```no_run + /// fn main() { + /// let x = String::from("hello world").repeat(1); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn main() { + /// let x = String::from("hello world").clone(); + /// } + /// ``` + #[clippy::version = "1.47.0"] + pub REPEAT_ONCE, + complexity, + "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` " +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/result_map_or_else_none.rs b/clippy_lints_methods/src/result_map_or_else_none.rs index af619c9e3bb1..65a866a8828d 100644 --- a/clippy_lints_methods/src/result_map_or_else_none.rs +++ b/clippy_lints_methods/src/result_map_or_else_none.rs @@ -8,7 +8,7 @@ use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; use rustc_span::symbol::sym; -use super::RESULT_MAP_OR_INTO_OPTION; +use crate::option_map_or_none::RESULT_MAP_OR_INTO_OPTION; /// lint use of `_.map_or_else(|_| None, Some)` for `Result`s pub(super) fn check<'tcx>( diff --git a/clippy_lints_methods/src/return_and_then.rs b/clippy_lints_methods/src/return_and_then.rs index 54f38a322b8d..d4d1ddc0c35a 100644 --- a/clippy_lints_methods/src/return_and_then.rs +++ b/clippy_lints_methods/src/return_and_then.rs @@ -11,7 +11,44 @@ use clippy_utils::ty::get_type_diagnostic_name; use clippy_utils::visitors::for_each_unconsumed_temporary; use clippy_utils::{peel_blocks, potential_return_of_enclosing_body}; -use super::RETURN_AND_THEN; +declare_clippy_lint! { + /// ### What it does + /// Detect functions that end with `Option::and_then` or `Result::and_then`, and suggest using + /// the `?` operator instead. + /// + /// ### Why is this bad? + /// The `and_then` method is used to chain a computation that returns an `Option` or a `Result`. + /// This can be replaced with the `?` operator, which is more concise and idiomatic. + /// + /// ### Example + /// + /// ```no_run + /// fn test(opt: Option) -> Option { + /// opt.and_then(|n| { + /// if n > 1 { + /// Some(n + 1) + /// } else { + /// None + /// } + /// }) + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn test(opt: Option) -> Option { + /// let n = opt?; + /// if n > 1 { + /// Some(n + 1) + /// } else { + /// None + /// } + /// } + /// ``` + #[clippy::version = "1.86.0"] + pub RETURN_AND_THEN, + restriction, + "using `Option::and_then` or `Result::and_then` to chain a computation that returns an `Option` or a `Result`" +} /// lint if `and_then` is the last expression in a block, and /// there are no references or temporaries in the receiver diff --git a/clippy_lints_methods/src/search_is_some.rs b/clippy_lints_methods/src/search_is_some.rs index 855babb797a2..07dac3846ca7 100644 --- a/clippy_lints_methods/src/search_is_some.rs +++ b/clippy_lints_methods/src/search_is_some.rs @@ -10,7 +10,36 @@ use rustc_hir::PatKind; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; -use super::SEARCH_IS_SOME; +declare_clippy_lint! { + /// ### What it does + /// Checks for an iterator or string search (such as `find()`, + /// `position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as: + /// * `_.any(_)`, or `_.contains(_)` for `is_some()`, + /// * `!_.any(_)`, or `!_.contains(_)` for `is_none()`. + /// + /// ### Example + /// ```no_run + /// let vec = vec![1]; + /// vec.iter().find(|x| **x == 0).is_some(); + /// + /// "hello world".find("world").is_none(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let vec = vec![1]; + /// vec.iter().any(|x| *x == 0); + /// + /// !"hello world".contains("world"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub SEARCH_IS_SOME, + complexity, + "using an iterator or string search followed by `is_some()` or `is_none()`, which is more succinctly expressed as a call to `any()` or `contains()` (with negation in case of `is_none()`)" +} /// lint searching an Iterator followed by `is_some()` /// or calling `find()` on a string followed by `is_some()` or `is_none()` diff --git a/clippy_lints_methods/src/seek_from_current.rs b/clippy_lints_methods/src/seek_from_current.rs index 8b51268da465..e4bf5d1f115d 100644 --- a/clippy_lints_methods/src/seek_from_current.rs +++ b/clippy_lints_methods/src/seek_from_current.rs @@ -9,7 +9,48 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::implements_trait; use clippy_utils::{is_enum_variant_ctor, sym}; -use super::SEEK_FROM_CURRENT; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks if the `seek` method of the `Seek` trait is called with `SeekFrom::Current(0)`, + /// and if it is, suggests using `stream_position` instead. + /// + /// ### Why is this bad? + /// + /// Readability. Use dedicated method. + /// + /// ### Example + /// + /// ```rust,no_run + /// use std::fs::File; + /// use std::io::{self, Write, Seek, SeekFrom}; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::create("foo.txt")?; + /// f.write_all(b"Hello")?; + /// eprintln!("Written {} bytes", f.seek(SeekFrom::Current(0))?); + /// + /// Ok(()) + /// } + /// ``` + /// Use instead: + /// ```rust,no_run + /// use std::fs::File; + /// use std::io::{self, Write, Seek, SeekFrom}; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::create("foo.txt")?; + /// f.write_all(b"Hello")?; + /// eprintln!("Written {} bytes", f.stream_position()?); + /// + /// Ok(()) + /// } + /// ``` + #[clippy::version = "1.67.0"] + pub SEEK_FROM_CURRENT, + complexity, + "use dedicated method for seek from current position" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv); diff --git a/clippy_lints_methods/src/seek_to_start_instead_of_rewind.rs b/clippy_lints_methods/src/seek_to_start_instead_of_rewind.rs index b8405a78f23a..53e4d2695f8f 100644 --- a/clippy_lints_methods/src/seek_to_start_instead_of_rewind.rs +++ b/clippy_lints_methods/src/seek_to_start_instead_of_rewind.rs @@ -8,7 +8,36 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::Span; -use super::SEEK_TO_START_INSTEAD_OF_REWIND; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for jumps to the start of a stream that implements `Seek` + /// and uses the `seek` method providing `Start` as parameter. + /// + /// ### Why is this bad? + /// + /// Readability. There is a specific method that was implemented for + /// this exact scenario. + /// + /// ### Example + /// ```no_run + /// # use std::io; + /// fn foo(t: &mut T) { + /// t.seek(io::SeekFrom::Start(0)); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # use std::io; + /// fn foo(t: &mut T) { + /// t.rewind(); + /// } + /// ``` + #[clippy::version = "1.67.0"] + pub SEEK_TO_START_INSTEAD_OF_REWIND, + complexity, + "jumping to the start of stream using `seek` method" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/single_char_add_str.rs b/clippy_lints_methods/src/single_char_add_str.rs index 058d508b0acd..c55900fa660f 100644 --- a/clippy_lints_methods/src/single_char_add_str.rs +++ b/clippy_lints_methods/src/single_char_add_str.rs @@ -1,14 +1,145 @@ -use crate::{single_char_insert_string, single_char_push_string}; -use rustc_hir as hir; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; +use rustc_ast::BorrowKind; +use rustc_errors::Applicability; +use rustc_hir::{self as hir, ExprKind}; use rustc_lint::LateContext; use rustc_span::sym; +declare_clippy_lint! { + /// ### What it does + /// Warns when using `push_str`/`insert_str` with a single-character string literal + /// where `push`/`insert` with a `char` would work fine. + /// + /// ### Why is this bad? + /// It's less clear that we are pushing a single character. + /// + /// ### Example + /// ```no_run + /// # let mut string = String::new(); + /// string.insert_str(0, "R"); + /// string.push_str("R"); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let mut string = String::new(); + /// string.insert(0, 'R'); + /// string.push('R'); + /// ``` + #[clippy::version = "1.49.0"] + pub SINGLE_CHAR_ADD_STR, + style, + "`push_str()` or `insert_str()` used with a single-character string literal as parameter" +} + pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { if cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id) { - single_char_push_string::check(cx, expr, receiver, args); + check_push_str(cx, expr, receiver, args); } else if cx.tcx.is_diagnostic_item(sym::string_insert_str, fn_def_id) { - single_char_insert_string::check(cx, expr, receiver, args); + check_insert_str(cx, expr, receiver, args); } } } + +/// lint for length-1 `str`s as argument for `push_str` +fn check_push_str(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); + let sugg = format!("{base_string_snippet}.push({extension_string})"); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `push_str()` using a single-character string literal", + "consider using `push` with a character literal", + sugg, + applicability, + ); + } + + if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind + && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind + && path_segment.ident.name == sym::to_string + && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) + { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); + let extension_string = + snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); + let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; + + let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})"); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `push_str()` using a single-character converted to string", + "consider using `push` without `to_string()`", + sugg, + applicability, + ); + } +} + +/// lint for length-1 `str`s as argument for `insert_str` +fn check_insert_str(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); + let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); + let sugg = format!("{base_string_snippet}.insert({pos_arg}, {extension_string})"); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `insert_str()` using a single-character string literal", + "consider using `insert` with a character literal", + sugg, + applicability, + ); + } + + if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind + && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind + && path_segment.ident.name == sym::to_string + && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) + { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); + let extension_string = + snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); + let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); + let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; + + let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})"); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `insert_str()` using a single-character converted to string", + "consider using `insert` without `to_string()`", + sugg, + applicability, + ); + } +} + +fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + if cx.typeck_results().expr_ty(expr).is_ref() + && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() + && ty.is_char() + { + return true; + } + + false +} + +fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + cx.typeck_results().expr_ty(expr).is_char() +} diff --git a/clippy_lints_methods/src/single_char_insert_string.rs b/clippy_lints_methods/src/single_char_insert_string.rs deleted file mode 100644 index 4a1d25deade9..000000000000 --- a/clippy_lints_methods/src/single_char_insert_string.rs +++ /dev/null @@ -1,67 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; -use rustc_ast::BorrowKind; -use rustc_errors::Applicability; -use rustc_hir::{self as hir, ExprKind}; -use rustc_lint::LateContext; - -use super::SINGLE_CHAR_ADD_STR; - -/// lint for length-1 `str`s as argument for `insert_str` -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); - let sugg = format!("{base_string_snippet}.insert({pos_arg}, {extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character string literal", - "consider using `insert` with a character literal", - sugg, - applicability, - ); - } - - if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind - && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind - && path_segment.ident.name == rustc_span::sym::to_string - && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) - { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let extension_string = - snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); - let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; - - let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character converted to string", - "consider using `insert` without `to_string()`", - sugg, - applicability, - ); - } -} - -fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if cx.typeck_results().expr_ty(expr).is_ref() - && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() - && ty.is_char() - { - return true; - } - - false -} - -fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_char() -} diff --git a/clippy_lints_methods/src/single_char_push_string.rs b/clippy_lints_methods/src/single_char_push_string.rs deleted file mode 100644 index bc271d593925..000000000000 --- a/clippy_lints_methods/src/single_char_push_string.rs +++ /dev/null @@ -1,65 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; -use rustc_ast::BorrowKind; -use rustc_errors::Applicability; -use rustc_hir::{self as hir, ExprKind}; -use rustc_lint::LateContext; - -use super::SINGLE_CHAR_ADD_STR; - -/// lint for length-1 `str`s as argument for `push_str` -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let sugg = format!("{base_string_snippet}.push({extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character string literal", - "consider using `push` with a character literal", - sugg, - applicability, - ); - } - - if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind - && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind - && path_segment.ident.name == rustc_span::sym::to_string - && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) - { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let extension_string = - snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); - let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; - - let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character converted to string", - "consider using `push` without `to_string()`", - sugg, - applicability, - ); - } -} - -fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if cx.typeck_results().expr_ty(expr).is_ref() - && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() - && ty.is_char() - { - return true; - } - - false -} - -fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_char() -} diff --git a/clippy_lints_methods/src/skip_while_next.rs b/clippy_lints_methods/src/skip_while_next.rs index 9f0b6c34ea2e..5f4d0681f546 100644 --- a/clippy_lints_methods/src/skip_while_next.rs +++ b/clippy_lints_methods/src/skip_while_next.rs @@ -4,7 +4,30 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::SKIP_WHILE_NEXT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.skip_while(condition).next()`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.find(!condition)`. + /// + /// ### Example + /// ```no_run + /// # let vec = vec![1]; + /// vec.iter().skip_while(|x| **x == 0).next(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let vec = vec![1]; + /// vec.iter().find(|x| **x != 0); + /// ``` + #[clippy::version = "1.42.0"] + pub SKIP_WHILE_NEXT, + complexity, + "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`" +} /// lint use of `skip_while().next()` for `Iterators` pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { diff --git a/clippy_lints_methods/src/sliced_string_as_bytes.rs b/clippy_lints_methods/src/sliced_string_as_bytes.rs index 6d4cfdb34f31..7377dacdde6a 100644 --- a/clippy_lints_methods/src/sliced_string_as_bytes.rs +++ b/clippy_lints_methods/src/sliced_string_as_bytes.rs @@ -5,7 +5,33 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, is_range_literal}; use rustc_lint::LateContext; -use super::SLICED_STRING_AS_BYTES; +declare_clippy_lint! { + /// ### What it does + /// Checks for string slices immediately followed by `as_bytes`. + /// + /// ### Why is this bad? + /// It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic. + /// + /// ### Known problems + /// In some cases, the UTF-8 validation and potential panic from string slicing may be required for + /// the code's correctness. If you need to ensure the slice boundaries fall on valid UTF-8 character + /// boundaries, the original form (`s[1..5].as_bytes()`) should be preferred. + /// + /// ### Example + /// ```rust + /// let s = "Lorem ipsum"; + /// s[1..5].as_bytes(); + /// ``` + /// Use instead: + /// ```rust + /// let s = "Lorem ipsum"; + /// &s.as_bytes()[1..5]; + /// ``` + #[clippy::version = "1.86.0"] + pub SLICED_STRING_AS_BYTES, + perf, + "slicing a string and immediately calling as_bytes is less efficient and can lead to panics" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) { if let ExprKind::Index(indexed, index, _) = recv.kind diff --git a/clippy_lints_methods/src/stable_sort_primitive.rs b/clippy_lints_methods/src/stable_sort_primitive.rs index aef14435d8af..0856ad7b1b13 100644 --- a/clippy_lints_methods/src/stable_sort_primitive.rs +++ b/clippy_lints_methods/src/stable_sort_primitive.rs @@ -5,7 +5,43 @@ use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use super::STABLE_SORT_PRIMITIVE; +declare_clippy_lint! { + /// ### What it does + /// When sorting primitive values (integers, bools, chars, as well + /// as arrays, slices, and tuples of such items), it is typically better to + /// use an unstable sort than a stable sort. + /// + /// ### Why is this bad? + /// Typically, using a stable sort consumes more memory and cpu cycles. + /// Because values which compare equal are identical, preserving their + /// relative order (the guarantee that a stable sort provides) means + /// nothing, while the extra costs still apply. + /// + /// ### Known problems + /// + /// As pointed out in + /// [issue #8241](https://github.com/rust-lang/rust-clippy/issues/8241), + /// a stable sort can instead be significantly faster for certain scenarios + /// (eg. when a sorted vector is extended with new data and resorted). + /// + /// For more information and benchmarking results, please refer to the + /// issue linked above. + /// + /// ### Example + /// ```no_run + /// let mut vec = vec![2, 1, 3]; + /// vec.sort(); + /// ``` + /// Use instead: + /// ```no_run + /// let mut vec = vec![2, 1, 3]; + /// vec.sort_unstable(); + /// ``` + #[clippy::version = "1.47.0"] + pub STABLE_SORT_PRIMITIVE, + pedantic, + "use of sort() when sort_unstable() is equivalent" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) diff --git a/clippy_lints_methods/src/str_split.rs b/clippy_lints_methods/src/str_split.rs index 479064a0671e..0c106b1baf24 100644 --- a/clippy_lints_methods/src/str_split.rs +++ b/clippy_lints_methods/src/str_split.rs @@ -7,7 +7,34 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; -use super::STR_SPLIT_AT_NEWLINE; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for usages of `str.trim().split("\n")` and `str.trim().split("\r\n")`. + /// + /// ### Why is this bad? + /// + /// Hard-coding the line endings makes the code less compatible. `str.lines` should be used instead. + /// + /// ### Example + /// ```no_run + /// "some\ntext\nwith\nnewlines\n".trim().split('\n'); + /// ``` + /// Use instead: + /// ```no_run + /// "some\ntext\nwith\nnewlines\n".lines(); + /// ``` + /// + /// ### Known Problems + /// + /// This lint cannot detect if the split is intentionally restricted to a single type of newline (`"\n"` or + /// `"\r\n"`), for example during the parsing of a specific file format in which precisely one newline type is + /// valid. + #[clippy::version = "1.77.0"] + pub STR_SPLIT_AT_NEWLINE, + pedantic, + "splitting a trimmed string at hard-coded newlines" +} pub(super) fn check<'a>(cx: &LateContext<'a>, expr: &'_ Expr<'_>, split_recv: &'a Expr<'_>, split_arg: &'_ Expr<'_>) { // We're looking for `A.trim().split(B)`, where the adjusted type of `A` is `&str` (e.g. an diff --git a/clippy_lints_methods/src/str_splitn.rs b/clippy_lints_methods/src/str_splitn.rs index 6f78d6c61281..c55905ecbe05 100644 --- a/clippy_lints_methods/src/str_splitn.rs +++ b/clippy_lints_methods/src/str_splitn.rs @@ -14,7 +14,64 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Span, Symbol, SyntaxContext}; -use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `str::splitn(2, _)` + /// + /// ### Why is this bad? + /// `split_once` is both clearer in intent and slightly more efficient. + /// + /// ### Example + /// ```rust,ignore + /// let s = "key=value=add"; + /// let (key, value) = s.splitn(2, '=').next_tuple()?; + /// let value = s.splitn(2, '=').nth(1)?; + /// + /// let mut parts = s.splitn(2, '='); + /// let key = parts.next()?; + /// let value = parts.next()?; + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// let s = "key=value=add"; + /// let (key, value) = s.split_once('=')?; + /// let value = s.split_once('=')?.1; + /// + /// let (key, value) = s.split_once('=')?; + /// ``` + /// + /// ### Limitations + /// The multiple statement variant currently only detects `iter.next()?`/`iter.next().unwrap()` + /// in two separate `let` statements that immediately follow the `splitn()` + #[clippy::version = "1.57.0"] + pub MANUAL_SPLIT_ONCE, + complexity, + "replace `.splitn(2, pat)` with `.split_once(pat)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `str::splitn` (or `str::rsplitn`) where using `str::split` would be the same. + /// ### Why is this bad? + /// The function `split` is simpler and there is no performance difference in these cases, considering + /// that both functions return a lazy iterator. + /// ### Example + /// ```no_run + /// let str = "key=value=add"; + /// let _ = str.splitn(3, '=').next().unwrap(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let str = "key=value=add"; + /// let _ = str.split('=').next().unwrap(); + /// ``` + #[clippy::version = "1.59.0"] + pub NEEDLESS_SPLITN, + complexity, + "usages of `str::splitn` that can be replaced with `str::split`" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints_methods/src/string_extend_chars.rs b/clippy_lints_methods/src/string_extend_chars.rs index f11a41f90f1a..a3fe671a11f3 100644 --- a/clippy_lints_methods/src/string_extend_chars.rs +++ b/clippy_lints_methods/src/string_extend_chars.rs @@ -6,7 +6,35 @@ use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use super::STRING_EXTEND_CHARS; +declare_clippy_lint! { + /// ### What it does + /// Checks for the use of `.extend(s.chars())` where s is a + /// `&str` or `String`. + /// + /// ### Why is this bad? + /// `.push_str(s)` is clearer + /// + /// ### Example + /// ```no_run + /// let abc = "abc"; + /// let def = String::from("def"); + /// let mut s = String::new(); + /// s.extend(abc.chars()); + /// s.extend(def.chars()); + /// ``` + /// The correct use would be: + /// ```no_run + /// let abc = "abc"; + /// let def = String::from("def"); + /// let mut s = String::new(); + /// s.push_str(abc); + /// s.push_str(&def); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub STRING_EXTEND_CHARS, + style, + "using `x.extend(s.chars())` where s is a `&str` or `String`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); diff --git a/clippy_lints_methods/src/string_lit_chars_any.rs b/clippy_lints_methods/src/string_lit_chars_any.rs index f0f9d30d3000..8dc915bd1c52 100644 --- a/clippy_lints_methods/src/string_lit_chars_any.rs +++ b/clippy_lints_methods/src/string_lit_chars_any.rs @@ -9,7 +9,33 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, Param, PatKind}; use rustc_lint::LateContext; use rustc_span::sym; -use super::STRING_LIT_CHARS_ANY; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.chars().any(|i| i == c)`. + /// + /// ### Why is this bad? + /// It's significantly slower than using a pattern instead, like + /// `matches!(c, '\\' | '.' | '+')`. + /// + /// Despite this being faster, this is not `perf` as this is pretty common, and is a rather nice + /// way to check if a `char` is any in a set. In any case, this `restriction` lint is available + /// for situations where that additional performance is absolutely necessary. + /// + /// ### Example + /// ```no_run + /// # let c = 'c'; + /// "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + /// ``` + /// Use instead: + /// ```no_run + /// # let c = 'c'; + /// matches!(c, '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + /// ``` + #[clippy::version = "1.73.0"] + pub STRING_LIT_CHARS_ANY, + restriction, + "checks for `.chars().any(|i| i == c)`" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/suspicious_command_arg_space.rs b/clippy_lints_methods/src/suspicious_command_arg_space.rs index c60a49067ec0..1608dc874d58 100644 --- a/clippy_lints_methods/src/suspicious_command_arg_space.rs +++ b/clippy_lints_methods/src/suspicious_command_arg_space.rs @@ -5,7 +5,31 @@ use rustc_lint::LateContext; use rustc_span::{Span, sym}; use {rustc_ast as ast, rustc_hir as hir}; -use super::SUSPICIOUS_COMMAND_ARG_SPACE; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Command::arg()` invocations that look like they + /// should be multiple arguments instead, such as `arg("-t ext2")`. + /// + /// ### Why is this bad? + /// + /// `Command::arg()` does not split arguments by space. An argument like `arg("-t ext2")` + /// will be passed as a single argument to the command, + /// which is likely not what was intended. + /// + /// ### Example + /// ```no_run + /// std::process::Command::new("echo").arg("-n hello").spawn().unwrap(); + /// ``` + /// Use instead: + /// ```no_run + /// std::process::Command::new("echo").args(["-n", "hello"]).spawn().unwrap(); + /// ``` + #[clippy::version = "1.69.0"] + pub SUSPICIOUS_COMMAND_ARG_SPACE, + suspicious, + "single command line argument that looks like it should be multiple arguments" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); diff --git a/clippy_lints_methods/src/suspicious_map.rs b/clippy_lints_methods/src/suspicious_map.rs index 788014d9bb63..3a7460930d05 100644 --- a/clippy_lints_methods/src/suspicious_map.rs +++ b/clippy_lints_methods/src/suspicious_map.rs @@ -5,7 +5,25 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::SUSPICIOUS_MAP; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `map` followed by a `count`. + /// + /// ### Why is this bad? + /// It looks suspicious. Maybe `map` was confused with `filter`. + /// If the `map` call is intentional, this should be rewritten + /// using `inspect`. Or, if you intend to drive the iterator to + /// completion, you can just use `for_each` instead. + /// + /// ### Example + /// ```no_run + /// let _ = (0..3).map(|x| x + 2).count(); + /// ``` + #[clippy::version = "1.39.0"] + pub SUSPICIOUS_MAP, + suspicious, + "suspicious usage of map" +} pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, count_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) { if is_trait_method(cx, count_recv, sym::Iterator) diff --git a/clippy_lints_methods/src/suspicious_splitn.rs b/clippy_lints_methods/src/suspicious_splitn.rs index f8b6d4349fbe..cc5ff10d8887 100644 --- a/clippy_lints_methods/src/suspicious_splitn.rs +++ b/clippy_lints_methods/src/suspicious_splitn.rs @@ -5,7 +5,36 @@ use rustc_lint::LateContext; use rustc_span::Symbol; use rustc_span::source_map::Spanned; -use super::SUSPICIOUS_SPLITN; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to [`splitn`] + /// (https://doc.rust-lang.org/std/primitive.str.html#method.splitn) and + /// related functions with either zero or one splits. + /// + /// ### Why is this bad? + /// These calls don't actually split the value and are + /// likely to be intended as a different number. + /// + /// ### Example + /// ```no_run + /// # let s = ""; + /// for x in s.splitn(1, ":") { + /// // .. + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let s = ""; + /// for x in s.splitn(2, ":") { + /// // .. + /// } + /// ``` + #[clippy::version = "1.54.0"] + pub SUSPICIOUS_SPLITN, + correctness, + "checks for `.splitn(0, ..)` and `.splitn(1, ..)`" +} pub(super) fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) { if count <= 1 diff --git a/clippy_lints_methods/src/suspicious_to_owned.rs b/clippy_lints_methods/src/suspicious_to_owned.rs index ce7aefed01f4..bc57cd1384d1 100644 --- a/clippy_lints_methods/src/suspicious_to_owned.rs +++ b/clippy_lints_methods/src/suspicious_to_owned.rs @@ -8,7 +8,53 @@ use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::{self}; use rustc_span::sym; -use super::SUSPICIOUS_TO_OWNED; +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `_.to_owned()`, on a `Cow<'_, _>`. + /// + /// ### Why is this bad? + /// Calling `to_owned()` on a `Cow` creates a clone of the `Cow` + /// itself, without taking ownership of the `Cow` contents (i.e. + /// it's equivalent to calling `Cow::clone`). + /// The similarly named `into_owned` method, on the other hand, + /// clones the `Cow` contents, effectively turning any `Cow::Borrowed` + /// into a `Cow::Owned`. + /// + /// Given the potential ambiguity, consider replacing `to_owned` + /// with `clone` for better readability or, if getting a `Cow::Owned` + /// was the original intent, using `into_owned` instead. + /// + /// ### Example + /// ```no_run + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let data = cow.to_owned(); + /// assert!(matches!(data, Cow::Borrowed(_))) + /// ``` + /// Use instead: + /// ```no_run + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let data = cow.clone(); + /// assert!(matches!(data, Cow::Borrowed(_))) + /// ``` + /// or + /// ```no_run + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let _data: String = cow.into_owned(); + /// ``` + #[clippy::version = "1.65.0"] + pub SUSPICIOUS_TO_OWNED, + suspicious, + "calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected" +} pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool { if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) diff --git a/clippy_lints_methods/src/swap_with_temporary.rs b/clippy_lints_methods/src/swap_with_temporary.rs index e378cbd6ae0a..2b0a2780d452 100644 --- a/clippy_lints_methods/src/swap_with_temporary.rs +++ b/clippy_lints_methods/src/swap_with_temporary.rs @@ -7,7 +7,52 @@ use rustc_lint::LateContext; use rustc_middle::ty::adjustment::Adjust; use rustc_span::sym; -use super::SWAP_WITH_TEMPORARY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `std::mem::swap` with temporary values. + /// + /// ### Why is this bad? + /// Storing a new value in place of a temporary value which will + /// be dropped right after the `swap` is an inefficient way of performing + /// an assignment. The same result can be achieved by using a regular + /// assignment. + /// + /// ### Examples + /// ```no_run + /// fn replace_string(s: &mut String) { + /// std::mem::swap(s, &mut String::from("replaced")); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn replace_string(s: &mut String) { + /// *s = String::from("replaced"); + /// } + /// ``` + /// + /// Also, swapping two temporary values has no effect, as they will + /// both be dropped right after swapping them. This is likely an indication + /// of a bug. For example, the following code swaps the references to + /// the last element of the vectors, instead of swapping the elements + /// themselves: + /// + /// ```no_run + /// fn bug(v1: &mut [i32], v2: &mut [i32]) { + /// // Incorrect: swapping temporary references (`&mut &mut` passed to swap) + /// std::mem::swap(&mut v1.last_mut().unwrap(), &mut v2.last_mut().unwrap()); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn correct(v1: &mut [i32], v2: &mut [i32]) { + /// std::mem::swap(v1.last_mut().unwrap(), v2.last_mut().unwrap()); + /// } + /// ``` + #[clippy::version = "1.88.0"] + pub SWAP_WITH_TEMPORARY, + complexity, + "detect swap with a temporary value" +} const MSG_TEMPORARY: &str = "this expression returns a temporary value"; const MSG_TEMPORARY_REFMUT: &str = "this is a mutable reference to a temporary value"; diff --git a/clippy_lints_methods/src/type_id_on_box.rs b/clippy_lints_methods/src/type_id_on_box.rs index 6c34f3d25a99..c416d6d767bc 100644 --- a/clippy_lints_methods/src/type_id_on_box.rs +++ b/clippy_lints_methods/src/type_id_on_box.rs @@ -1,4 +1,3 @@ -use crate::TYPE_ID_ON_BOX; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use rustc_errors::Applicability; @@ -9,6 +8,46 @@ use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::{self, ExistentialPredicate, Ty}; use rustc_span::{Span, sym}; +declare_clippy_lint! { + /// ### What it does + /// Looks for calls to `.type_id()` on a `Box`. + /// + /// ### Why is this bad? + /// This almost certainly does not do what the user expects and can lead to subtle bugs. + /// Calling `.type_id()` on a `Box` returns a fixed `TypeId` of the `Box` itself, + /// rather than returning the `TypeId` of the underlying type behind the trait object. + /// + /// For `Box` specifically (and trait objects that have `Any` as its supertrait), + /// this lint will provide a suggestion, which is to dereference the receiver explicitly + /// to go from `Box` to `dyn Any`. + /// This makes sure that `.type_id()` resolves to a dynamic call on the trait object + /// and not on the box. + /// + /// If the fixed `TypeId` of the `Box` is the intended behavior, it's better to be explicit about it + /// and write `TypeId::of::>()`: + /// this makes it clear that a fixed `TypeId` is returned and not the `TypeId` of the implementor. + /// + /// ### Example + /// ```rust,ignore + /// use std::any::{Any, TypeId}; + /// + /// let any_box: Box = Box::new(42_i32); + /// assert_eq!(any_box.type_id(), TypeId::of::()); // ⚠️ this fails! + /// ``` + /// Use instead: + /// ```no_run + /// use std::any::{Any, TypeId}; + /// + /// let any_box: Box = Box::new(42_i32); + /// assert_eq!((*any_box).type_id(), TypeId::of::()); + /// // ^ dereference first, to call `type_id` on `dyn Any` + /// ``` + #[clippy::version = "1.73.0"] + pub TYPE_ID_ON_BOX, + suspicious, + "calling `.type_id()` on a boxed trait object" +} + /// Checks if the given type is `dyn Any`, or a trait object that has `Any` as a supertrait. /// Only in those cases will its vtable have a `type_id` method that returns the implementor's /// `TypeId`, and only in those cases can we give a proper suggestion to dereference the box. diff --git a/clippy_lints_methods/src/unbuffered_bytes.rs b/clippy_lints_methods/src/unbuffered_bytes.rs index dd5566f8c8ba..4d03aaeb675e 100644 --- a/clippy_lints_methods/src/unbuffered_bytes.rs +++ b/clippy_lints_methods/src/unbuffered_bytes.rs @@ -1,4 +1,3 @@ -use super::UNBUFFERED_BYTES; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_trait_method; use clippy_utils::ty::implements_trait; @@ -6,6 +5,33 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `Read::bytes` on types which don't implement `BufRead`. + /// + /// ### Why is this bad? + /// The default implementation calls `read` for each byte, which can be very inefficient for data that’s not in memory, such as `File`. + /// + /// ### Example + /// ```no_run + /// use std::io::Read; + /// use std::fs::File; + /// let file = File::open("./bytes.txt").unwrap(); + /// file.bytes(); + /// ``` + /// Use instead: + /// ```no_run + /// use std::io::{BufReader, Read}; + /// use std::fs::File; + /// let file = BufReader::new(File::open("./bytes.txt").unwrap()); + /// file.bytes(); + /// ``` + #[clippy::version = "1.87.0"] + pub UNBUFFERED_BYTES, + perf, + "calling .bytes() is very inefficient when data is not in memory" +} + pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { // Lint if the `.bytes()` call is from the `Read` trait and the implementor is not buffered. if is_trait_method(cx, expr, sym::IoRead) diff --git a/clippy_lints_methods/src/uninit_assumed_init.rs b/clippy_lints_methods/src/uninit_assumed_init.rs index 6371fe644282..46c4a4c94956 100644 --- a/clippy_lints_methods/src/uninit_assumed_init.rs +++ b/clippy_lints_methods/src/uninit_assumed_init.rs @@ -5,7 +5,40 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::UNINIT_ASSUMED_INIT; +declare_clippy_lint! { + /// ### What it does + /// Checks for `MaybeUninit::uninit().assume_init()`. + /// + /// ### Why is this bad? + /// For most types, this is undefined behavior. + /// + /// ### Known problems + /// For now, we accept empty tuples and tuples / arrays + /// of `MaybeUninit`. There may be other types that allow uninitialized + /// data, but those are not yet rigorously defined. + /// + /// ### Example + /// ```no_run + /// // Beware the UB + /// use std::mem::MaybeUninit; + /// + /// let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; + /// ``` + /// + /// Note that the following is OK: + /// + /// ```no_run + /// use std::mem::MaybeUninit; + /// + /// let _: [MaybeUninit; 5] = unsafe { + /// MaybeUninit::uninit().assume_init() + /// }; + /// ``` + #[clippy::version = "1.39.0"] + pub UNINIT_ASSUMED_INIT, + correctness, + "`MaybeUninit::uninit().assume_init()`" +} /// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter) pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { diff --git a/clippy_lints_methods/src/unit_hash.rs b/clippy_lints_methods/src/unit_hash.rs index 3c7955bc4698..a9af8f18c080 100644 --- a/clippy_lints_methods/src/unit_hash.rs +++ b/clippy_lints_methods/src/unit_hash.rs @@ -6,7 +6,44 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::sym; -use super::UNIT_HASH; +declare_clippy_lint! { + /// ### What it does + /// Detects `().hash(_)`. + /// + /// ### Why is this bad? + /// Hashing a unit value doesn't do anything as the implementation of `Hash` for `()` is a no-op. + /// + /// ### Example + /// ```no_run + /// # use std::hash::Hash; + /// # use std::collections::hash_map::DefaultHasher; + /// # enum Foo { Empty, WithValue(u8) } + /// # use Foo::*; + /// # let mut state = DefaultHasher::new(); + /// # let my_enum = Foo::Empty; + /// match my_enum { + /// Empty => ().hash(&mut state), + /// WithValue(x) => x.hash(&mut state), + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # use std::hash::Hash; + /// # use std::collections::hash_map::DefaultHasher; + /// # enum Foo { Empty, WithValue(u8) } + /// # use Foo::*; + /// # let mut state = DefaultHasher::new(); + /// # let my_enum = Foo::Empty; + /// match my_enum { + /// Empty => 0_u8.hash(&mut state), + /// WithValue(x) => x.hash(&mut state), + /// } + /// ``` + #[clippy::version = "1.58.0"] + pub UNIT_HASH, + correctness, + "hashing a unit value, which does nothing" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { if is_trait_method(cx, expr, sym::Hash) && cx.typeck_results().expr_ty(recv).is_unit() { diff --git a/clippy_lints_methods/src/unnecessary_fallible_conversions.rs b/clippy_lints_methods/src/unnecessary_fallible_conversions.rs index ce81282ddfeb..7bacf2cf0521 100644 --- a/clippy_lints_methods/src/unnecessary_fallible_conversions.rs +++ b/clippy_lints_methods/src/unnecessary_fallible_conversions.rs @@ -8,7 +8,32 @@ use rustc_middle::ty; use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_span::{Span, sym}; -use super::UNNECESSARY_FALLIBLE_CONVERSIONS; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `TryInto::try_into` and `TryFrom::try_from` when their infallible counterparts + /// could be used. + /// + /// ### Why is this bad? + /// In those cases, the `TryInto` and `TryFrom` trait implementation is a blanket impl that forwards + /// to `Into` or `From`, which always succeeds. + /// The returned `Result<_, Infallible>` requires error handling to get the contained value + /// even though the conversion can never fail. + /// + /// ### Example + /// ```rust + /// let _: Result = 1i32.try_into(); + /// let _: Result = <_>::try_from(1i32); + /// ``` + /// Use `from`/`into` instead: + /// ```rust + /// let _: i64 = 1i32.into(); + /// let _: i64 = <_>::from(1i32); + /// ``` + #[clippy::version = "1.75.0"] + pub UNNECESSARY_FALLIBLE_CONVERSIONS, + style, + "calling the `try_from` and `try_into` trait methods when `From`/`Into` is implemented" +} #[derive(Copy, Clone)] enum SpansKind { diff --git a/clippy_lints_methods/src/unnecessary_filter_map.rs b/clippy_lints_methods/src/unnecessary_filter_map.rs index d260e0ef6e19..fa6800f19889 100644 --- a/clippy_lints_methods/src/unnecessary_filter_map.rs +++ b/clippy_lints_methods/src/unnecessary_filter_map.rs @@ -11,7 +11,65 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Symbol; -use super::{UNNECESSARY_FILTER_MAP, UNNECESSARY_FIND_MAP}; +declare_clippy_lint! { + /// ### What it does + /// Checks for `filter_map` calls that could be replaced by `filter` or `map`. + /// More specifically it checks if the closure provided is only performing one of the + /// filter or map operations and suggests the appropriate option. + /// + /// ### Why is this bad? + /// Complexity. The intent is also clearer if only a single + /// operation is being performed. + /// + /// ### Example + /// ```no_run + /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None }); + /// + /// // As there is no transformation of the argument this could be written as: + /// let _ = (0..3).filter(|&x| x > 2); + /// ``` + /// + /// ```no_run + /// let _ = (0..4).filter_map(|x| Some(x + 1)); + /// + /// // As there is no conditional check on the argument this could be written as: + /// let _ = (0..4).map(|x| x + 1); + /// ``` + #[clippy::version = "1.31.0"] + pub UNNECESSARY_FILTER_MAP, + complexity, + "using `filter_map` when a more succinct alternative exists" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `find_map` calls that could be replaced by `find` or `map`. More + /// specifically it checks if the closure provided is only performing one of the + /// find or map operations and suggests the appropriate option. + /// + /// ### Why is this bad? + /// Complexity. The intent is also clearer if only a single + /// operation is being performed. + /// + /// ### Example + /// ```no_run + /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None }); + /// + /// // As there is no transformation of the argument this could be written as: + /// let _ = (0..3).find(|&x| x > 2); + /// ``` + /// + /// ```no_run + /// let _ = (0..4).find_map(|x| Some(x + 1)); + /// + /// // As there is no conditional check on the argument this could be written as: + /// let _ = (0..4).map(|x| x + 1).next(); + /// ``` + #[clippy::version = "1.61.0"] + pub UNNECESSARY_FIND_MAP, + complexity, + "using `find_map` when a more succinct alternative exists" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/unnecessary_first_then_check.rs b/clippy_lints_methods/src/unnecessary_first_then_check.rs index d322909bef35..378c7941e845 100644 --- a/clippy_lints_methods/src/unnecessary_first_then_check.rs +++ b/clippy_lints_methods/src/unnecessary_first_then_check.rs @@ -6,7 +6,33 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::Span; -use super::UNNECESSARY_FIRST_THEN_CHECK; +declare_clippy_lint! { + /// ### What it does + /// Checks the usage of `.first().is_some()` or `.first().is_none()` to check if a slice is + /// empty. + /// + /// ### Why is this bad? + /// Using `.is_empty()` is shorter and better communicates the intention. + /// + /// ### Example + /// ```no_run + /// let v = vec![1, 2, 3]; + /// if v.first().is_none() { + /// // The vector is empty... + /// } + /// ``` + /// Use instead: + /// ```no_run + /// let v = vec![1, 2, 3]; + /// if v.is_empty() { + /// // The vector is empty... + /// } + /// ``` + #[clippy::version = "1.83.0"] + pub UNNECESSARY_FIRST_THEN_CHECK, + complexity, + "calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints_methods/src/unnecessary_fold.rs b/clippy_lints_methods/src/unnecessary_fold.rs index 8e3cc9abe832..4cecffc4f781 100644 --- a/clippy_lints_methods/src/unnecessary_fold.rs +++ b/clippy_lints_methods/src/unnecessary_fold.rs @@ -10,7 +10,29 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Span, sym}; -use super::UNNECESSARY_FOLD; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `fold` when a more succinct alternative exists. + /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`, + /// `sum` or `product`. + /// + /// ### Why is this bad? + /// Readability. + /// + /// ### Example + /// ```no_run + /// (0..3).fold(false, |acc, x| acc || x > 2); + /// ``` + /// + /// Use instead: + /// ```no_run + /// (0..3).any(|x| x > 2); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub UNNECESSARY_FOLD, + style, + "using `fold` when a more succinct alternative exists" +} /// Do we need to suggest turbofish when suggesting a replacement method? /// Changing `fold` to `sum` needs it sometimes when the return type can't be diff --git a/clippy_lints_methods/src/unnecessary_get_then_check.rs b/clippy_lints_methods/src/unnecessary_get_then_check.rs index 39fce2c40c91..a881a3542361 100644 --- a/clippy_lints_methods/src/unnecessary_get_then_check.rs +++ b/clippy_lints_methods/src/unnecessary_get_then_check.rs @@ -8,7 +8,34 @@ use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::{Span, sym}; -use super::UNNECESSARY_GET_THEN_CHECK; +declare_clippy_lint! { + /// ### What it does + /// Checks the usage of `.get().is_some()` or `.get().is_none()` on std map types. + /// + /// ### Why is this bad? + /// It can be done in one call with `.contains()`/`.contains_key()`. + /// + /// ### Example + /// ```no_run + /// # use std::collections::HashSet; + /// let s: HashSet = HashSet::new(); + /// if s.get("a").is_some() { + /// // code + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # use std::collections::HashSet; + /// let s: HashSet = HashSet::new(); + /// if s.contains("a") { + /// // code + /// } + /// ``` + #[clippy::version = "1.78.0"] + pub UNNECESSARY_GET_THEN_CHECK, + suspicious, + "calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`" +} fn is_a_std_set_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { is_type_diagnostic_item(cx, ty, sym::HashSet) || is_type_diagnostic_item(cx, ty, sym::BTreeSet) diff --git a/clippy_lints_methods/src/unnecessary_iter_cloned.rs b/clippy_lints_methods/src/unnecessary_iter_cloned.rs deleted file mode 100644 index 20cf35363d13..000000000000 --- a/clippy_lints_methods/src/unnecessary_iter_cloned.rs +++ /dev/null @@ -1,143 +0,0 @@ -use super::utils::clone_or_copy_needed; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::higher::ForLoop; -use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::{get_iterator_item_ty, implements_trait}; -use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr, path_to_local}; -use core::ops::ControlFlow; -use itertools::Itertools; -use rustc_errors::Applicability; -use rustc_hir::def_id::DefId; -use rustc_hir::{BindingMode, Expr, ExprKind, Node, PatKind}; -use rustc_lint::LateContext; -use rustc_span::{Symbol, sym}; - -use super::UNNECESSARY_TO_OWNED; - -pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool { - if let Some(parent) = get_parent_expr(cx, expr) - && let Some(callee_def_id) = fn_def_id(cx, parent) - && is_into_iter(cx, callee_def_id) - { - check_for_loop_iter(cx, parent, method_name, receiver, false) - } else { - false - } -} - -/// Checks whether `expr` is an iterator in a `for` loop and, if so, determines whether the -/// iterated-over items could be iterated over by reference. The reason why `check` above does not -/// include this code directly is so that it can be called from -/// `unnecessary_into_owned::check_into_iter_call_arg`. -pub fn check_for_loop_iter( - cx: &LateContext<'_>, - expr: &Expr<'_>, - method_name: Symbol, - receiver: &Expr<'_>, - cloned_before_iter: bool, -) -> bool { - if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent)) - && let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent) - && let (clone_or_copy_needed, references_to_binding) = clone_or_copy_needed(cx, pat, body) - && !clone_or_copy_needed - && let Some(receiver_snippet) = receiver.span.get_source_text(cx) - { - // Issue 12098 - // https://github.com/rust-lang/rust-clippy/issues/12098 - // if the assignee have `mut borrow` conflict with the iteratee - // the lint should not execute, former didn't consider the mut case - - // check whether `expr` is mutable - fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let Some(hir_id) = path_to_local(expr) - && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) - { - matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)) - } else { - true - } - } - - fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Expr<'_>) -> bool { - let mut change = false; - if let ExprKind::Block(block, ..) = body.kind { - for_each_expr_without_closures(block, |e| { - match e.kind { - ExprKind::Assign(assignee, _, _) | ExprKind::AssignOp(_, assignee, _) => { - change |= !can_mut_borrow_both(cx, caller, assignee); - }, - _ => {}, - } - // the return value has no effect but the function need one return value - ControlFlow::<()>::Continue(()) - }); - } - change - } - - if let ExprKind::Call(_, [child, ..]) = expr.kind { - // filter first layer of iterator - let mut child = child; - // get inner real caller requests for clone - while let ExprKind::MethodCall(_, caller, _, _) = child.kind { - child = caller; - } - if is_mutable(cx, child) && is_caller_or_fields_change(cx, body, child) { - // skip lint - return true; - } - } - - // the lint should not be executed if no violation happens - let snippet = if let ExprKind::MethodCall(maybe_iter_method_name, collection, [], _) = receiver.kind - && maybe_iter_method_name.ident.name == sym::iter - && let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator) - && let receiver_ty = cx.typeck_results().expr_ty(receiver) - && implements_trait(cx, receiver_ty, iterator_trait_id, &[]) - && let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty) - && let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) - && let collection_ty = cx.typeck_results().expr_ty(collection) - && implements_trait(cx, collection_ty, into_iterator_trait_id, &[]) - && let Some(into_iter_item_ty) = cx.get_associated_type(collection_ty, into_iterator_trait_id, sym::Item) - && iter_item_ty == into_iter_item_ty - && let Some(collection_snippet) = collection.span.get_source_text(cx) - { - collection_snippet - } else { - receiver_snippet - }; - span_lint_and_then( - cx, - UNNECESSARY_TO_OWNED, - expr.span, - format!("unnecessary use of `{method_name}`"), - |diag| { - // If `check_into_iter_call_arg` called `check_for_loop_iter` because a call to - // a `to_owned`-like function was removed, then the next suggestion may be - // incorrect. This is because the iterator that results from the call's removal - // could hold a reference to a resource that is used mutably. See - // https://github.com/rust-lang/rust-clippy/issues/8148. - let applicability = if cloned_before_iter { - Applicability::MaybeIncorrect - } else { - Applicability::MachineApplicable - }; - - let combined = references_to_binding - .into_iter() - .chain(vec![(expr.span, snippet.to_owned())]) - .collect_vec(); - - diag.multipart_suggestion("remove any references to the binding", combined, applicability); - }, - ); - return true; - } - false -} - -/// Returns true if the named method is `IntoIterator::into_iter`. -pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool { - Some(callee_def_id) == cx.tcx.lang_items().into_iter_fn() -} diff --git a/clippy_lints_methods/src/unnecessary_join.rs b/clippy_lints_methods/src/unnecessary_join.rs index efd1a718504c..3add04930b68 100644 --- a/clippy_lints_methods/src/unnecessary_join.rs +++ b/clippy_lints_methods/src/unnecessary_join.rs @@ -7,7 +7,37 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; -use super::UNNECESSARY_JOIN; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.collect::>().join("")` on iterators. + /// + /// ### Why is this bad? + /// `.collect::()` is more concise and might be more performant + /// + /// ### Example + /// ```no_run + /// let vector = vec!["hello", "world"]; + /// let output = vector.iter().map(|item| item.to_uppercase()).collect::>().join(""); + /// println!("{}", output); + /// ``` + /// The correct use would be: + /// ```no_run + /// let vector = vec!["hello", "world"]; + /// let output = vector.iter().map(|item| item.to_uppercase()).collect::(); + /// println!("{}", output); + /// ``` + /// ### Known problems + /// While `.collect::()` is sometimes more performant, there are cases where + /// using `.collect::()` over `.collect::>().join("")` + /// will prevent loop unrolling and will result in a negative performance impact. + /// + /// Additionally, differences have been observed between aarch64 and x86_64 assembly output, + /// with aarch64 tending to producing faster assembly in more cases when using `.collect::()` + #[clippy::version = "1.61.0"] + pub UNNECESSARY_JOIN, + pedantic, + "using `.collect::>().join(\"\")` on an iterator" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/unnecessary_lazy_eval.rs b/clippy_lints_methods/src/unnecessary_lazy_eval.rs index 71e606add526..2eec2ad381b8 100644 --- a/clippy_lints_methods/src/unnecessary_lazy_eval.rs +++ b/clippy_lints_methods/src/unnecessary_lazy_eval.rs @@ -8,7 +8,44 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::UNNECESSARY_LAZY_EVALUATIONS; +declare_clippy_lint! { + /// ### What it does + /// As the counterpart to `or_fun_call`, this lint looks for unnecessary + /// lazily evaluated closures on `Option` and `Result`. + /// + /// This lint suggests changing the following functions, when eager evaluation results in + /// simpler code: + /// - `unwrap_or_else` to `unwrap_or` + /// - `and_then` to `and` + /// - `or_else` to `or` + /// - `get_or_insert_with` to `get_or_insert` + /// - `ok_or_else` to `ok_or` + /// - `then` to `then_some` (for msrv >= 1.62.0) + /// + /// ### Why is this bad? + /// Using eager evaluation is shorter and simpler in some cases. + /// + /// ### Known problems + /// It is possible, but not recommended for `Deref` and `Index` to have + /// side effects. Eagerly evaluating them can change the semantics of the program. + /// + /// ### Example + /// ```no_run + /// let opt: Option = None; + /// + /// opt.unwrap_or_else(|| 42); + /// ``` + /// Use instead: + /// ```no_run + /// let opt: Option = None; + /// + /// opt.unwrap_or(42); + /// ``` + #[clippy::version = "1.48.0"] + pub UNNECESSARY_LAZY_EVALUATIONS, + style, + "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation" +} /// lint use of `_else(simple closure)` for `Option`s and `Result`s that can be /// replaced with `(return value of simple closure)` diff --git a/clippy_lints_methods/src/unnecessary_literal_unwrap.rs b/clippy_lints_methods/src/unnecessary_literal_unwrap.rs index cc4448192d3e..9d02078da681 100644 --- a/clippy_lints_methods/src/unnecessary_literal_unwrap.rs +++ b/clippy_lints_methods/src/unnecessary_literal_unwrap.rs @@ -7,7 +7,31 @@ use rustc_middle::ty; use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_span::Symbol; -use super::UNNECESSARY_LITERAL_UNWRAP; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.unwrap()` related calls on `Result`s and `Option`s that are constructed. + /// + /// ### Why is this bad? + /// It is better to write the value directly without the indirection. + /// + /// ### Examples + /// ```no_run + /// let val1 = Some(1).unwrap(); + /// let val2 = Ok::<_, ()>(1).unwrap(); + /// let val3 = Err::<(), _>(1).unwrap_err(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let val1 = 1; + /// let val2 = 1; + /// let val3 = 1; + /// ``` + #[clippy::version = "1.72.0"] + pub UNNECESSARY_LITERAL_UNWRAP, + complexity, + "using `unwrap()` related calls on `Result` and `Option` constructors" +} fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) -> Option<&'a hir::Ty<'a, AmbigArg>> { let args = args?; diff --git a/clippy_lints_methods/src/unnecessary_map_or.rs b/clippy_lints_methods/src/unnecessary_map_or.rs index b90748dd1585..e52701077788 100644 --- a/clippy_lints_methods/src/unnecessary_map_or.rs +++ b/clippy_lints_methods/src/unnecessary_map_or.rs @@ -13,7 +13,40 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::UNNECESSARY_MAP_OR; +declare_clippy_lint! { + /// ### What it does + /// Converts some constructs mapping an Enum value for equality comparison. + /// + /// ### Why is this bad? + /// Calls such as `opt.map_or(false, |val| val == 5)` are needlessly long and cumbersome, + /// and can be reduced to, for example, `opt == Some(5)` assuming `opt` implements `PartialEq`. + /// Also, calls such as `opt.map_or(true, |val| val == 5)` can be reduced to + /// `opt.is_none_or(|val| val == 5)`. + /// This lint offers readability and conciseness improvements. + /// + /// ### Example + /// ```no_run + /// pub fn a(x: Option) -> (bool, bool) { + /// ( + /// x.map_or(false, |n| n == 5), + /// x.map_or(true, |n| n > 5), + /// ) + /// } + /// ``` + /// Use instead: + /// ```no_run + /// pub fn a(x: Option) -> (bool, bool) { + /// ( + /// x == Some(5), + /// x.is_none_or(|n| n > 5), + /// ) + /// } + /// ``` + #[clippy::version = "1.84.0"] + pub UNNECESSARY_MAP_OR, + style, + "reduce unnecessary calls to `.map_or(bool, …)`" +} pub(super) enum Variant { Ok, diff --git a/clippy_lints_methods/src/unnecessary_min_or_max.rs b/clippy_lints_methods/src/unnecessary_min_or_max.rs index 413881d5ec99..229cc57110e3 100644 --- a/clippy_lints_methods/src/unnecessary_min_or_max.rs +++ b/clippy_lints_methods/src/unnecessary_min_or_max.rs @@ -1,16 +1,38 @@ -use std::cmp::Ordering; - -use super::UNNECESSARY_MIN_OR_MAX; use clippy_utils::consts::{ConstEvalCtxt, Constant, ConstantSource, FullInt}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; - use clippy_utils::sym; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Span, Symbol}; +use std::cmp::Ordering; + +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary calls to `min()` or `max()` in the following cases + /// - Either both side is constant + /// - One side is clearly larger than the other, like i32::MIN and an i32 variable + /// + /// ### Why is this bad? + /// + /// In the aforementioned cases it is not necessary to call `min()` or `max()` + /// to compare values, it may even cause confusion. + /// + /// ### Example + /// ```no_run + /// let _ = 0.min(7_u32); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = 0; + /// ``` + #[clippy::version = "1.81.0"] + pub UNNECESSARY_MIN_OR_MAX, + complexity, + "using 'min()/max()' when there is no need for it" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/unnecessary_result_map_or_else.rs b/clippy_lints_methods/src/unnecessary_result_map_or_else.rs index f84d0d6dff0a..74783090bc0c 100644 --- a/clippy_lints_methods/src/unnecessary_result_map_or_else.rs +++ b/clippy_lints_methods/src/unnecessary_result_map_or_else.rs @@ -1,3 +1,4 @@ +use super::utils::get_last_chain_binding_hir_id; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::peel_blocks; use clippy_utils::source::snippet; @@ -8,8 +9,30 @@ use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath}; use rustc_lint::LateContext; use rustc_span::symbol::sym; -use super::UNNECESSARY_RESULT_MAP_OR_ELSE; -use super::utils::get_last_chain_binding_hir_id; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map_or_else()` "map closure" for `Result` type. + /// + /// ### Why is this bad? + /// This can be written more concisely by using `unwrap_or_else()`. + /// + /// ### Example + /// ```no_run + /// # fn handle_error(_: ()) -> u32 { 0 } + /// let x: Result = Ok(0); + /// let y = x.map_or_else(|err| handle_error(err), |n| n); + /// ``` + /// Use instead: + /// ```no_run + /// # fn handle_error(_: ()) -> u32 { 0 } + /// let x: Result = Ok(0); + /// let y = x.unwrap_or_else(|err| handle_error(err)); + /// ``` + #[clippy::version = "1.78.0"] + pub UNNECESSARY_RESULT_MAP_OR_ELSE, + suspicious, + "making no use of the \"map closure\" when calling `.map_or_else(|err| handle_error(err), |n| n)`" +} fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) { let msg = "unused \"map closure\" when calling `Result::map_or_else` value"; diff --git a/clippy_lints_methods/src/unnecessary_sort_by.rs b/clippy_lints_methods/src/unnecessary_sort_by.rs index edd4ecd2b4ac..68c216211eb7 100644 --- a/clippy_lints_methods/src/unnecessary_sort_by.rs +++ b/clippy_lints_methods/src/unnecessary_sort_by.rs @@ -11,7 +11,39 @@ use rustc_span::sym; use rustc_span::symbol::Ident; use std::iter; -use super::UNNECESSARY_SORT_BY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `Vec::sort_by` passing in a closure + /// which compares the two arguments, either directly or indirectly. + /// + /// ### Why is this bad? + /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if + /// possible) than to use `Vec::sort_by` and a more complicated + /// closure. + /// + /// ### Known problems + /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already + /// imported by a use statement, then it will need to be added manually. + /// + /// ### Example + /// ```no_run + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec = Vec::new(); + /// vec.sort_by(|a, b| a.foo().cmp(&b.foo())); + /// ``` + /// Use instead: + /// ```no_run + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec = Vec::new(); + /// vec.sort_by_key(|a| a.foo()); + /// ``` + #[clippy::version = "1.46.0"] + pub UNNECESSARY_SORT_BY, + complexity, + "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer" +} enum LintTrigger { Sort(SortDetection), diff --git a/clippy_lints_methods/src/unnecessary_to_owned.rs b/clippy_lints_methods/src/unnecessary_to_owned.rs index 769526d131bf..a09a1a87539e 100644 --- a/clippy_lints_methods/src/unnecessary_to_owned.rs +++ b/clippy_lints_methods/src/unnecessary_to_owned.rs @@ -1,18 +1,20 @@ use super::implicit_clone::is_clone_like; -use super::unnecessary_iter_cloned::{self, is_into_iter}; +use crate::utils::clone_or_copy_needed; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::higher::ForLoop; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{SpanRangeExt, snippet}; use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item}; -use clippy_utils::visitors::find_all_ret_expressions; +use clippy_utils::visitors::{find_all_ret_expressions, for_each_expr_without_closures}; use clippy_utils::{ - fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, is_expr_temporary_value, peel_middle_ty_refs, - return_ty, sym, + can_mut_borrow_both, fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, is_expr_temporary_value, + path_to_local, peel_middle_ty_refs, return_ty, sym, }; +use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node}; +use rustc_hir::{BindingMode, BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node, PatKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::mir::Mutability; @@ -24,7 +26,36 @@ use rustc_span::Symbol; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::{Obligation, ObligationCause}; -use super::UNNECESSARY_TO_OWNED; +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) + /// and other `to_owned`-like functions. + /// + /// ### Why is this bad? + /// The unnecessary calls result in useless allocations. + /// + /// ### Known problems + /// `unnecessary_to_owned` can falsely trigger if `IntoIterator::into_iter` is applied to an + /// owned copy of a resource and the resource is later used mutably. See + /// [#8148](https://github.com/rust-lang/rust-clippy/issues/8148). + /// + /// ### Example + /// ```no_run + /// let path = std::path::Path::new("x"); + /// foo(&path.to_string_lossy().to_string()); + /// fn foo(s: &str) {} + /// ``` + /// Use instead: + /// ```no_run + /// let path = std::path::Path::new("x"); + /// foo(&path.to_string_lossy()); + /// fn foo(s: &str) {} + /// ``` + #[clippy::version = "1.59.0"] + pub UNNECESSARY_TO_OWNED, + perf, + "unnecessary calls to `to_owned`-like functions" +} pub fn check<'tcx>( cx: &LateContext<'tcx>, @@ -38,7 +69,7 @@ pub fn check<'tcx>( && args.is_empty() { if is_cloned_or_copied(cx, method_name, method_def_id) { - unnecessary_iter_cloned::check(cx, expr, method_name, receiver); + check_iter_cloned(cx, expr, method_name, receiver); } else if is_to_owned_like(cx, expr, method_name, method_def_id) { if check_split_call_arg(cx, expr, method_name, receiver) { return; @@ -223,7 +254,7 @@ fn check_into_iter_call_arg( // Calling `iter()` on a temporary object can lead to false positives. #14242 && !is_expr_temporary_value(cx, receiver) { - if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) { + if check_for_loop_iter(cx, parent, method_name, receiver, true) { return true; } @@ -755,3 +786,130 @@ fn check_borrow_predicate<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { check_if_applicable_to_argument(cx, &arg); } } + +fn check_iter_cloned(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool { + if let Some(parent) = get_parent_expr(cx, expr) + && let Some(callee_def_id) = fn_def_id(cx, parent) + && is_into_iter(cx, callee_def_id) + { + check_for_loop_iter(cx, parent, method_name, receiver, false) + } else { + false + } +} + +/// Checks whether `expr` is an iterator in a `for` loop and, if so, determines whether the +/// iterated-over items could be iterated over by reference. The reason why `check` above does not +/// include this code directly is so that it can be called from +/// `unnecessary_into_owned::check_into_iter_call_arg`. +pub fn check_for_loop_iter( + cx: &LateContext<'_>, + expr: &Expr<'_>, + method_name: Symbol, + receiver: &Expr<'_>, + cloned_before_iter: bool, +) -> bool { + if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent)) + && let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent) + && let (clone_or_copy_needed, references_to_binding) = clone_or_copy_needed(cx, pat, body) + && !clone_or_copy_needed + && let Some(receiver_snippet) = receiver.span.get_source_text(cx) + { + // Issue 12098 + // https://github.com/rust-lang/rust-clippy/issues/12098 + // if the assignee have `mut borrow` conflict with the iteratee + // the lint should not execute, former didn't consider the mut case + + // check whether `expr` is mutable + fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let Some(hir_id) = path_to_local(expr) + && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) + { + matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)) + } else { + true + } + } + + fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Expr<'_>) -> bool { + let mut change = false; + if let ExprKind::Block(block, ..) = body.kind { + for_each_expr_without_closures(block, |e| { + match e.kind { + ExprKind::Assign(assignee, _, _) | ExprKind::AssignOp(_, assignee, _) => { + change |= !can_mut_borrow_both(cx, caller, assignee); + }, + _ => {}, + } + // the return value has no effect but the function need one return value + ControlFlow::<()>::Continue(()) + }); + } + change + } + + if let ExprKind::Call(_, [child, ..]) = expr.kind { + // filter first layer of iterator + let mut child = child; + // get inner real caller requests for clone + while let ExprKind::MethodCall(_, caller, _, _) = child.kind { + child = caller; + } + if is_mutable(cx, child) && is_caller_or_fields_change(cx, body, child) { + // skip lint + return true; + } + } + + // the lint should not be executed if no violation happens + let snippet = if let ExprKind::MethodCall(maybe_iter_method_name, collection, [], _) = receiver.kind + && maybe_iter_method_name.ident.name == sym::iter + && let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator) + && let receiver_ty = cx.typeck_results().expr_ty(receiver) + && implements_trait(cx, receiver_ty, iterator_trait_id, &[]) + && let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty) + && let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) + && let collection_ty = cx.typeck_results().expr_ty(collection) + && implements_trait(cx, collection_ty, into_iterator_trait_id, &[]) + && let Some(into_iter_item_ty) = cx.get_associated_type(collection_ty, into_iterator_trait_id, sym::Item) + && iter_item_ty == into_iter_item_ty + && let Some(collection_snippet) = collection.span.get_source_text(cx) + { + collection_snippet + } else { + receiver_snippet + }; + span_lint_and_then( + cx, + UNNECESSARY_TO_OWNED, + expr.span, + format!("unnecessary use of `{method_name}`"), + |diag| { + // If `check_into_iter_call_arg` called `check_for_loop_iter` because a call to + // a `to_owned`-like function was removed, then the next suggestion may be + // incorrect. This is because the iterator that results from the call's removal + // could hold a reference to a resource that is used mutably. See + // https://github.com/rust-lang/rust-clippy/issues/8148. + let applicability = if cloned_before_iter { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; + + let combined = references_to_binding + .into_iter() + .chain(vec![(expr.span, snippet.to_owned())]) + .collect::>(); + + diag.multipart_suggestion("remove any references to the binding", combined, applicability); + }, + ); + return true; + } + false +} + +/// Returns true if the named method is `IntoIterator::into_iter`. +fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool { + Some(callee_def_id) == cx.tcx.lang_items().into_iter_fn() +} diff --git a/clippy_lints_methods/src/unwrap_expect_used.rs b/clippy_lints_methods/src/unwrap_expect_used.rs index 027215e3b4d7..f6ebe98b6cb5 100644 --- a/clippy_lints_methods/src/unwrap_expect_used.rs +++ b/clippy_lints_methods/src/unwrap_expect_used.rs @@ -6,7 +6,92 @@ use rustc_lint::{LateContext, Lint}; use rustc_middle::ty; use rustc_span::sym; -use super::{EXPECT_USED, UNWRAP_USED}; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.unwrap()` or `.unwrap_err()` calls on `Result`s and `.unwrap()` call on `Option`s. + /// + /// ### Why restrict this? + /// It is better to handle the `None` or `Err` case, + /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of + /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is + /// `Allow` by default. + /// + /// `result.unwrap()` will let the thread panic on `Err` values. + /// Normally, you want to implement more sophisticated error handling, + /// and propagate errors upwards with `?` operator. + /// + /// Even if you want to panic on errors, not all `Error`s implement good + /// messages on display. Therefore, it may be beneficial to look at the places + /// where they may get displayed. Activate this lint to do just that. + /// + /// ### Examples + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.unwrap(); + /// result.unwrap(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.expect("more helpful message"); + /// result.expect("more helpful message"); + /// ``` + /// + /// If [expect_used](#expect_used) is enabled, instead: + /// ```rust,ignore + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option?; + /// + /// // or + /// + /// result?; + /// ``` + #[clippy::version = "1.45.0"] + pub UNWRAP_USED, + restriction, + "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s. + /// + /// ### Why restrict this? + /// Usually it is better to handle the `None` or `Err` case. + /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why + /// this lint is `Allow` by default. + /// + /// `result.expect()` will let the thread panic on `Err` + /// values. Normally, you want to implement more sophisticated error handling, + /// and propagate errors upwards with `?` operator. + /// + /// ### Examples + /// ```rust,ignore + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.expect("one"); + /// result.expect("one"); + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option?; + /// + /// // or + /// + /// result?; + /// ``` + #[clippy::version = "1.45.0"] + pub EXPECT_USED, + restriction, + "using `.expect()` on `Result` or `Option`, which might be better handled" +} #[derive(Clone, Copy, Eq, PartialEq)] pub(super) enum Variant { diff --git a/clippy_lints_methods/src/useless_asref.rs b/clippy_lints_methods/src/useless_asref.rs index d30c12e0c483..d2803c6a4939 100644 --- a/clippy_lints_methods/src/useless_asref.rs +++ b/clippy_lints_methods/src/useless_asref.rs @@ -11,7 +11,31 @@ use rustc_span::{Span, Symbol, sym}; use core::ops::ControlFlow; -use super::USELESS_ASREF; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.as_ref()` or `.as_mut()` where the + /// types before and after the call are the same. + /// + /// ### Why is this bad? + /// The call is unnecessary. + /// + /// ### Example + /// ```no_run + /// # fn do_stuff(x: &[i32]) {} + /// let x: &[i32] = &[1, 2, 3, 4, 5]; + /// do_stuff(x.as_ref()); + /// ``` + /// The correct use would be: + /// ```no_run + /// # fn do_stuff(x: &[i32]) {} + /// let x: &[i32] = &[1, 2, 3, 4, 5]; + /// do_stuff(x); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub USELESS_ASREF, + complexity, + "using `as_ref` where the types before and after the call are the same" +} /// Returns the first type inside the `Option`/`Result` type passed as argument. fn get_enum_ty(enum_ty: Ty<'_>) -> Option> { diff --git a/clippy_lints_methods/src/useless_nonzero_new_unchecked.rs b/clippy_lints_methods/src/useless_nonzero_new_unchecked.rs index 22df1f3f485e..22e7ef4d5959 100644 --- a/clippy_lints_methods/src/useless_nonzero_new_unchecked.rs +++ b/clippy_lints_methods/src/useless_nonzero_new_unchecked.rs @@ -8,7 +8,32 @@ use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, Node, QPath, UnsafeSource use rustc_lint::LateContext; use rustc_span::sym; -use super::USELESS_NONZERO_NEW_UNCHECKED; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `NonZero*::new_unchecked()` being used in a `const` context. + /// + /// ### Why is this bad? + /// + /// Using `NonZero*::new_unchecked()` is an `unsafe` function and requires an `unsafe` context. When used in a + /// context evaluated at compilation time, `NonZero*::new().unwrap()` will provide the same result with identical + /// runtime performances while not requiring `unsafe`. + /// + /// ### Example + /// ```no_run + /// use std::num::NonZeroUsize; + /// const PLAYERS: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(3) }; + /// ``` + /// Use instead: + /// ```no_run + /// use std::num::NonZeroUsize; + /// const PLAYERS: NonZeroUsize = NonZeroUsize::new(3).unwrap(); + /// ``` + #[clippy::version = "1.86.0"] + pub USELESS_NONZERO_NEW_UNCHECKED, + complexity, + "using `NonZero::new_unchecked()` in a `const` context" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'tcx>, args: &[Expr<'_>], msrv: Msrv) { if let ExprKind::Path(QPath::TypeRelative(ty, segment)) = func.kind diff --git a/clippy_lints_methods/src/vec_resize_to_zero.rs b/clippy_lints_methods/src/vec_resize_to_zero.rs index 5ea4ada128a2..f9236fc4f23d 100644 --- a/clippy_lints_methods/src/vec_resize_to_zero.rs +++ b/clippy_lints_methods/src/vec_resize_to_zero.rs @@ -8,7 +8,27 @@ use rustc_lint::LateContext; use rustc_span::source_map::Spanned; use rustc_span::{Span, sym}; -use super::VEC_RESIZE_TO_ZERO; +declare_clippy_lint! { + /// ### What it does + /// Finds occurrences of `Vec::resize(0, an_int)` + /// + /// ### Why is this bad? + /// This is probably an argument inversion mistake. + /// + /// ### Example + /// ```no_run + /// vec![1, 2, 3, 4, 5].resize(0, 5) + /// ``` + /// + /// Use instead: + /// ```no_run + /// vec![1, 2, 3, 4, 5].clear() + /// ``` + #[clippy::version = "1.46.0"] + pub VEC_RESIZE_TO_ZERO, + correctness, + "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/verbose_file_reads.rs b/clippy_lints_methods/src/verbose_file_reads.rs index 8ed61637eca2..38a675e3e0c3 100644 --- a/clippy_lints_methods/src/verbose_file_reads.rs +++ b/clippy_lints_methods/src/verbose_file_reads.rs @@ -5,7 +5,32 @@ use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::sym; -use super::VERBOSE_FILE_READS; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of File::read_to_end and File::read_to_string. + /// + /// ### Why restrict this? + /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. + /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) + /// + /// ### Example + /// ```rust,no_run + /// # use std::io::Read; + /// # use std::fs::File; + /// let mut f = File::open("foo.txt").unwrap(); + /// let mut bytes = Vec::new(); + /// f.read_to_end(&mut bytes).unwrap(); + /// ``` + /// Can be written more concisely as + /// ```rust,no_run + /// # use std::fs; + /// let mut bytes = fs::read("foo.txt").unwrap(); + /// ``` + #[clippy::version = "1.44.0"] + pub VERBOSE_FILE_READS, + restriction, + "use of `File::read_to_end` or `File::read_to_string`" +} pub(super) const READ_TO_END_MSG: (&str, &str) = ("use of `File::read_to_end`", "consider using `fs::read` instead"); pub(super) const READ_TO_STRING_MSG: (&str, &str) = ( diff --git a/clippy_lints_methods/src/waker_clone_wake.rs b/clippy_lints_methods/src/waker_clone_wake.rs index b5f34a9be2e2..b7b24b5e07e5 100644 --- a/clippy_lints_methods/src/waker_clone_wake.rs +++ b/clippy_lints_methods/src/waker_clone_wake.rs @@ -6,7 +6,27 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::sym; -use super::WAKER_CLONE_WAKE; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `waker.clone().wake()` + /// + /// ### Why is this bad? + /// Cloning the waker is not necessary, `wake_by_ref()` enables the same operation + /// without extra cloning/dropping. + /// + /// ### Example + /// ```rust,ignore + /// waker.clone().wake(); + /// ``` + /// Should be written + /// ```rust,ignore + /// waker.wake_by_ref(); + /// ``` + #[clippy::version = "1.75.0"] + pub WAKER_CLONE_WAKE, + perf, + "cloning a `Waker` only to wake it" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv); diff --git a/clippy_lints_methods/src/wrong_self_convention.rs b/clippy_lints_methods/src/wrong_self_convention.rs index 881402d2e401..5cc73a2d1c91 100644 --- a/clippy_lints_methods/src/wrong_self_convention.rs +++ b/clippy_lints_methods/src/wrong_self_convention.rs @@ -6,7 +6,65 @@ use rustc_middle::ty::Ty; use rustc_span::{Span, Symbol}; use std::fmt; -use super::WRONG_SELF_CONVENTION; +declare_clippy_lint! { + /// ### What it does + /// Checks for methods with certain name prefixes or suffixes, and which + /// do not adhere to standard conventions regarding how `self` is taken. + /// The actual rules are: + /// + /// |Prefix |Postfix |`self` taken | `self` type | + /// |-------|------------|-------------------------------|--------------| + /// |`as_` | none |`&self` or `&mut self` | any | + /// |`from_`| none | none | any | + /// |`into_`| none |`self` | any | + /// |`is_` | none |`&mut self` or `&self` or none | any | + /// |`to_` | `_mut` |`&mut self` | any | + /// |`to_` | not `_mut` |`self` | `Copy` | + /// |`to_` | not `_mut` |`&self` | not `Copy` | + /// + /// Note: Clippy doesn't trigger methods with `to_` prefix in: + /// - Traits definition. + /// Clippy can not tell if a type that implements a trait is `Copy` or not. + /// - Traits implementation, when `&self` is taken. + /// The method signature is controlled by the trait and often `&self` is required for all types that implement the trait + /// (see e.g. the `std::string::ToString` trait). + /// + /// Clippy allows `Pin<&Self>` and `Pin<&mut Self>` if `&self` and `&mut self` is required. + /// + /// Please find more info here: + /// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv + /// + /// ### Why is this bad? + /// Consistency breeds readability. If you follow the + /// conventions, your users won't be surprised that they, e.g., need to supply a + /// mutable reference to a `as_..` function. + /// + /// ### Example + /// ```no_run + /// # struct X; + /// impl X { + /// fn as_str(self) -> &'static str { + /// // .. + /// # "" + /// } + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// # struct X; + /// impl X { + /// fn as_str(&self) -> &'static str { + /// // .. + /// # "" + /// } + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub WRONG_SELF_CONVENTION, + style, + "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention" +} #[rustfmt::skip] const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [ diff --git a/clippy_lints_methods/src/zst_offset.rs b/clippy_lints_methods/src/zst_offset.rs index 102fa7bc8953..07b6fc9c01b5 100644 --- a/clippy_lints_methods/src/zst_offset.rs +++ b/clippy_lints_methods/src/zst_offset.rs @@ -3,7 +3,23 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty; -use super::ZST_OFFSET; +declare_clippy_lint! { + /// ### What it does + /// Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to + /// zero-sized types + /// + /// ### Why is this bad? + /// This is a no-op, and likely unintended + /// + /// ### Example + /// ```no_run + /// unsafe { (&() as *const ()).offset(1) }; + /// ``` + #[clippy::version = "1.41.0"] + pub ZST_OFFSET, + correctness, + "Check for offset calculations on raw pointers to zero-sized types" +} pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { if let ty::RawPtr(ty, _) = cx.typeck_results().expr_ty(recv).kind()