Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2024-09-05 17:00:37 +02:00
commit 87ce1d8069
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
141 changed files with 3554 additions and 1145 deletions

View file

@ -162,7 +162,7 @@ jobs:
find $DIR ! -executable -o -type d ! -path $DIR | xargs rm -rf
- name: Upload Binaries
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: binaries
path: target/debug
@ -202,7 +202,7 @@ jobs:
# Download
- name: Download target dir
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: binaries
path: target/debug

View file

@ -6,7 +6,61 @@ document.
## Unreleased / Beta / In Rust Nightly
[c9139bd5...master](https://github.com/rust-lang/rust-clippy/compare/c9139bd5...master)
[b794b8e0...master](https://github.com/rust-lang/rust-clippy/compare/b794b8e0...master)
## Rust 1.81
Current stable, released 2024-09-05
### New Lints
* Added [`cfg_not_test`] to `restriction`
[#11293](https://github.com/rust-lang/rust-clippy/pull/11293)
* Added [`byte_char_slices`] to `style`
[#10155](https://github.com/rust-lang/rust-clippy/pull/10155)
* Added [`set_contains_or_insert`] to `nursery`
[#12873](https://github.com/rust-lang/rust-clippy/pull/12873)
* Added [`manual_rotate`] to `style`
[#12983](https://github.com/rust-lang/rust-clippy/pull/12983)
* Added [`unnecessary_min_or_max`] to `complexity`
[#12368](https://github.com/rust-lang/rust-clippy/pull/12368)
* Added [`manual_inspect`] to `complexity`
[#12287](https://github.com/rust-lang/rust-clippy/pull/12287)
* Added [`field_scoped_visibility_modifiers`] to `restriction`
[#12893](https://github.com/rust-lang/rust-clippy/pull/12893)
* Added [`manual_pattern_char_comparison`] to `style`
[#12849](https://github.com/rust-lang/rust-clippy/pull/12849)
* Added [`needless_maybe_sized`] to `suspicious`
[#10632](https://github.com/rust-lang/rust-clippy/pull/10632)
* Added [`needless_character_iteration`] to `suspicious`
[#12815](https://github.com/rust-lang/rust-clippy/pull/12815)
### Moves and Deprecations
* [`allow_attributes`], [`allow_attributes_without_reason`]: Now work on stable
[rust#120924](https://github.com/rust-lang/rust/pull/120924)
* Renamed `overflow_check_conditional` to [`panicking_overflow_checks`]
[#12944](https://github.com/rust-lang/rust-clippy/pull/12944)
* Moved [`panicking_overflow_checks`] to `correctness` (From `complexity` now deny-by-default)
[#12944](https://github.com/rust-lang/rust-clippy/pull/12944)
* Renamed `thread_local_initializer_can_be_made_const` to [`missing_const_for_thread_local`]
[#12974](https://github.com/rust-lang/rust-clippy/pull/12974)
* Deprecated [`maybe_misused_cfg`] and [`mismatched_target_os`] as they are now caught by cargo
and rustc
[#12875](https://github.com/rust-lang/rust-clippy/pull/12875)
### Enhancements
* [`significant_drop_in_scrutinee`]: Now also checks scrutinies of `while let` and `for let`
expressions
[#12870](https://github.com/rust-lang/rust-clippy/pull/12870)
* [`std_instead_of_core`]: Now respects the `msrv` configuration
[#13168](https://github.com/rust-lang/rust-clippy/pull/13168)
### ICE Fixes
* [`suboptimal_flops`]: No longer crashes on custom `.log()` functions
[#12884](https://github.com/rust-lang/rust-clippy/pull/12884)
## Rust 1.80
@ -5500,6 +5554,7 @@ Released 2018-09-13
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
[`invalid_utf8_in_unchecked`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_utf8_in_unchecked
[`inverted_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#inverted_saturating_sub
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
@ -5757,6 +5812,7 @@ Released 2018-09-13
[`pathbuf_init_then_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#pathbuf_init_then_push
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
[`permissions_set_readonly_false`]: https://rust-lang.github.io/rust-clippy/master/index.html#permissions_set_readonly_false
[`pointers_in_nomem_asm_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#pointers_in_nomem_asm_block
[`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
@ -6013,6 +6069,7 @@ Released 2018-09-13
[`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug
[`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self
[`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding
[`used_underscore_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_items
[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref
[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute
[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
@ -6047,6 +6104,7 @@ Released 2018-09-13
[`zero_repeat_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_repeat_side_effects
[`zero_sized_map_values`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_sized_map_values
[`zero_width_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_width_space
[`zombie_processes`]: https://rust-lang.github.io/rust-clippy/master/index.html#zombie_processes
[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset
<!-- end autogenerated links to lint list -->
<!-- begin autogenerated links to configuration documentation -->

View file

@ -36,7 +36,7 @@ msrv_aliases! {
1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
1,50,0 { BOOL_THEN, CLAMP }
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN }
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST }
1,46,0 { CONST_IF_MATCH }
1,45,0 { STR_STRIP_PREFIX }
1,43,0 { LOG2_10, LOG10_2, NUMERIC_ASSOCIATED_CONSTANTS }

View file

@ -470,7 +470,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
});
// Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl
while let Some(LintDeclSearchResult { content, .. }) = iter.find(|result| result.token == TokenKind::Ident) {
while let Some(LintDeclSearchResult { content, .. }) = iter.find(|result| result.token_kind == TokenKind::Ident) {
let mut iter = iter
.by_ref()
.filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
@ -480,7 +480,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
// matches `!{`
match_tokens!(iter, Bang OpenBrace);
if let Some(LintDeclSearchResult { range, .. }) =
iter.find(|result| result.token == TokenKind::CloseBrace)
iter.find(|result| result.token_kind == TokenKind::CloseBrace)
{
last_decl_curly_offset = Some(range.end);
}

View file

@ -29,7 +29,7 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
}
if let Some(url) = url.take() {
thread::spawn(move || {
Command::new(PYTHON)
let mut child = Command::new(PYTHON)
.arg("-m")
.arg("http.server")
.arg(port.to_string())
@ -40,6 +40,7 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
thread::sleep(Duration::from_millis(500));
// Launch browser after first export.py has completed and http.server is up
let _result = opener::open(url);
child.wait().unwrap();
});
}
thread::sleep(Duration::from_millis(1000));

View file

@ -26,7 +26,7 @@ pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMet
cx,
ALLOW_ATTRIBUTES_WITHOUT_REASON,
attr.span,
format!("`{}` attribute without specifying a reason", name.as_str()),
format!("`{name}` attribute without specifying a reason"),
|diag| {
diag.help("try adding a reason at the end with `, reason = \"..\"`");
},

View file

@ -1,52 +0,0 @@
use super::{EMPTY_LINE_AFTER_DOC_COMMENTS, EMPTY_LINE_AFTER_OUTER_ATTR};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::{is_present_in_source, without_block_comments, SpanRangeExt};
use rustc_ast::{AttrKind, AttrStyle};
use rustc_lint::EarlyContext;
use rustc_span::Span;
/// Check for empty lines after outer attributes.
///
/// Attributes and documentation comments are both considered outer attributes
/// by the AST. However, the average user likely considers them to be different.
/// Checking for empty lines after each of these attributes is split into two different
/// lints but can share the same logic.
pub(super) fn check(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
let mut iter = item.attrs.iter().peekable();
while let Some(attr) = iter.next() {
if (matches!(attr.kind, AttrKind::Normal(..)) || matches!(attr.kind, AttrKind::DocComment(..)))
&& attr.style == AttrStyle::Outer
&& is_present_in_source(cx, attr.span)
{
let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt(), item.span.parent());
let end_of_attr_to_next_attr_or_item = Span::new(
attr.span.hi(),
iter.peek().map_or(item.span.lo(), |next_attr| next_attr.span.lo()),
item.span.ctxt(),
item.span.parent(),
);
if let Some(snippet) = end_of_attr_to_next_attr_or_item.get_source_text(cx) {
let lines = snippet.split('\n').collect::<Vec<_>>();
let lines = without_block_comments(lines);
if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
let (lint_msg, lint_type) = match attr.kind {
AttrKind::DocComment(..) => (
"found an empty line after a doc comment. \
Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?",
EMPTY_LINE_AFTER_DOC_COMMENTS,
),
AttrKind::Normal(..) => (
"found an empty line after an outer attribute. \
Perhaps you forgot to add a `!` to make it an inner attribute?",
EMPTY_LINE_AFTER_OUTER_ATTR,
),
};
span_lint(cx, lint_type, begin_of_attr_to_item, lint_msg);
}
}
}
}
}

View file

@ -4,7 +4,6 @@ mod blanket_clippy_restriction_lints;
mod deprecated_cfg_attr;
mod deprecated_semver;
mod duplicated_attributes;
mod empty_line_after;
mod inline_always;
mod mixed_attributes_style;
mod non_minimal_cfg;
@ -126,94 +125,6 @@ declare_clippy_lint! {
"use of `#[deprecated(since = \"x\")]` where x is not semver"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for empty lines after outer attributes
///
/// ### Why is this bad?
/// Most likely the attribute was meant to be an inner attribute using a '!'.
/// If it was meant to be an outer attribute, then the following item
/// should not be separated by empty lines.
///
/// ### Known problems
/// Can cause false positives.
///
/// From the clippy side it's difficult to detect empty lines between an attributes and the
/// following item because empty lines and comments are not part of the AST. The parsing
/// currently works for basic cases but is not perfect.
///
/// ### Example
/// ```no_run
/// #[allow(dead_code)]
///
/// fn not_quite_good_code() { }
/// ```
///
/// Use instead:
/// ```no_run
/// // Good (as inner attribute)
/// #![allow(dead_code)]
///
/// fn this_is_fine() { }
///
/// // or
///
/// // Good (as outer attribute)
/// #[allow(dead_code)]
/// fn this_is_fine_too() { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub EMPTY_LINE_AFTER_OUTER_ATTR,
nursery,
"empty line after outer attribute"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for empty lines after documentation comments.
///
/// ### Why is this bad?
/// The documentation comment was most likely meant to be an inner attribute or regular comment.
/// If it was intended to be a documentation comment, then the empty line should be removed to
/// be more idiomatic.
///
/// ### Known problems
/// Only detects empty lines immediately following the documentation. If the doc comment is followed
/// by an attribute and then an empty line, this lint will not trigger. Use `empty_line_after_outer_attr`
/// in combination with this lint to detect both cases.
///
/// Does not detect empty lines after doc attributes (e.g. `#[doc = ""]`).
///
/// ### Example
/// ```no_run
/// /// Some doc comment with a blank line after it.
///
/// fn not_quite_good_code() { }
/// ```
///
/// Use instead:
/// ```no_run
/// /// Good (no blank line)
/// fn this_is_fine() { }
/// ```
///
/// ```no_run
/// // Good (convert to a regular comment)
///
/// fn this_is_fine_too() { }
/// ```
///
/// ```no_run
/// //! Good (convert to a comment on an inner attribute)
///
/// fn this_is_fine_as_well() { }
/// ```
#[clippy::version = "1.70.0"]
pub EMPTY_LINE_AFTER_DOC_COMMENTS,
nursery,
"empty line after documentation comments"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
@ -601,18 +512,12 @@ impl EarlyAttributes {
impl_lint_pass!(EarlyAttributes => [
DEPRECATED_CFG_ATTR,
EMPTY_LINE_AFTER_OUTER_ATTR,
EMPTY_LINE_AFTER_DOC_COMMENTS,
NON_MINIMAL_CFG,
DEPRECATED_CLIPPY_CFG_ATTR,
UNNECESSARY_CLIPPY_CFG,
]);
impl EarlyLintPass for EarlyAttributes {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
empty_line_after::check(cx, item);
}
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
deprecated_cfg_attr::check(cx, attr, &self.msrv);
deprecated_cfg_attr::check_clippy(cx, attr);

View file

@ -22,7 +22,7 @@ declare_clippy_lint! {
/// ```ignore
/// b"Hello"
/// ```
#[clippy::version = "1.68.0"]
#[clippy::version = "1.81.0"]
pub BYTE_CHAR_SLICES,
style,
"hard to read byte char slice"

View file

@ -22,7 +22,7 @@ declare_clippy_lint! {
/// # fn important_check() {}
/// important_check();
/// ```
#[clippy::version = "1.73.0"]
#[clippy::version = "1.81.0"]
pub CFG_NOT_TEST,
restriction,
"enforce against excluding code from test builds"

View file

@ -49,8 +49,6 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
crate::attrs::DEPRECATED_SEMVER_INFO,
crate::attrs::DUPLICATED_ATTRIBUTES_INFO,
crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
crate::attrs::INLINE_ALWAYS_INFO,
crate::attrs::MIXED_ATTRIBUTES_STYLE_INFO,
crate::attrs::NON_MINIMAL_CFG_INFO,
@ -138,6 +136,8 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::doc::DOC_LINK_WITH_QUOTES_INFO,
crate::doc::DOC_MARKDOWN_INFO,
crate::doc::EMPTY_DOCS_INFO,
crate::doc::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
crate::doc::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
crate::doc::MISSING_ERRORS_DOC_INFO,
crate::doc::MISSING_PANICS_DOC_INFO,
crate::doc::MISSING_SAFETY_DOC_INFO,
@ -217,6 +217,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::implicit_return::IMPLICIT_RETURN_INFO,
crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO,
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
crate::implicit_saturating_sub::INVERTED_SATURATING_SUB_INFO,
crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO,
crate::incompatible_msrv::INCOMPATIBLE_MSRV_INFO,
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
@ -486,6 +487,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::misc::SHORT_CIRCUIT_STATEMENT_INFO,
crate::misc::TOPLEVEL_REF_ARG_INFO,
crate::misc::USED_UNDERSCORE_BINDING_INFO,
crate::misc::USED_UNDERSCORE_ITEMS_INFO,
crate::misc_early::BUILTIN_TYPE_SHADOW_INFO,
crate::misc_early::DOUBLE_NEG_INFO,
crate::misc_early::DUPLICATE_UNDERSCORE_ARGUMENT_INFO,
@ -598,6 +600,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::pathbuf_init_then_push::PATHBUF_INIT_THEN_PUSH_INFO,
crate::pattern_type_mismatch::PATTERN_TYPE_MISMATCH_INFO,
crate::permissions_set_readonly_false::PERMISSIONS_SET_READONLY_FALSE_INFO,
crate::pointers_in_nomem_asm_block::POINTERS_IN_NOMEM_ASM_BLOCK_INFO,
crate::precedence::PRECEDENCE_INFO,
crate::ptr::CMP_NULL_INFO,
crate::ptr::INVALID_NULL_PTR_USAGE_INFO,
@ -767,4 +770,5 @@ pub static LINTS: &[&crate::LintInfo] = &[
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,
crate::zombie_processes::ZOMBIE_PROCESSES_INFO,
];

View file

@ -0,0 +1,329 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{snippet_indent, SpanRangeExt};
use clippy_utils::tokenize_with_text;
use itertools::Itertools;
use rustc_ast::token::CommentKind;
use rustc_ast::{AttrKind, AttrStyle, Attribute};
use rustc_errors::{Applicability, Diag, SuggestionStyle};
use rustc_hir::{ItemKind, Node};
use rustc_lexer::TokenKind;
use rustc_lint::LateContext;
use rustc_span::{ExpnKind, InnerSpan, Span, SpanData};
use super::{EMPTY_LINE_AFTER_DOC_COMMENTS, EMPTY_LINE_AFTER_OUTER_ATTR};
#[derive(Debug, PartialEq, Clone, Copy)]
enum StopKind {
Attr,
Doc(CommentKind),
}
impl StopKind {
fn is_doc(self) -> bool {
matches!(self, StopKind::Doc(_))
}
}
#[derive(Debug)]
struct Stop {
span: Span,
kind: StopKind,
first: usize,
last: usize,
}
impl Stop {
fn convert_to_inner(&self) -> (Span, String) {
let inner = match self.kind {
// #|[...]
StopKind::Attr => InnerSpan::new(1, 1),
// /// or /**
// ^ ^
StopKind::Doc(_) => InnerSpan::new(2, 3),
};
(self.span.from_inner(inner), "!".into())
}
fn comment_out(&self, cx: &LateContext<'_>, suggestions: &mut Vec<(Span, String)>) {
match self.kind {
StopKind::Attr => {
if cx.tcx.sess.source_map().is_multiline(self.span) {
suggestions.extend([
(self.span.shrink_to_lo(), "/* ".into()),
(self.span.shrink_to_hi(), " */".into()),
]);
} else {
suggestions.push((self.span.shrink_to_lo(), "// ".into()));
}
},
StopKind::Doc(CommentKind::Line) => suggestions.push((self.span.shrink_to_lo(), "// ".into())),
StopKind::Doc(CommentKind::Block) => {
// /** outer */ /*! inner */
// ^ ^
let asterisk = self.span.from_inner(InnerSpan::new(1, 2));
suggestions.push((asterisk, String::new()));
},
}
}
fn from_attr(cx: &LateContext<'_>, attr: &Attribute) -> Option<Self> {
let SpanData { lo, hi, .. } = attr.span.data();
let file = cx.tcx.sess.source_map().lookup_source_file(lo);
Some(Self {
span: attr.span,
kind: match attr.kind {
AttrKind::Normal(_) => StopKind::Attr,
AttrKind::DocComment(comment_kind, _) => StopKind::Doc(comment_kind),
},
first: file.lookup_line(file.relative_position(lo))?,
last: file.lookup_line(file.relative_position(hi))?,
})
}
}
/// Represents a set of attrs/doc comments separated by 1 or more empty lines
///
/// ```ignore
/// /// chunk 1 docs
/// // not an empty line so also part of chunk 1
/// #[chunk_1_attrs] // <-- prev_stop
///
/// /* gap */
///
/// /// chunk 2 docs // <-- next_stop
/// #[chunk_2_attrs]
/// ```
struct Gap<'a> {
/// The span of individual empty lines including the newline at the end of the line
empty_lines: Vec<Span>,
has_comment: bool,
next_stop: &'a Stop,
prev_stop: &'a Stop,
/// The chunk that includes [`prev_stop`](Self::prev_stop)
prev_chunk: &'a [Stop],
}
impl<'a> Gap<'a> {
fn new(cx: &LateContext<'_>, prev_chunk: &'a [Stop], next_chunk: &'a [Stop]) -> Option<Self> {
let prev_stop = prev_chunk.last()?;
let next_stop = next_chunk.first()?;
let gap_span = prev_stop.span.between(next_stop.span);
let gap_snippet = gap_span.get_source_text(cx)?;
let mut has_comment = false;
let mut empty_lines = Vec::new();
for (token, source, inner_span) in tokenize_with_text(&gap_snippet) {
match token {
TokenKind::BlockComment {
doc_style: None,
terminated: true,
}
| TokenKind::LineComment { doc_style: None } => has_comment = true,
TokenKind::Whitespace => {
let newlines = source.bytes().positions(|b| b == b'\n');
empty_lines.extend(
newlines
.tuple_windows()
.map(|(a, b)| InnerSpan::new(inner_span.start + a + 1, inner_span.start + b))
.map(|inner_span| gap_span.from_inner(inner_span)),
);
},
// Ignore cfg_attr'd out attributes as they may contain empty lines, could also be from macro
// shenanigans
_ => return None,
}
}
(!empty_lines.is_empty()).then_some(Self {
empty_lines,
has_comment,
next_stop,
prev_stop,
prev_chunk,
})
}
}
/// If the node the attributes/docs apply to is the first in the module/crate suggest converting
/// them to inner attributes/docs
fn suggest_inner(cx: &LateContext<'_>, diag: &mut Diag<'_, ()>, kind: StopKind, gaps: &[Gap<'_>]) {
let Some(owner) = cx.last_node_with_lint_attrs.as_owner() else {
return;
};
let parent_desc = match cx.tcx.parent_hir_node(owner.into()) {
Node::Item(item)
if let ItemKind::Mod(parent_mod) = item.kind
&& let [first, ..] = parent_mod.item_ids
&& first.owner_id == owner =>
{
"parent module"
},
Node::Crate(crate_mod)
if let Some(first) = crate_mod
.item_ids
.iter()
.map(|&id| cx.tcx.hir().item(id))
// skip prelude imports
.find(|item| !matches!(item.span.ctxt().outer_expn_data().kind, ExpnKind::AstPass(_)))
&& first.owner_id == owner =>
{
"crate"
},
_ => return,
};
diag.multipart_suggestion_verbose(
match kind {
StopKind::Attr => format!("if the attribute should apply to the {parent_desc} use an inner attribute"),
StopKind::Doc(_) => format!("if the comment should document the {parent_desc} use an inner doc comment"),
},
gaps.iter()
.flat_map(|gap| gap.prev_chunk)
.map(Stop::convert_to_inner)
.collect(),
Applicability::MaybeIncorrect,
);
}
fn check_gaps(cx: &LateContext<'_>, gaps: &[Gap<'_>]) -> bool {
let Some(first_gap) = gaps.first() else {
return false;
};
let empty_lines = || gaps.iter().flat_map(|gap| gap.empty_lines.iter().copied());
let mut has_comment = false;
let mut has_attr = false;
for gap in gaps {
has_comment |= gap.has_comment;
if !has_attr {
has_attr = gap.prev_chunk.iter().any(|stop| stop.kind == StopKind::Attr);
}
}
let kind = first_gap.prev_stop.kind;
let (lint, kind_desc) = match kind {
StopKind::Attr => (EMPTY_LINE_AFTER_OUTER_ATTR, "outer attribute"),
StopKind::Doc(_) => (EMPTY_LINE_AFTER_DOC_COMMENTS, "doc comment"),
};
let (lines, are, them) = if empty_lines().nth(1).is_some() {
("lines", "are", "them")
} else {
("line", "is", "it")
};
span_lint_and_then(
cx,
lint,
first_gap.prev_stop.span.to(empty_lines().last().unwrap()),
format!("empty {lines} after {kind_desc}"),
|diag| {
if let Some(owner) = cx.last_node_with_lint_attrs.as_owner() {
let def_id = owner.to_def_id();
let def_descr = cx.tcx.def_descr(def_id);
diag.span_label(
cx.tcx.def_span(def_id),
match kind {
StopKind::Attr => format!("the attribute applies to this {def_descr}"),
StopKind::Doc(_) => format!("the comment documents this {def_descr}"),
},
);
}
diag.multipart_suggestion_with_style(
format!("if the empty {lines} {are} unintentional remove {them}"),
empty_lines().map(|empty_line| (empty_line, String::new())).collect(),
Applicability::MaybeIncorrect,
SuggestionStyle::HideCodeAlways,
);
if has_comment && kind.is_doc() {
// Likely doc comments that applied to some now commented out code
//
// /// Old docs for Foo
// // struct Foo;
let mut suggestions = Vec::new();
for stop in gaps.iter().flat_map(|gap| gap.prev_chunk) {
stop.comment_out(cx, &mut suggestions);
}
let name = match cx.tcx.hir().opt_name(cx.last_node_with_lint_attrs) {
Some(name) => format!("`{name}`"),
None => "this".into(),
};
diag.multipart_suggestion_verbose(
format!("if the doc comment should not document {name} comment it out"),
suggestions,
Applicability::MaybeIncorrect,
);
} else {
suggest_inner(cx, diag, kind, gaps);
}
if kind == StopKind::Doc(CommentKind::Line)
&& gaps
.iter()
.all(|gap| !gap.has_comment && gap.next_stop.kind == StopKind::Doc(CommentKind::Line))
{
// Commentless empty gaps between line doc comments, possibly intended to be part of the markdown
let indent = snippet_indent(cx, first_gap.prev_stop.span).unwrap_or_default();
diag.multipart_suggestion_verbose(
format!("if the documentation should include the empty {lines} include {them} in the comment"),
empty_lines()
.map(|empty_line| (empty_line, format!("{indent}///")))
.collect(),
Applicability::MaybeIncorrect,
);
}
},
);
kind.is_doc()
}
/// Returns `true` if [`EMPTY_LINE_AFTER_DOC_COMMENTS`] triggered, used to skip other doc comment
/// lints where they would be confusing
///
/// [`EMPTY_LINE_AFTER_OUTER_ATTR`] is also here to share an implementation but does not return
/// `true` if it triggers
pub(super) fn check(cx: &LateContext<'_>, attrs: &[Attribute]) -> bool {
let mut outer = attrs
.iter()
.filter(|attr| attr.style == AttrStyle::Outer && !attr.span.from_expansion())
.map(|attr| Stop::from_attr(cx, attr))
.collect::<Option<Vec<_>>>()
.unwrap_or_default();
if outer.is_empty() {
return false;
}
// Push a fake attribute Stop for the item itself so we check for gaps between the last outer
// attr/doc comment and the item they apply to
let span = cx.tcx.hir().span(cx.last_node_with_lint_attrs);
if !span.from_expansion()
&& let Ok(line) = cx.tcx.sess.source_map().lookup_line(span.lo())
{
outer.push(Stop {
span,
kind: StopKind::Attr,
first: line.line,
// last doesn't need to be accurate here, we don't compare it with anything
last: line.line,
});
}
let mut gaps = Vec::new();
let mut last = 0;
for pos in outer
.array_windows()
.positions(|[a, b]| b.first.saturating_sub(a.last) > 1)
{
// we want to be after the first stop in the window
let pos = pos + 1;
if let Some(gap) = Gap::new(cx, &outer[last..pos], &outer[pos..]) {
last = pos;
gaps.push(gap);
}
}
check_gaps(cx, &gaps)
}

View file

@ -22,7 +22,6 @@ pub(super) fn check(
range: Range<usize>,
mut span: Span,
containers: &[super::Container],
line_break_span: Span,
) {
if doc[range.clone()].contains('\t') {
// We don't do tab stops correctly.
@ -52,29 +51,6 @@ pub(super) fn check(
"doc list item without indentation"
};
span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| {
let snippet = clippy_utils::source::snippet(cx, line_break_span, "");
if snippet.chars().filter(|&c| c == '\n').count() > 1
&& let Some(doc_comment_start) = snippet.rfind('\n')
&& let doc_comment = snippet[doc_comment_start..].trim()
&& (doc_comment == "///" || doc_comment == "//!")
{
// suggest filling in a blank line
diag.span_suggestion_verbose(
line_break_span.shrink_to_lo(),
"if this should be its own paragraph, add a blank doc comment line",
format!("\n{doc_comment}"),
Applicability::MaybeIncorrect,
);
if ccount > 0 || blockquote_level > 0 {
diag.help("if this not intended to be a quote at all, escape it with `\\>`");
} else {
let indent = list_indentation - lcount;
diag.help(format!(
"if this is intended to be part of the list, indent {indent} spaces"
));
}
return;
}
if ccount == 0 && blockquote_level == 0 {
// simpler suggestion style for indentation
let indent = list_indentation - lcount;

View file

@ -32,6 +32,7 @@ use rustc_span::{sym, Span};
use std::ops::Range;
use url::Url;
mod empty_line_after;
mod link_with_quotes;
mod markdown;
mod missing_headers;
@ -455,7 +456,82 @@ declare_clippy_lint! {
"ensure that the first line of a documentation paragraph isn't too long"
}
#[derive(Clone)]
declare_clippy_lint! {
/// ### What it does
/// Checks for empty lines after outer attributes
///
/// ### Why is this bad?
/// The attribute may have meant to be an inner attribute (`#![attr]`). If
/// it was meant to be an outer attribute (`#[attr]`) then the empty line
/// should be removed
///
/// ### Example
/// ```no_run
/// #[allow(dead_code)]
///
/// fn not_quite_good_code() {}
/// ```
///
/// Use instead:
/// ```no_run
/// // Good (as inner attribute)
/// #![allow(dead_code)]
///
/// fn this_is_fine() {}
///
/// // or
///
/// // Good (as outer attribute)
/// #[allow(dead_code)]
/// fn this_is_fine_too() {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub EMPTY_LINE_AFTER_OUTER_ATTR,
suspicious,
"empty line after outer attribute"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for empty lines after doc comments.
///
/// ### Why is this bad?
/// The doc comment may have meant to be an inner doc comment, regular
/// comment or applied to some old code that is now commented out. If it was
/// intended to be a doc comment, then the empty line should be removed.
///
/// ### Example
/// ```no_run
/// /// Some doc comment with a blank line after it.
///
/// fn f() {}
///
/// /// Docs for `old_code`
/// // fn old_code() {}
///
/// fn new_code() {}
/// ```
///
/// Use instead:
/// ```no_run
/// //! Convert it to an inner doc comment
///
/// // Or a regular comment
///
/// /// Or remove the empty line
/// fn f() {}
///
/// // /// Docs for `old_code`
/// // fn old_code() {}
///
/// fn new_code() {}
/// ```
#[clippy::version = "1.70.0"]
pub EMPTY_LINE_AFTER_DOC_COMMENTS,
suspicious,
"empty line after doc comments"
}
pub struct Documentation {
valid_idents: FxHashSet<String>,
check_private_items: bool,
@ -482,6 +558,8 @@ impl_lint_pass!(Documentation => [
SUSPICIOUS_DOC_COMMENTS,
EMPTY_DOCS,
DOC_LAZY_CONTINUATION,
EMPTY_LINE_AFTER_OUTER_ATTR,
EMPTY_LINE_AFTER_DOC_COMMENTS,
TOO_LONG_FIRST_DOC_PARAGRAPH,
]);
@ -612,12 +690,10 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
Some(("fake".into(), "fake".into()))
}
if is_doc_hidden(attrs) {
if suspicious_doc_comments::check(cx, attrs) || empty_line_after::check(cx, attrs) || is_doc_hidden(attrs) {
return None;
}
suspicious_doc_comments::check(cx, attrs);
let (fragments, _) = attrs_to_doc_fragments(
attrs.iter().filter_map(|attr| {
if in_external_macro(cx.sess(), attr.span) {
@ -816,7 +892,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
range.end..next_range.start,
Span::new(span.hi(), next_span.lo(), span.ctxt(), span.parent()),
&containers[..],
span,
);
}
},

View file

@ -7,7 +7,7 @@ use rustc_span::Span;
use super::SUSPICIOUS_DOC_COMMENTS;
pub fn check(cx: &LateContext<'_>, attrs: &[Attribute]) {
pub fn check(cx: &LateContext<'_>, attrs: &[Attribute]) -> bool {
let replacements: Vec<_> = collect_doc_replacements(attrs);
if let Some((&(lo_span, _), &(hi_span, _))) = replacements.first().zip(replacements.last()) {
@ -24,6 +24,10 @@ pub fn check(cx: &LateContext<'_>, attrs: &[Attribute]) {
);
},
);
true
} else {
false
}
}

View file

@ -41,7 +41,7 @@ declare_clippy_lint! {
/// }
/// }
/// ```
#[clippy::version = "1.78.0"]
#[clippy::version = "1.81.0"]
pub FIELD_SCOPED_VISIBILITY_MODIFIERS,
restriction,
"checks for usage of a scoped visibility modifier, like `pub(crate)`, on fields"

View file

@ -181,6 +181,9 @@ fn convert_to_from(
let from = self_ty.span.get_source_text(cx)?;
let into = target_ty.span.get_source_text(cx)?;
let return_type = matches!(sig.decl.output, FnRetTy::Return(_))
.then_some(String::from("Self"))
.unwrap_or_default();
let mut suggestions = vec![
// impl Into<T> for U -> impl From<T> for U
// ~~~~ ~~~~
@ -197,13 +200,10 @@ fn convert_to_from(
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
// ~~~~ ~~~~
(self_ident.span, format!("val: {from}")),
];
if let FnRetTy::Return(_) = sig.decl.output {
// fn into(self) -> T -> fn into(self) -> Self
// ~ ~~~~
suggestions.push((sig.decl.output.span(), String::from("Self")));
}
(sig.decl.output.span(), return_type),
];
let mut finder = SelfFinder {
cx,

View file

@ -1,11 +1,17 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{higher, is_integer_literal, peel_blocks_with_stmt, SpanlessEq};
use clippy_config::msrvs::{self, Msrv};
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::{
higher, is_in_const_context, is_integer_literal, path_to_local, peel_blocks, peel_blocks_with_stmt, SpanlessEq,
};
use rustc_ast::ast::LitKind;
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, HirId, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -39,7 +45,49 @@ declare_clippy_lint! {
"Perform saturating subtraction instead of implicitly checking lower bound of data type"
}
declare_lint_pass!(ImplicitSaturatingSub => [IMPLICIT_SATURATING_SUB]);
declare_clippy_lint! {
/// ### What it does
/// Checks for comparisons between integers, followed by subtracting the greater value from the
/// lower one.
///
/// ### Why is this bad?
/// This could result in an underflow and is most likely not what the user wants. If this was
/// intended to be a saturated subtraction, consider using the `saturating_sub` method directly.
///
/// ### Example
/// ```no_run
/// let a = 12u32;
/// let b = 13u32;
///
/// let result = if a > b { b - a } else { 0 };
/// ```
///
/// Use instead:
/// ```no_run
/// let a = 12u32;
/// let b = 13u32;
///
/// let result = a.saturating_sub(b);
/// ```
#[clippy::version = "1.44.0"]
pub INVERTED_SATURATING_SUB,
correctness,
"Check if a variable is smaller than another one and still subtract from it even if smaller"
}
pub struct ImplicitSaturatingSub {
msrv: Msrv,
}
impl_lint_pass!(ImplicitSaturatingSub => [IMPLICIT_SATURATING_SUB, INVERTED_SATURATING_SUB]);
impl ImplicitSaturatingSub {
pub fn new(conf: &'static Conf) -> Self {
Self {
msrv: conf.msrv.clone(),
}
}
}
impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
@ -50,73 +98,260 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
// Check if the conditional expression is a binary operation
&& let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind
// Ensure that the binary operator is >, !=, or <
&& (BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node)
// Check if assign operation is done
&& let Some(target) = subtracts_one(cx, then)
// Extracting out the variable name
&& let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind
{
// Handle symmetric conditions in the if statement
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node {
(cond_left, cond_right)
} else {
return;
}
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
(cond_right, cond_left)
} else {
return;
}
check_with_condition(cx, expr, cond_op.node, cond_left, cond_right, then);
} else if let Some(higher::If {
cond,
then: if_block,
r#else: Some(else_block),
}) = higher::If::hir(expr)
&& let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind
{
check_manual_check(
cx, expr, cond_op, cond_left, cond_right, if_block, else_block, &self.msrv,
);
}
}
extract_msrv_attr!(LateContext);
}
#[allow(clippy::too_many_arguments)]
fn check_manual_check<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'tcx>,
condition: &BinOp,
left_hand: &Expr<'tcx>,
right_hand: &Expr<'tcx>,
if_block: &Expr<'tcx>,
else_block: &Expr<'tcx>,
msrv: &Msrv,
) {
let ty = cx.typeck_results().expr_ty(left_hand);
if ty.is_numeric() && !ty.is_signed() {
match condition.node {
BinOpKind::Gt | BinOpKind::Ge => check_gt(
cx,
condition.span,
expr.span,
left_hand,
right_hand,
if_block,
else_block,
msrv,
),
BinOpKind::Lt | BinOpKind::Le => check_gt(
cx,
condition.span,
expr.span,
right_hand,
left_hand,
if_block,
else_block,
msrv,
),
_ => {},
}
}
}
#[allow(clippy::too_many_arguments)]
fn check_gt(
cx: &LateContext<'_>,
condition_span: Span,
expr_span: Span,
big_var: &Expr<'_>,
little_var: &Expr<'_>,
if_block: &Expr<'_>,
else_block: &Expr<'_>,
msrv: &Msrv,
) {
if let Some(big_var) = Var::new(big_var)
&& let Some(little_var) = Var::new(little_var)
{
check_subtraction(
cx,
condition_span,
expr_span,
big_var,
little_var,
if_block,
else_block,
msrv,
);
}
}
struct Var {
span: Span,
hir_id: HirId,
}
impl Var {
fn new(expr: &Expr<'_>) -> Option<Self> {
path_to_local(expr).map(|hir_id| Self {
span: expr.span,
hir_id,
})
}
}
#[allow(clippy::too_many_arguments)]
fn check_subtraction(
cx: &LateContext<'_>,
condition_span: Span,
expr_span: Span,
big_var: Var,
little_var: Var,
if_block: &Expr<'_>,
else_block: &Expr<'_>,
msrv: &Msrv,
) {
let if_block = peel_blocks(if_block);
let else_block = peel_blocks(else_block);
if is_integer_literal(if_block, 0) {
// We need to check this case as well to prevent infinite recursion.
if is_integer_literal(else_block, 0) {
// Well, seems weird but who knows?
return;
}
// If the subtraction is done in the `else` block, then we need to also revert the two
// variables as it means that the check was reverted too.
check_subtraction(
cx,
condition_span,
expr_span,
little_var,
big_var,
else_block,
if_block,
msrv,
);
return;
}
if is_integer_literal(else_block, 0)
&& let ExprKind::Binary(op, left, right) = if_block.kind
&& let BinOpKind::Sub = op.node
{
let local_left = path_to_local(left);
let local_right = path_to_local(right);
if Some(big_var.hir_id) == local_left && Some(little_var.hir_id) == local_right {
// This part of the condition is voluntarily split from the one before to ensure that
// if `snippet_opt` fails, it won't try the next conditions.
if let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
&& (!is_in_const_context(cx) || msrv.meets(msrvs::SATURATING_SUB_CONST))
{
span_lint_and_sugg(
cx,
IMPLICIT_SATURATING_SUB,
expr_span,
"manual arithmetic check found",
"replace it with",
format!("{big_var_snippet}.saturating_sub({little_var_snippet})"),
Applicability::MachineApplicable,
);
}
} else if Some(little_var.hir_id) == local_left
&& Some(big_var.hir_id) == local_right
&& let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
{
span_lint_and_then(
cx,
INVERTED_SATURATING_SUB,
condition_span,
"inverted arithmetic check before subtraction",
|diag| {
diag.span_note(
if_block.span,
format!("this subtraction underflows when `{little_var_snippet} < {big_var_snippet}`"),
);
diag.span_suggestion(
if_block.span,
"try replacing it with",
format!("{big_var_snippet} - {little_var_snippet}"),
Applicability::MaybeIncorrect,
);
},
);
}
}
}
fn check_with_condition<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'tcx>,
cond_op: BinOpKind,
cond_left: &Expr<'tcx>,
cond_right: &Expr<'tcx>,
then: &Expr<'tcx>,
) {
// Ensure that the binary operator is >, !=, or <
if (BinOpKind::Ne == cond_op || BinOpKind::Gt == cond_op || BinOpKind::Lt == cond_op)
// Check if assign operation is done
&& let Some(target) = subtracts_one(cx, then)
// Extracting out the variable name
&& let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind
{
// Handle symmetric conditions in the if statement
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
if BinOpKind::Gt == cond_op || BinOpKind::Ne == cond_op {
(cond_left, cond_right)
} else {
return;
};
// Check if the variable in the condition statement is an integer
if !cx.typeck_results().expr_ty(cond_var).is_integral() {
}
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
if BinOpKind::Lt == cond_op || BinOpKind::Ne == cond_op {
(cond_right, cond_left)
} else {
return;
}
} else {
return;
};
// Get the variable name
let var_name = ares_path.segments[0].ident.name.as_str();
match cond_num_val.kind {
ExprKind::Lit(cond_lit) => {
// Check if the constant is zero
if let LitKind::Int(Pu128(0), _) = cond_lit.node {
if cx.typeck_results().expr_ty(cond_left).is_signed() {
} else {
print_lint_and_sugg(cx, var_name, expr);
};
}
},
ExprKind::Path(QPath::TypeRelative(_, name)) => {
if name.ident.as_str() == "MIN"
&& let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(const_id)
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
{
// Check if the variable in the condition statement is an integer
if !cx.typeck_results().expr_ty(cond_var).is_integral() {
return;
}
// Get the variable name
let var_name = ares_path.segments[0].ident.name.as_str();
match cond_num_val.kind {
ExprKind::Lit(cond_lit) => {
// Check if the constant is zero
if let LitKind::Int(Pu128(0), _) = cond_lit.node {
if cx.typeck_results().expr_ty(cond_left).is_signed() {
} else {
print_lint_and_sugg(cx, var_name, expr);
}
},
ExprKind::Call(func, []) => {
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind
&& name.ident.as_str() == "min_value"
&& let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(func_id)
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
{
print_lint_and_sugg(cx, var_name, expr);
}
},
_ => (),
}
};
}
},
ExprKind::Path(QPath::TypeRelative(_, name)) => {
if name.ident.as_str() == "MIN"
&& let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(const_id)
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
{
print_lint_and_sugg(cx, var_name, expr);
}
},
ExprKind::Call(func, []) => {
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind
&& name.ident.as_str() == "min_value"
&& let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(func_id)
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
{
print_lint_and_sugg(cx, var_name, expr);
}
},
_ => (),
}
}
}

View file

@ -287,6 +287,7 @@ mod pass_by_ref_or_value;
mod pathbuf_init_then_push;
mod pattern_type_mismatch;
mod permissions_set_readonly_false;
mod pointers_in_nomem_asm_block;
mod precedence;
mod ptr;
mod ptr_offset_with_cast;
@ -386,6 +387,7 @@ mod write;
mod zero_div_zero;
mod zero_repeat_side_effects;
mod zero_sized_map_values;
mod zombie_processes;
// end lints modules, do not remove this comment, its used in `update_lints`
use clippy_config::{get_configuration_metadata, Conf};
@ -623,7 +625,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd));
store.register_late_pass(|_| Box::new(strings::StringAdd));
store.register_late_pass(|_| Box::new(implicit_return::ImplicitReturn));
store.register_late_pass(|_| Box::new(implicit_saturating_sub::ImplicitSaturatingSub));
store.register_late_pass(move |_| Box::new(implicit_saturating_sub::ImplicitSaturatingSub::new(conf)));
store.register_late_pass(|_| Box::new(default_numeric_fallback::DefaultNumericFallback));
store.register_late_pass(|_| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
@ -933,5 +935,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(set_contains_or_insert::SetContainsOrInsert));
store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice));
store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest));
store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses));
store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -77,9 +77,10 @@ impl Num {
impl LateLintPass<'_> for ManualRangePatterns {
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &'_ rustc_hir::Pat<'_>) {
// a pattern like 1 | 2 seems fine, lint if there are at least 3 alternatives
// or at least one range
// or more then one range (exclude triggering on stylistic using OR with one element
// like described https://github.com/rust-lang/rust-clippy/issues/11825)
if let PatKind::Or(pats) = pat.kind
&& (pats.len() >= 3 || pats.iter().any(|p| matches!(p.kind, PatKind::Range(..))))
&& (pats.len() >= 3 || (pats.len() > 1 && pats.iter().any(|p| matches!(p.kind, PatKind::Range(..)))))
&& !in_external_macro(cx.sess(), pat.span)
{
let mut min = Num::dummy(i128::MAX);

View file

@ -14,7 +14,6 @@ use rustc_span::sym;
/// - `hashmap.into_iter().map(|(_, v)| v)`
///
/// on `HashMaps` and `BTreeMaps` in std
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
map_type: &'tcx str, // iter / into_iter

View file

@ -441,6 +441,17 @@ declare_clippy_lint! {
/// }
/// }
/// ```
///
/// 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,
@ -3964,7 +3975,7 @@ declare_clippy_lint! {
/// ```no_run
/// let _ = 0;
/// ```
#[clippy::version = "1.78.0"]
#[clippy::version = "1.81.0"]
pub UNNECESSARY_MIN_OR_MAX,
complexity,
"using 'min()/max()' when there is no need for it"
@ -4025,7 +4036,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.78.0"]
pub MANUAL_C_STR_LITERALS,
pedantic,
complexity,
r#"creating a `CStr` through functions when `c""` literals can be used"#
}
@ -4099,7 +4110,7 @@ declare_clippy_lint! {
/// ```no_run
/// "foo".is_ascii();
/// ```
#[clippy::version = "1.80.0"]
#[clippy::version = "1.81.0"]
pub NEEDLESS_CHARACTER_ITERATION,
suspicious,
"is_ascii() called on a char iterator"

View file

@ -1,16 +1,15 @@
use std::cmp::Ordering;
use super::UNNECESSARY_MIN_OR_MAX;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::consts::{ConstEvalCtxt, Constant, ConstantSource, FullInt};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::Span;
use rustc_span::{sym, Span};
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
@ -21,26 +20,30 @@ pub(super) fn check<'tcx>(
) {
let typeck_results = cx.typeck_results();
let ecx = ConstEvalCtxt::with_env(cx.tcx, cx.param_env, typeck_results);
if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv)
&& let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg)
if let Some(id) = typeck_results.type_dependent_def_id(expr.hir_id)
&& (cx.tcx.is_diagnostic_item(sym::cmp_ord_min, id) || cx.tcx.is_diagnostic_item(sym::cmp_ord_max, id))
{
let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else {
return;
};
if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv)
&& let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg)
{
let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else {
return;
};
lint(cx, expr, name, recv.span, arg.span, ord);
} else if let Some(extrema) = detect_extrema(cx, recv) {
let ord = match extrema {
Extrema::Minimum => Ordering::Less,
Extrema::Maximum => Ordering::Greater,
};
lint(cx, expr, name, recv.span, arg.span, ord);
} else if let Some(extrema) = detect_extrema(cx, arg) {
let ord = match extrema {
Extrema::Minimum => Ordering::Greater,
Extrema::Maximum => Ordering::Less,
};
lint(cx, expr, name, recv.span, arg.span, ord);
lint(cx, expr, name, recv.span, arg.span, ord);
} else if let Some(extrema) = detect_extrema(cx, recv) {
let ord = match extrema {
Extrema::Minimum => Ordering::Less,
Extrema::Maximum => Ordering::Greater,
};
lint(cx, expr, name, recv.span, arg.span, ord);
} else if let Some(extrema) = detect_extrema(cx, arg) {
let ord = match extrema {
Extrema::Minimum => Ordering::Greater,
Extrema::Maximum => Ordering::Less,
};
lint(cx, expr, name, recv.span, arg.span, ord);
}
}
}

View file

@ -5,7 +5,8 @@ use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability;
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, GenericArgKind};
use rustc_middle::ty;
use rustc_middle::ty::GenericArgKind;
use rustc_span::sym;
use rustc_span::symbol::Ident;
use std::iter;

View file

@ -7,6 +7,7 @@ use clippy_utils::{
};
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
BinOpKind, BindingMode, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, QPath, Stmt, StmtKind,
@ -80,6 +81,45 @@ declare_clippy_lint! {
"using a binding which is prefixed with an underscore"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for the use of item with a single leading
/// underscore.
///
/// ### Why is this bad?
/// A single leading underscore is usually used to indicate
/// that a item will not be used. Using such a item breaks this
/// expectation.
///
/// ### Example
/// ```no_run
/// fn _foo() {}
///
/// struct _FooStruct {}
///
/// fn main() {
/// _foo();
/// let _ = _FooStruct{};
/// }
/// ```
///
/// Use instead:
/// ```no_run
/// fn foo() {}
///
/// struct FooStruct {}
///
/// fn main() {
/// foo();
/// let _ = FooStruct{};
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub USED_UNDERSCORE_ITEMS,
pedantic,
"using a item which is prefixed with an underscore"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for the use of short circuit boolean conditions as
@ -104,6 +144,7 @@ declare_clippy_lint! {
declare_lint_pass!(LintPass => [
TOPLEVEL_REF_ARG,
USED_UNDERSCORE_BINDING,
USED_UNDERSCORE_ITEMS,
SHORT_CIRCUIT_STATEMENT,
]);
@ -205,51 +246,104 @@ impl<'tcx> LateLintPass<'tcx> for LintPass {
{
return;
}
let (definition_hir_id, ident) = match expr.kind {
ExprKind::Path(ref qpath) => {
if let QPath::Resolved(None, path) = qpath
&& let Res::Local(id) = path.res
&& is_used(cx, expr)
{
(id, last_path_segment(qpath).ident)
} else {
return;
}
},
ExprKind::Field(recv, ident) => {
if let Some(adt_def) = cx.typeck_results().expr_ty_adjusted(recv).ty_adt_def()
&& let Some(field) = adt_def.all_fields().find(|field| field.name == ident.name)
&& let Some(local_did) = field.did.as_local()
&& !cx.tcx.type_of(field.did).skip_binder().is_phantom_data()
{
(cx.tcx.local_def_id_to_hir_id(local_did), ident)
} else {
return;
}
},
_ => return,
};
let name = ident.name.as_str();
if name.starts_with('_')
&& !name.starts_with("__")
&& let definition_span = cx.tcx.hir().span(definition_hir_id)
&& !definition_span.from_expansion()
&& !fulfill_or_allowed(cx, USED_UNDERSCORE_BINDING, [expr.hir_id, definition_hir_id])
{
span_lint_and_then(
cx,
USED_UNDERSCORE_BINDING,
expr.span,
format!(
"used binding `{name}` which is prefixed with an underscore. A leading \
underscore signals that a binding will not be used"
),
|diag| {
diag.span_note(definition_span, format!("`{name}` is defined here"));
},
);
}
used_underscore_binding(cx, expr);
used_underscore_items(cx, expr);
}
}
fn used_underscore_items<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (def_id, ident) = match expr.kind {
ExprKind::Call(func, ..) => {
if let ExprKind::Path(QPath::Resolved(.., path)) = func.kind
&& let Some(last_segment) = path.segments.last()
&& let Res::Def(_, def_id) = last_segment.res
{
(def_id, last_segment.ident)
} else {
return;
}
},
ExprKind::MethodCall(path, ..) => {
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
(def_id, path.ident)
} else {
return;
}
},
ExprKind::Struct(QPath::Resolved(_, path), ..) => {
if let Some(last_segment) = path.segments.last()
&& let Res::Def(_, def_id) = last_segment.res
{
(def_id, last_segment.ident)
} else {
return;
}
},
_ => return,
};
let name = ident.name.as_str();
let definition_span = cx.tcx.def_span(def_id);
if name.starts_with('_')
&& !name.starts_with("__")
&& !definition_span.from_expansion()
&& def_id.krate == LOCAL_CRATE
{
span_lint_and_then(
cx,
USED_UNDERSCORE_ITEMS,
expr.span,
"used underscore-prefixed item".to_string(),
|diag| {
diag.span_note(definition_span, "item is defined here".to_string());
},
);
}
}
fn used_underscore_binding<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (definition_hir_id, ident) = match expr.kind {
ExprKind::Path(ref qpath) => {
if let QPath::Resolved(None, path) = qpath
&& let Res::Local(id) = path.res
&& is_used(cx, expr)
{
(id, last_path_segment(qpath).ident)
} else {
return;
}
},
ExprKind::Field(recv, ident) => {
if let Some(adt_def) = cx.typeck_results().expr_ty_adjusted(recv).ty_adt_def()
&& let Some(field) = adt_def.all_fields().find(|field| field.name == ident.name)
&& let Some(local_did) = field.did.as_local()
&& !cx.tcx.type_of(field.did).skip_binder().is_phantom_data()
{
(cx.tcx.local_def_id_to_hir_id(local_did), ident)
} else {
return;
}
},
_ => return,
};
let name = ident.name.as_str();
if name.starts_with('_')
&& !name.starts_with("__")
&& let definition_span = cx.tcx.hir().span(definition_hir_id)
&& !definition_span.from_expansion()
&& !fulfill_or_allowed(cx, USED_UNDERSCORE_BINDING, [expr.hir_id, definition_hir_id])
{
span_lint_and_then(
cx,
USED_UNDERSCORE_BINDING,
expr.span,
"used underscore-prefixed binding".to_string(),
|diag| {
diag.span_note(definition_span, "binding is defined here".to_string());
},
);
}
}

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id};
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, LetStmt, Node, Stmt, StmtKind};
@ -134,6 +135,11 @@ impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
}
fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) {
if let Some(macro_call) = root_macro_call_first_node(self.cx, e) {
if self.cx.tcx.item_name(macro_call.def_id).as_str() == "todo" {
return;
}
}
span_lint(self.cx, DIVERGING_SUB_EXPRESSION, e.span, "sub-expression diverges");
}
}

View file

@ -96,10 +96,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
self.found = true;
return;
},
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
if adj

View file

@ -26,7 +26,7 @@ declare_clippy_lint! {
///
/// // or choose alternative bounds for `T` so that it can be unsized
/// ```
#[clippy::version = "1.79.0"]
#[clippy::version = "1.81.0"]
pub NEEDLESS_MAYBE_SIZED,
suspicious,
"a `?Sized` bound that is unusable due to a `Sized` requirement"

View file

@ -129,7 +129,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
})
.collect::<Vec<_>>();
// Collect moved variables and spans which will need dereferencings from the
// Collect moved variables and spans which will need dereferencing from the
// function body.
let MovedVariablesCtxt { moved_vars } = {
let mut ctx = MovedVariablesCtxt::default();
@ -148,12 +148,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
return;
}
// Ignore `self`s.
if idx == 0 {
if let PatKind::Binding(.., ident, _) = arg.pat.kind {
if ident.name == kw::SelfLower {
continue;
}
// Ignore `self`s and params whose variable name starts with an underscore
if let PatKind::Binding(.., ident, _) = arg.pat.kind {
if idx == 0 && ident.name == kw::SelfLower {
continue;
}
if ident.name.as_str().starts_with('_') {
continue;
}
}

View file

@ -1,8 +1,9 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_in_test;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use rustc_hir::Expr;
use clippy_utils::{is_in_test, match_def_path, paths};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
@ -95,10 +96,49 @@ impl_lint_pass!(PanicUnimplemented => [UNIMPLEMENTED, UNREACHABLE, TODO, PANIC])
impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
return;
};
if is_panic(cx, macro_call.def_id) {
if let Some(macro_call) = root_macro_call_first_node(cx, expr) {
if is_panic(cx, macro_call.def_id) {
if cx.tcx.hir().is_inside_const_context(expr.hir_id)
|| self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id)
{
return;
}
span_lint(
cx,
PANIC,
macro_call.span,
"`panic` should not be present in production code",
);
return;
}
match cx.tcx.item_name(macro_call.def_id).as_str() {
"todo" => {
span_lint(
cx,
TODO,
macro_call.span,
"`todo` should not be present in production code",
);
},
"unimplemented" => {
span_lint(
cx,
UNIMPLEMENTED,
macro_call.span,
"`unimplemented` should not be present in production code",
);
},
"unreachable" => {
span_lint(cx, UNREACHABLE, macro_call.span, "usage of the `unreachable!` macro");
},
_ => {},
}
} else if let ExprKind::Call(func, [_]) = expr.kind
&& let ExprKind::Path(QPath::Resolved(None, expr_path)) = func.kind
&& let Res::Def(DefKind::Fn, def_id) = expr_path.res
&& match_def_path(cx, def_id, &paths::PANIC_ANY)
{
if cx.tcx.hir().is_inside_const_context(expr.hir_id)
|| self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id)
{
@ -108,32 +148,10 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
span_lint(
cx,
PANIC,
macro_call.span,
"`panic` should not be present in production code",
expr.span,
"`panic_any` should not be present in production code",
);
return;
}
match cx.tcx.item_name(macro_call.def_id).as_str() {
"todo" => {
span_lint(
cx,
TODO,
macro_call.span,
"`todo` should not be present in production code",
);
},
"unimplemented" => {
span_lint(
cx,
UNIMPLEMENTED,
macro_call.span,
"`unimplemented` should not be present in production code",
);
},
"unreachable" => {
span_lint(cx, UNREACHABLE, macro_call.span, "usage of the `unreachable!` macro");
},
_ => {},
}
}
}

View file

@ -0,0 +1,88 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::InlineAsmOptions;
use rustc_hir::{Expr, ExprKind, InlineAsm, InlineAsmOperand};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks if any pointer is being passed to an asm! block with `nomem` option.
///
/// ### Why is this bad?
/// `nomem` forbids any reads or writes to memory and passing a pointer suggests
/// that either of those will happen.
///
/// ### Example
/// ```no_run
/// fn f(p: *mut u32) {
/// unsafe { core::arch::asm!("mov [{p}], 42", p = in(reg) p, options(nomem, nostack)); }
/// }
/// ```
/// Use instead:
/// ```no_run
/// fn f(p: *mut u32) {
/// unsafe { core::arch::asm!("mov [{p}], 42", p = in(reg) p, options(nostack)); }
/// }
/// ```
#[clippy::version = "1.81.0"]
pub POINTERS_IN_NOMEM_ASM_BLOCK,
suspicious,
"pointers in nomem asm block"
}
declare_lint_pass!(PointersInNomemAsmBlock => [POINTERS_IN_NOMEM_ASM_BLOCK]);
impl<'tcx> LateLintPass<'tcx> for PointersInNomemAsmBlock {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::InlineAsm(asm) = &expr.kind {
check_asm(cx, asm);
}
}
}
fn check_asm(cx: &LateContext<'_>, asm: &InlineAsm<'_>) {
if !asm.options.contains(InlineAsmOptions::NOMEM) {
return;
}
let spans = asm
.operands
.iter()
.filter(|(op, _span)| has_in_operand_pointer(cx, op))
.map(|(_op, span)| *span)
.collect::<Vec<Span>>();
if spans.is_empty() {
return;
}
span_lint_and_then(
cx,
POINTERS_IN_NOMEM_ASM_BLOCK,
spans,
"passing pointers to nomem asm block",
additional_notes,
);
}
fn has_in_operand_pointer(cx: &LateContext<'_>, asm_op: &InlineAsmOperand<'_>) -> bool {
let asm_in_expr = match asm_op {
InlineAsmOperand::SymStatic { .. }
| InlineAsmOperand::Out { .. }
| InlineAsmOperand::Const { .. }
| InlineAsmOperand::SymFn { .. }
| InlineAsmOperand::Label { .. } => return false,
InlineAsmOperand::SplitInOut { in_expr, .. } => in_expr,
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => expr,
};
// This checks for raw ptrs, refs and function pointers - the last one
// also technically counts as reading memory.
cx.typeck_results().expr_ty(asm_in_expr).is_any_ptr()
}
fn additional_notes(diag: &mut rustc_errors::Diag<'_, ()>) {
diag.note("`nomem` means that no memory write or read happens inside the asm! block");
diag.note("if this is intentional and no pointers are read or written to, consider allowing the lint");
}

View file

@ -42,7 +42,7 @@ declare_clippy_lint! {
/// println!("inserted {value:?}");
/// }
/// ```
#[clippy::version = "1.80.0"]
#[clippy::version = "1.81.0"]
pub SET_CONTAINS_OR_INSERT,
nursery,
"call to `<set>::contains` followed by `<set>::insert`"

View file

@ -33,7 +33,7 @@ declare_clippy_lint! {
/// ```no_run
/// "Hello World!".trim_end_matches(['.', ',', '!', '?']);
/// ```
#[clippy::version = "1.80.0"]
#[clippy::version = "1.81.0"]
pub MANUAL_PATTERN_CHAR_COMPARISON,
style,
"manual char comparison in string patterns"

View file

@ -0,0 +1,334 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{fn_def_id, get_enclosing_block, match_any_def_paths, match_def_path, path_to_local_id, paths};
use rustc_ast::Mutability;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_block, walk_expr, walk_local, Visitor};
use rustc_hir::{Expr, ExprKind, HirId, LetStmt, Node, PatKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use std::ops::ControlFlow;
use ControlFlow::{Break, Continue};
declare_clippy_lint! {
/// ### What it does
/// Looks for code that spawns a process but never calls `wait()` on the child.
///
/// ### Why is this bad?
/// As explained in the [standard library documentation](https://doc.rust-lang.org/stable/std/process/struct.Child.html#warning),
/// calling `wait()` is necessary on Unix platforms to properly release all OS resources associated with the process.
/// Not doing so will effectively leak process IDs and/or other limited global resources,
/// which can eventually lead to resource exhaustion, so it's recommended to call `wait()` in long-running applications.
/// Such processes are called "zombie processes".
///
/// ### Example
/// ```rust
/// use std::process::Command;
///
/// let _child = Command::new("ls").spawn().expect("failed to execute child");
/// ```
/// Use instead:
/// ```rust
/// use std::process::Command;
///
/// let mut child = Command::new("ls").spawn().expect("failed to execute child");
/// child.wait().expect("failed to wait on child");
/// ```
#[clippy::version = "1.74.0"]
pub ZOMBIE_PROCESSES,
suspicious,
"not waiting on a spawned child process"
}
declare_lint_pass!(ZombieProcesses => [ZOMBIE_PROCESSES]);
impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::Call(..) | ExprKind::MethodCall(..) = expr.kind
&& let Some(child_adt) = cx.typeck_results().expr_ty(expr).ty_adt_def()
&& match_def_path(cx, child_adt.did(), &paths::CHILD)
{
match cx.tcx.parent_hir_node(expr.hir_id) {
Node::LetStmt(local)
if let PatKind::Binding(_, local_id, ..) = local.pat.kind
&& let Some(enclosing_block) = get_enclosing_block(cx, expr.hir_id) =>
{
let mut vis = WaitFinder::WalkUpTo(cx, local_id);
// If it does have a `wait()` call, we're done. Don't lint.
if let Break(BreakReason::WaitFound) = walk_block(&mut vis, enclosing_block) {
return;
}
// Don't emit a suggestion since the binding is used later
check(cx, expr, false);
},
Node::LetStmt(&LetStmt { pat, .. }) if let PatKind::Wild = pat.kind => {
// `let _ = child;`, also dropped immediately without `wait()`ing
check(cx, expr, true);
},
Node::Stmt(&Stmt {
kind: StmtKind::Semi(_),
..
}) => {
// Immediately dropped. E.g. `std::process::Command::new("echo").spawn().unwrap();`
check(cx, expr, true);
},
_ => {},
}
}
}
}
enum BreakReason {
WaitFound,
EarlyReturn,
}
/// A visitor responsible for finding a `wait()` call on a local variable.
///
/// Conditional `wait()` calls are assumed to not call wait:
/// ```ignore
/// let mut c = Command::new("").spawn().unwrap();
/// if true {
/// c.wait();
/// }
/// ```
///
/// Note that this visitor does NOT explicitly look for `wait()` calls directly, but rather does the
/// inverse -- checking if all uses of the local are either:
/// - a field access (`child.{stderr,stdin,stdout}`)
/// - calling `id` or `kill`
/// - no use at all (e.g. `let _x = child;`)
/// - taking a shared reference (`&`), `wait()` can't go through that
///
/// None of these are sufficient to prevent zombie processes.
/// Doing it like this means more FNs, but FNs are better than FPs.
///
/// `return` expressions, conditional or not, short-circuit the visitor because
/// if a `wait()` call hadn't been found at that point, it might never reach one at a later point:
/// ```ignore
/// let mut c = Command::new("").spawn().unwrap();
/// if true {
/// return; // Break(BreakReason::EarlyReturn)
/// }
/// c.wait(); // this might not be reachable
/// ```
enum WaitFinder<'a, 'tcx> {
WalkUpTo(&'a LateContext<'tcx>, HirId),
Found(&'a LateContext<'tcx>, HirId),
}
impl<'a, 'tcx> Visitor<'tcx> for WaitFinder<'a, 'tcx> {
type Result = ControlFlow<BreakReason>;
fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) -> Self::Result {
if let Self::WalkUpTo(cx, local_id) = *self
&& let PatKind::Binding(_, pat_id, ..) = l.pat.kind
&& local_id == pat_id
{
*self = Self::Found(cx, local_id);
}
walk_local(self, l)
}
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result {
let Self::Found(cx, local_id) = *self else {
return walk_expr(self, ex);
};
if path_to_local_id(ex, local_id) {
match cx.tcx.parent_hir_node(ex.hir_id) {
Node::Stmt(Stmt {
kind: StmtKind::Semi(_),
..
}) => {},
Node::Expr(expr) if let ExprKind::Field(..) = expr.kind => {},
Node::Expr(expr) if let ExprKind::AddrOf(_, Mutability::Not, _) = expr.kind => {},
Node::Expr(expr)
if let Some(fn_did) = fn_def_id(cx, expr)
&& match_any_def_paths(cx, fn_did, &[&paths::CHILD_ID, &paths::CHILD_KILL]).is_some() => {},
// Conservatively assume that all other kinds of nodes call `.wait()` somehow.
_ => return Break(BreakReason::WaitFound),
}
} else {
match ex.kind {
ExprKind::Ret(..) => return Break(BreakReason::EarlyReturn),
ExprKind::If(cond, then, None) => {
walk_expr(self, cond)?;
// A `wait()` call in an if expression with no else is not enough:
//
// let c = spawn();
// if true {
// c.wait();
// }
//
// This might not call wait(). However, early returns are propagated,
// because they might lead to a later wait() not being called.
if let Break(BreakReason::EarlyReturn) = walk_expr(self, then) {
return Break(BreakReason::EarlyReturn);
}
return Continue(());
},
ExprKind::If(cond, then, Some(else_)) => {
walk_expr(self, cond)?;
#[expect(clippy::unnested_or_patterns)]
match (walk_expr(self, then), walk_expr(self, else_)) {
(Continue(()), Continue(()))
// `wait()` in one branch but nothing in the other does not count
| (Continue(()), Break(BreakReason::WaitFound))
| (Break(BreakReason::WaitFound), Continue(())) => {},
// `wait()` in both branches is ok
(Break(BreakReason::WaitFound), Break(BreakReason::WaitFound)) => {
return Break(BreakReason::WaitFound);
},
// Propagate early returns in either branch
(Break(BreakReason::EarlyReturn), _) | (_, Break(BreakReason::EarlyReturn)) => {
return Break(BreakReason::EarlyReturn);
},
}
return Continue(());
},
_ => {},
}
}
walk_expr(self, ex)
}
}
/// This function has shared logic between the different kinds of nodes that can trigger the lint.
///
/// In particular, `let <binding> = <expr that spawns child>;` requires some custom additional logic
/// such as checking that the binding is not used in certain ways, which isn't necessary for
/// `let _ = <expr that spawns child>;`.
///
/// This checks if the program doesn't unconditionally exit after the spawn expression.
fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, emit_suggestion: bool) {
let Some(block) = get_enclosing_block(cx, spawn_expr.hir_id) else {
return;
};
let mut vis = ExitPointFinder {
cx,
state: ExitPointState::WalkUpTo(spawn_expr.hir_id),
};
if let Break(ExitCallFound) = vis.visit_block(block) {
// Visitor found an unconditional `exit()` call, so don't lint.
return;
}
span_lint_and_then(
cx,
ZOMBIE_PROCESSES,
spawn_expr.span,
"spawned process is never `wait()`ed on",
|diag| {
if emit_suggestion {
diag.span_suggestion(
spawn_expr.span.shrink_to_hi(),
"try",
".wait()",
Applicability::MaybeIncorrect,
);
} else {
diag.note("consider calling `.wait()`");
}
diag.note("not doing so might leave behind zombie processes")
.note("see https://doc.rust-lang.org/stable/std/process/struct.Child.html#warning");
},
);
}
/// Checks if the given expression exits the process.
fn is_exit_expression(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
fn_def_id(cx, expr).is_some_and(|fn_did| {
cx.tcx.is_diagnostic_item(sym::process_exit, fn_did) || match_def_path(cx, fn_did, &paths::ABORT)
})
}
#[derive(Debug)]
enum ExitPointState {
/// Still walking up to the expression that initiated the visitor.
WalkUpTo(HirId),
/// We're inside of a control flow construct (e.g. `if`, `match`, `loop`)
/// Within this, we shouldn't accept any `exit()` calls in here, but we can leave all of these
/// constructs later and still continue looking for an `exit()` call afterwards. Example:
/// ```ignore
/// Command::new("").spawn().unwrap();
///
/// if true { // depth=1
/// if true { // depth=2
/// match () { // depth=3
/// () => loop { // depth=4
///
/// std::process::exit();
/// ^^^^^^^^^^^^^^^^^^^^^ conditional exit call, ignored
///
/// } // depth=3
/// } // depth=2
/// } // depth=1
/// } // depth=0
///
/// std::process::exit();
/// ^^^^^^^^^^^^^^^^^^^^^ this exit call is accepted because we're now unconditionally calling it
/// ```
/// We can only get into this state from `NoExit`.
InControlFlow { depth: u32 },
/// No exit call found yet, but looking for one.
NoExit,
}
fn expr_enters_control_flow(expr: &Expr<'_>) -> bool {
matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Loop(..))
}
struct ExitPointFinder<'a, 'tcx> {
state: ExitPointState,
cx: &'a LateContext<'tcx>,
}
struct ExitCallFound;
impl<'a, 'tcx> Visitor<'tcx> for ExitPointFinder<'a, 'tcx> {
type Result = ControlFlow<ExitCallFound>;
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result {
match self.state {
ExitPointState::WalkUpTo(id) if expr.hir_id == id => {
self.state = ExitPointState::NoExit;
walk_expr(self, expr)
},
ExitPointState::NoExit if expr_enters_control_flow(expr) => {
self.state = ExitPointState::InControlFlow { depth: 1 };
walk_expr(self, expr)?;
if let ExitPointState::InControlFlow { .. } = self.state {
self.state = ExitPointState::NoExit;
}
Continue(())
},
ExitPointState::NoExit if is_exit_expression(self.cx, expr) => Break(ExitCallFound),
ExitPointState::InControlFlow { ref mut depth } if expr_enters_control_flow(expr) => {
*depth += 1;
walk_expr(self, expr)?;
match self.state {
ExitPointState::InControlFlow { depth: 1 } => self.state = ExitPointState::NoExit,
ExitPointState::InControlFlow { ref mut depth } => *depth -= 1,
_ => {},
}
Continue(())
},
_ => Continue(()),
}
}
}

View file

@ -183,15 +183,15 @@ pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
let mut iter = tokenize_with_text(src);
// Search for the token sequence [`#`, `[`, `cfg`]
while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) {
let mut iter = iter.by_ref().skip_while(|(t, _)| {
while iter.any(|(t, ..)| matches!(t, TokenKind::Pound)) {
let mut iter = iter.by_ref().skip_while(|(t, ..)| {
matches!(
t,
TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. }
)
});
if matches!(iter.next(), Some((TokenKind::OpenBracket, _)))
&& matches!(iter.next(), Some((TokenKind::Ident, "cfg")))
if matches!(iter.next(), Some((TokenKind::OpenBracket, ..)))
&& matches!(iter.next(), Some((TokenKind::Ident, "cfg", _)))
{
return true;
}

View file

@ -1194,8 +1194,8 @@ fn eq_span_tokens(
&& let Some(rsrc) = right.get_source_range(cx)
&& let Some(rsrc) = rsrc.as_str()
{
let pred = |t: &(_, _)| pred(t.0);
let map = |(_, x)| x;
let pred = |&(token, ..): &(TokenKind, _, _)| pred(token);
let map = |(_, source, _)| source;
let ltok = tokenize_with_text(lsrc).filter(pred).map(map);
let rtok = tokenize_with_text(rsrc).filter(pred).map(map);

View file

@ -121,7 +121,7 @@ use rustc_middle::ty::{
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{kw, Ident, Symbol};
use rustc_span::{sym, Span};
use rustc_span::{sym, InnerSpan, Span};
use rustc_target::abi::Integer;
use visitors::Visitable;
@ -2950,13 +2950,14 @@ pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> ExprU
}
/// Tokenizes the input while keeping the text associated with each token.
pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str)> {
pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str, InnerSpan)> {
let mut pos = 0;
tokenize(s).map(move |t| {
let end = pos + t.len;
let range = pos as usize..end as usize;
let inner = InnerSpan::new(range.start, range.end);
pos = end;
(t.kind, s.get(range).unwrap_or_default())
(t.kind, s.get(range).unwrap_or_default(), inner)
})
}
@ -2980,8 +2981,8 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String {
let snippet = sm.span_to_snippet(span).unwrap_or_default();
let res = tokenize_with_text(&snippet)
.filter(|(t, _)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }))
.map(|(_, s)| s)
.filter(|(t, ..)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }))
.map(|(_, s, _)| s)
.join("\n");
res
}
@ -3001,7 +3002,7 @@ pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {
/// pat: Some(a)
/// else_body: return None
/// ```
///
/// And for this example:
/// ```ignore
/// let Some(FooBar { a, b }) = ex else { return None };
@ -3011,7 +3012,7 @@ pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {
/// pat: Some(FooBar { a, b })
/// else_body: return None
/// ```
///
/// We output `Some(a)` in the first instance, and `Some(FooBar { a, b })` in the second, because
/// the question mark operator is applicable here. Callers have to check whether we are in a
/// constant or not.

View file

@ -4,6 +4,7 @@
//! Whenever possible, please consider diagnostic items over hardcoded paths.
//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
pub const ABORT: [&str; 3] = ["std", "process", "abort"];
pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"];
pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
["rustc_lint_defs", "Applicability", "Unspecified"],
@ -23,6 +24,9 @@ pub const CORE_RESULT_OK_METHOD: [&str; 4] = ["core", "result", "Result", "ok"];
pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"];
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
pub const CHILD: [&str; 3] = ["std", "process", "Child"];
pub const CHILD_ID: [&str; 4] = ["std", "process", "Child", "id"];
pub const CHILD_KILL: [&str; 4] = ["std", "process", "Child", "kill"];
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
pub const FILE_OPTIONS: [&str; 4] = ["std", "fs", "File", "options"];
@ -56,6 +60,7 @@ pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to
pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"];
pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"];
pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"];
pub const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"];
pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
pub const PATH_MAIN_SEPARATOR: [&str; 3] = ["std", "path", "MAIN_SEPARATOR"];
pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];

View file

@ -666,39 +666,6 @@ pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> {
(outer_span.ctxt() == outer).then_some(outer_span)
}
/// Removes block comments from the given `Vec` of lines.
///
/// # Examples
///
/// ```rust,ignore
/// without_block_comments(vec!["/*", "foo", "*/"]);
/// // => vec![]
///
/// without_block_comments(vec!["bar", "/*", "foo", "*/"]);
/// // => vec!["bar"]
/// ```
pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> {
let mut without = vec![];
let mut nest_level = 0;
for line in lines {
if line.contains("/*") {
nest_level += 1;
continue;
} else if line.contains("*/") {
nest_level -= 1;
continue;
}
if nest_level == 0 {
without.push(line);
}
}
without
}
/// Trims the whitespace from the start and the end of the span.
pub fn trim_span(sm: &SourceMap, span: Span) -> Span {
let data = span.data();
@ -776,7 +743,7 @@ pub fn str_literal_to_char_literal(
#[cfg(test)]
mod test {
use super::{reindent_multiline, without_block_comments};
use super::reindent_multiline;
#[test]
fn test_reindent_multiline_single_line() {
@ -844,29 +811,4 @@ mod test {
z
}".into(), true, Some(8)));
}
#[test]
fn test_without_block_comments_lines_without_block_comments() {
let result = without_block_comments(vec!["/*", "", "*/"]);
println!("result: {result:?}");
assert!(result.is_empty());
let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]);
assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]);
let result = without_block_comments(vec!["/* rust", "", "*/"]);
assert!(result.is_empty());
let result = without_block_comments(vec!["/* one-line comment */"]);
assert!(result.is_empty());
let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]);
assert!(result.is_empty());
let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]);
assert!(result.is_empty());
let result = without_block_comments(vec!["foo", "bar", "baz"]);
assert_eq!(result, vec!["foo", "bar", "baz"]);
}
}

View file

@ -180,8 +180,10 @@ impl<'a> Sugg<'a> {
) -> Self {
use rustc_ast::ast::RangeLimits;
let mut snippet = |span: Span| snippet_with_context(cx, span, ctxt, default, app).0;
match expr.kind {
_ if expr.span.ctxt() != ctxt => Sugg::NonParen(snippet_with_context(cx, expr.span, ctxt, default, app).0),
_ if expr.span.ctxt() != ctxt => Sugg::NonParen(snippet(expr.span)),
ast::ExprKind::AddrOf(..)
| ast::ExprKind::Closure { .. }
| ast::ExprKind::If(..)
@ -224,46 +226,38 @@ impl<'a> Sugg<'a> {
| ast::ExprKind::While(..)
| ast::ExprKind::Await(..)
| ast::ExprKind::Err(_)
| ast::ExprKind::Dummy => Sugg::NonParen(snippet_with_context(cx, expr.span, ctxt, default, app).0),
| ast::ExprKind::Dummy => Sugg::NonParen(snippet(expr.span)),
ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::HalfOpen) => Sugg::BinOp(
AssocOp::DotDot,
lhs.as_ref().map_or("".into(), |lhs| {
snippet_with_context(cx, lhs.span, ctxt, default, app).0
}),
rhs.as_ref().map_or("".into(), |rhs| {
snippet_with_context(cx, rhs.span, ctxt, default, app).0
}),
lhs.as_ref().map_or("".into(), |lhs| snippet(lhs.span)),
rhs.as_ref().map_or("".into(), |rhs| snippet(rhs.span)),
),
ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::Closed) => Sugg::BinOp(
AssocOp::DotDotEq,
lhs.as_ref().map_or("".into(), |lhs| {
snippet_with_context(cx, lhs.span, ctxt, default, app).0
}),
rhs.as_ref().map_or("".into(), |rhs| {
snippet_with_context(cx, rhs.span, ctxt, default, app).0
}),
lhs.as_ref().map_or("".into(), |lhs| snippet(lhs.span)),
rhs.as_ref().map_or("".into(), |rhs| snippet(rhs.span)),
),
ast::ExprKind::Assign(ref lhs, ref rhs, _) => Sugg::BinOp(
AssocOp::Assign,
snippet_with_context(cx, lhs.span, ctxt, default, app).0,
snippet_with_context(cx, rhs.span, ctxt, default, app).0,
snippet(lhs.span),
snippet(rhs.span),
),
ast::ExprKind::AssignOp(op, ref lhs, ref rhs) => Sugg::BinOp(
astbinop2assignop(op),
snippet_with_context(cx, lhs.span, ctxt, default, app).0,
snippet_with_context(cx, rhs.span, ctxt, default, app).0,
snippet(lhs.span),
snippet(rhs.span),
),
ast::ExprKind::Binary(op, ref lhs, ref rhs) => Sugg::BinOp(
AssocOp::from_ast_binop(op.node),
snippet_with_context(cx, lhs.span, ctxt, default, app).0,
snippet_with_context(cx, rhs.span, ctxt, default, app).0,
snippet(lhs.span),
snippet(rhs.span),
),
ast::ExprKind::Cast(ref lhs, ref ty) |
//FIXME(chenyukang), remove this after type ascription is removed from AST
ast::ExprKind::Type(ref lhs, ref ty) => Sugg::BinOp(
AssocOp::As,
snippet_with_context(cx, lhs.span, ctxt, default, app).0,
snippet_with_context(cx, ty.span, ctxt, default, app).0,
snippet(lhs.span),
snippet(ty.span),
),
}
}

View file

@ -268,8 +268,6 @@ pub fn main() {
},
_ => Some(s.to_string()),
})
// FIXME: remove this line in 1.79 to only keep `--cfg clippy`.
.chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()])
.chain(vec!["--cfg".into(), "clippy".into()])
.collect::<Vec<String>>();

View file

@ -19,8 +19,8 @@ LL | const DEREF_MUT_TRAIT: [&str; 4] = ["core", "ops", "deref", "DerefMut"]
error: hardcoded path to a diagnostic item
--> tests/ui-internal/unnecessary_def_path_hardcoded_path.rs:12:43
|
LL | const OPS_MOD: [&str; 5] = ["core", "ops"];
| ^^^^^^^^^^^^^^^
LL | const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: convert all references to use `sym::deref_method`

View file

@ -1,4 +1,4 @@
#[warn(clippy::disallowed_names)]
#![warn(clippy::disallowed_names)]
fn main() {
// `foo` is part of the default configuration

View file

@ -1,4 +1,4 @@
#[warn(clippy::disallowed_names)]
#![warn(clippy::disallowed_names)]
fn main() {
// `foo` is part of the default configuration

View file

@ -1,5 +1,6 @@
//@compile-flags: --test
#![warn(clippy::panic)]
use std::panic::panic_any;
fn main() {
enum Enam {
@ -12,6 +13,10 @@ fn main() {
}
}
fn issue_13292() {
panic_any("should lint")
}
#[test]
fn lonely_test() {
enum Enam {

View file

@ -1,5 +1,5 @@
error: `panic` should not be present in production code
--> tests/ui-toml/panic/panic.rs:11:14
--> tests/ui-toml/panic/panic.rs:12:14
|
LL | _ => panic!(""),
| ^^^^^^^^^^
@ -7,5 +7,11 @@ LL | _ => panic!(""),
= note: `-D clippy::panic` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::panic)]`
error: aborting due to 1 previous error
error: `panic_any` should not be present in production code
--> tests/ui-toml/panic/panic.rs:17:5
|
LL | panic_any("should lint")
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 2 previous errors

View file

@ -15,7 +15,6 @@ use proc_macros::{external, with_span};
#[warn(deref_nullptr)]
#[deny(deref_nullptr)]
#[forbid(deref_nullptr)]
fn main() {
external! {
#[allow(dead_code)]

View file

@ -36,7 +36,7 @@ LL | #[expect(dead_code)]
= help: try adding a reason at the end with `, reason = ".."`
error: `allow` attribute without specifying a reason
--> tests/ui/allow_attributes_without_reason.rs:47:5
--> tests/ui/allow_attributes_without_reason.rs:46:5
|
LL | #[allow(unused)]
| ^^^^^^^^^^^^^^^^
@ -44,7 +44,7 @@ LL | #[allow(unused)]
= help: try adding a reason at the end with `, reason = ".."`
error: `allow` attribute without specifying a reason
--> tests/ui/allow_attributes_without_reason.rs:47:5
--> tests/ui/allow_attributes_without_reason.rs:46:5
|
LL | #[allow(unused)]
| ^^^^^^^^^^^^^^^^

View file

@ -0,0 +1,7 @@
pub struct _ExternalStruct {}
impl _ExternalStruct {
pub fn _foo(self) {}
}
pub fn _exernal_foo() {}

View file

@ -2,16 +2,16 @@
#![feature(rustc_private)]
#![feature(core_intrinsics)]
extern crate libc;
#[warn(clippy::cast_ptr_alignment)]
#[allow(
#![warn(clippy::cast_ptr_alignment)]
#![allow(
clippy::no_effect,
clippy::unnecessary_operation,
clippy::cast_lossless,
clippy::borrow_as_ptr
)]
extern crate libc;
fn main() {
/* These should be warned against */

View file

@ -1,5 +1,5 @@
#[allow(clippy::unnecessary_operation)]
#[allow(clippy::implicit_clone)]
#![allow(clippy::unnecessary_operation)]
#![allow(clippy::implicit_clone)]
fn main() {
let x = &Baz;

View file

@ -1,9 +1,7 @@
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_if)]
#![warn(clippy::collapsible_if, clippy::collapsible_else_if)]
#[rustfmt::skip]
#[warn(clippy::collapsible_if)]
#[warn(clippy::collapsible_else_if)]
fn main() {
let x = "hello";
let y = "world";
@ -76,7 +74,6 @@ fn main() {
}
#[rustfmt::skip]
#[allow(dead_code)]
fn issue_7318() {
if true { println!("I've been resolved!")
}else if false {}

View file

@ -1,9 +1,7 @@
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_if)]
#![warn(clippy::collapsible_if, clippy::collapsible_else_if)]
#[rustfmt::skip]
#[warn(clippy::collapsible_if)]
#[warn(clippy::collapsible_else_if)]
fn main() {
let x = "hello";
let y = "world";
@ -90,7 +88,6 @@ fn main() {
}
#[rustfmt::skip]
#[allow(dead_code)]
fn issue_7318() {
if true { println!("I've been resolved!")
}else{

View file

@ -1,5 +1,5 @@
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:13:12
--> tests/ui/collapsible_else_if.rs:11:12
|
LL | } else {
| ____________^
@ -19,7 +19,7 @@ LL + }
|
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:21:12
--> tests/ui/collapsible_else_if.rs:19:12
|
LL | } else {
| ____________^
@ -37,7 +37,7 @@ LL + }
|
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:29:12
--> tests/ui/collapsible_else_if.rs:27:12
|
LL | } else {
| ____________^
@ -60,7 +60,7 @@ LL + }
|
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:40:12
--> tests/ui/collapsible_else_if.rs:38:12
|
LL | } else {
| ____________^
@ -83,7 +83,7 @@ LL + }
|
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:51:12
--> tests/ui/collapsible_else_if.rs:49:12
|
LL | } else {
| ____________^
@ -106,7 +106,7 @@ LL + }
|
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:62:12
--> tests/ui/collapsible_else_if.rs:60:12
|
LL | } else {
| ____________^
@ -129,7 +129,7 @@ LL + }
|
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:73:12
--> tests/ui/collapsible_else_if.rs:71:12
|
LL | } else {
| ____________^
@ -152,7 +152,7 @@ LL + }
|
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:96:10
--> tests/ui/collapsible_else_if.rs:93:10
|
LL | }else{
| __________^

View file

@ -1,4 +1,4 @@
/// Test for https://github.com/rust-lang/rust-clippy/issues/1698
// Test for https://github.com/rust-lang/rust-clippy/issues/1698
pub trait Trait {
const CONSTANT: u8;

View file

@ -1,6 +1,4 @@
#[allow(dead_code)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/478
// Test for https://github.com/rust-lang/rust-clippy/issues/478
enum Baz {
One,

View file

@ -1,4 +1,4 @@
/// Test for https://github.com/rust-lang/rust-clippy/issues/11230
// Test for https://github.com/rust-lang/rust-clippy/issues/11230
fn main() {
const A: &[for<'a> fn(&'a ())] = &[];

View file

@ -1,6 +1,6 @@
#![allow(clippy::all)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/1588
// Test for https://github.com/rust-lang/rust-clippy/issues/1588
fn main() {
match 1 {

View file

@ -1,6 +1,6 @@
#![allow(clippy::all)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/1969
// Test for https://github.com/rust-lang/rust-clippy/issues/1969
fn main() {}

View file

@ -1,8 +1,8 @@
#![allow(dead_code, clippy::char_lit_as_u8, clippy::needless_bool)]
/// Should not trigger an ICE in `SpanlessHash` / `consts::constant`
///
/// Issue: https://github.com/rust-lang/rust-clippy/issues/2499
// Should not trigger an ICE in `SpanlessHash` / `consts::constant`
//
// Issue: https://github.com/rust-lang/rust-clippy/issues/2499
fn f(s: &[u8]) -> bool {
let t = s[0] as char;

View file

@ -3,7 +3,6 @@
/// Should not trigger an ICE in `SpanlessHash` / `consts::constant`
///
/// Issue: https://github.com/rust-lang/rust-clippy/issues/2594
fn spanless_hash_ice() {
let txt = "something";
let empty_header: [u8; 1] = [1; 1];

View file

@ -1,4 +1,4 @@
/// Test for https://github.com/rust-lang/rust-clippy/issues/2727
// Test for https://github.com/rust-lang/rust-clippy/issues/2727
pub fn f(new: fn()) {
new();

View file

@ -5,10 +5,10 @@
dead_code
)]
/// This should not compile-fail with:
///
/// error[E0277]: the trait bound `T: Foo` is not satisfied
// See rust-lang/rust-clippy#2760.
// This should not compile-fail with:
//
// error[E0277]: the trait bound `T: Foo` is not satisfied
// See https://github.com/rust-lang/rust-clippy/issues/2760
trait Foo {
type Bar;

View file

@ -1,4 +1,4 @@
/// Test for https://github.com/rust-lang/rust-clippy/issues/2862
// Test for https://github.com/rust-lang/rust-clippy/issues/2862
pub trait FooMap {
fn map<B, F: Fn() -> B>(&self, f: F) -> B;

View file

@ -1,6 +1,6 @@
#![allow(dead_code, clippy::extra_unused_lifetimes)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/2865
// Test for https://github.com/rust-lang/rust-clippy/issues/2865
struct Ice {
size: String,

View file

@ -1,4 +1,4 @@
/// Test for https://github.com/rust-lang/rust-clippy/issues/3151
// Test for https://github.com/rust-lang/rust-clippy/issues/3151
#[derive(Clone)]
pub struct HashMap<V, S> {

View file

@ -2,7 +2,7 @@
#![allow(clippy::disallowed_names, clippy::equatable_if_let, clippy::needless_if)]
#![allow(unused)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/3462
// Test for https://github.com/rust-lang/rust-clippy/issues/3462
enum Foo {
Bar,

View file

@ -1,4 +1,4 @@
/// Test for https://github.com/rust-lang/rust-clippy/issues/3747
// Test for https://github.com/rust-lang/rust-clippy/issues/3747
macro_rules! a {
( $pub:tt $($attr:tt)* ) => {

View file

@ -1,6 +1,6 @@
#![deny(clippy::all)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/700
// Test for https://github.com/rust-lang/rust-clippy/issues/700
fn core() {}

View file

@ -1,6 +1,6 @@
#![deny(clippy::all)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/1336
// Test for https://github.com/rust-lang/rust-clippy/issues/1336
#[allow(dead_code)]
struct Foo;

View file

@ -1,7 +1,7 @@
#![allow(clippy::comparison_chain)]
#![deny(clippy::if_same_then_else)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/2426
// Test for https://github.com/rust-lang/rust-clippy/issues/2426
fn main() {}

View file

@ -1,6 +1,6 @@
#![deny(clippy::multiple_inherent_impl)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/4578
// Test for https://github.com/rust-lang/rust-clippy/issues/4578
macro_rules! impl_foo {
($struct:ident) => {

View file

@ -1,6 +1,6 @@
#![allow(warnings)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/825
// Test for https://github.com/rust-lang/rust-clippy/issues/825
// this should compile in a reasonable amount of time
fn rust_type_id(name: &str) {

View file

@ -1,6 +1,6 @@
#![deny(clippy::match_same_arms)]
/// Test for https://github.com/rust-lang/rust-clippy/issues/2427
// Test for https://github.com/rust-lang/rust-clippy/issues/2427
const PRICE_OF_SWEETS: u32 = 5;
const PRICE_OF_KINDNESS: u32 = 0;

View file

@ -1,4 +1,4 @@
/// Test for https://github.com/rust-lang/rust-clippy/issues/1346
// Test for https://github.com/rust-lang/rust-clippy/issues/1346
#[deny(warnings)]
fn cfg_return() -> i32 {

View file

@ -1,7 +1,6 @@
#![allow(clippy::non_canonical_clone_impl, clippy::non_canonical_partial_ord_impl, dead_code)]
#![warn(clippy::expl_impl_clone_on_copy)]
#[derive(Copy)]
struct Qux;

View file

@ -1,5 +1,5 @@
error: you are implementing `Clone` explicitly on a `Copy` type
--> tests/ui/derive.rs:8:1
--> tests/ui/derive.rs:7:1
|
LL | / impl Clone for Qux {
LL | |
@ -10,7 +10,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> tests/ui/derive.rs:8:1
--> tests/ui/derive.rs:7:1
|
LL | / impl Clone for Qux {
LL | |
@ -23,7 +23,7 @@ LL | | }
= help: to override `-D warnings` add `#[allow(clippy::expl_impl_clone_on_copy)]`
error: you are implementing `Clone` explicitly on a `Copy` type
--> tests/ui/derive.rs:33:1
--> tests/ui/derive.rs:32:1
|
LL | / impl<'a> Clone for Lt<'a> {
LL | |
@ -34,7 +34,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> tests/ui/derive.rs:33:1
--> tests/ui/derive.rs:32:1
|
LL | / impl<'a> Clone for Lt<'a> {
LL | |
@ -45,7 +45,7 @@ LL | | }
| |_^
error: you are implementing `Clone` explicitly on a `Copy` type
--> tests/ui/derive.rs:45:1
--> tests/ui/derive.rs:44:1
|
LL | / impl Clone for BigArray {
LL | |
@ -56,7 +56,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> tests/ui/derive.rs:45:1
--> tests/ui/derive.rs:44:1
|
LL | / impl Clone for BigArray {
LL | |
@ -67,7 +67,7 @@ LL | | }
| |_^
error: you are implementing `Clone` explicitly on a `Copy` type
--> tests/ui/derive.rs:57:1
--> tests/ui/derive.rs:56:1
|
LL | / impl Clone for FnPtr {
LL | |
@ -78,7 +78,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> tests/ui/derive.rs:57:1
--> tests/ui/derive.rs:56:1
|
LL | / impl Clone for FnPtr {
LL | |
@ -89,7 +89,7 @@ LL | | }
| |_^
error: you are implementing `Clone` explicitly on a `Copy` type
--> tests/ui/derive.rs:78:1
--> tests/ui/derive.rs:77:1
|
LL | / impl<T: Clone> Clone for Generic2<T> {
LL | |
@ -100,7 +100,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> tests/ui/derive.rs:78:1
--> tests/ui/derive.rs:77:1
|
LL | / impl<T: Clone> Clone for Generic2<T> {
LL | |

View file

@ -67,3 +67,9 @@ fn foobar() {
};
}
}
#[allow(unused)]
fn ignore_todo() {
let x: u32 = todo!();
println!("{x}");
}

View file

@ -1,47 +0,0 @@
// https://github.com/rust-lang/rust-clippy/issues/12917
#![warn(clippy::doc_lazy_continuation)]
/// This is a constant.
///
/// The meaning of which should not be explained.
pub const A: i32 = 42;
/// This is another constant, no longer used.
///
/// This block of documentation has a long
/// explanation and derivation to explain
/// why it is what it is, and how it's used.
///
/// It is left here for historical reasons, and
/// for reference.
///
/// Reasons it's great:
/// - First reason
/// - Second reason
///
//pub const B: i32 = 1337;
/// This is yet another constant.
///
/// This has a similar fate as `B`.
///
/// Reasons it's useful:
/// 1. First reason
/// 2. Second reason
///
//pub const C: i32 = 8008;
/// This is still in use.
pub const D: i32 = 20;
/// > blockquote code path
///
/// bottom text
pub const E: i32 = 20;
/// > blockquote code path
///
#[repr(C)]
/// bottom text
pub struct Foo(i32);

View file

@ -1,43 +0,0 @@
// https://github.com/rust-lang/rust-clippy/issues/12917
#![warn(clippy::doc_lazy_continuation)]
/// This is a constant.
///
/// The meaning of which should not be explained.
pub const A: i32 = 42;
/// This is another constant, no longer used.
///
/// This block of documentation has a long
/// explanation and derivation to explain
/// why it is what it is, and how it's used.
///
/// It is left here for historical reasons, and
/// for reference.
///
/// Reasons it's great:
/// - First reason
/// - Second reason
//pub const B: i32 = 1337;
/// This is yet another constant.
///
/// This has a similar fate as `B`.
///
/// Reasons it's useful:
/// 1. First reason
/// 2. Second reason
//pub const C: i32 = 8008;
/// This is still in use.
pub const D: i32 = 20;
/// > blockquote code path
/// bottom text
pub const E: i32 = 20;
/// > blockquote code path
#[repr(C)]
/// bottom text
pub struct Foo(i32);

View file

@ -1,56 +0,0 @@
error: doc list item without indentation
--> tests/ui/doc/doc_lazy_blank_line.rs:23:5
|
LL | /// This is yet another constant.
| ^
|
= help: if this is intended to be part of the list, indent 3 spaces
= note: `-D clippy::doc-lazy-continuation` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::doc_lazy_continuation)]`
help: if this should be its own paragraph, add a blank doc comment line
|
LL ~ /// - Second reason
LL + ///
|
error: doc list item without indentation
--> tests/ui/doc/doc_lazy_blank_line.rs:32:5
|
LL | /// This is still in use.
| ^
|
= help: if this is intended to be part of the list, indent 4 spaces
help: if this should be its own paragraph, add a blank doc comment line
|
LL ~ /// 2. Second reason
LL + ///
|
error: doc quote line without `>` marker
--> tests/ui/doc/doc_lazy_blank_line.rs:37:5
|
LL | /// bottom text
| ^
|
= help: if this not intended to be a quote at all, escape it with `\>`
help: if this should be its own paragraph, add a blank doc comment line
|
LL ~ /// > blockquote code path
LL + ///
|
error: doc quote line without `>` marker
--> tests/ui/doc/doc_lazy_blank_line.rs:42:5
|
LL | /// bottom text
| ^
|
= help: if this not intended to be a quote at all, escape it with `\>`
help: if this should be its own paragraph, add a blank doc comment line
|
LL ~ /// > blockquote code path
LL + ///
|
error: aborting due to 4 previous errors

View file

@ -7,9 +7,8 @@ fn one() {}
/// 1. first line
/// lazy list continuations don't make warnings with this lint
///
//~^ ERROR: doc list item without indentation
/// because they don't have the
/// because they don't have the
//~^ ERROR: doc list item without indentation
fn two() {}
@ -20,9 +19,8 @@ fn three() {}
/// - first line
/// lazy list continuations don't make warnings with this lint
///
//~^ ERROR: doc list item without indentation
/// because they don't have the
/// because they don't have the
//~^ ERROR: doc list item without indentation
fn four() {}
@ -33,9 +31,8 @@ fn five() {}
/// - - first line
/// this will warn on the lazy continuation
///
//~^ ERROR: doc list item without indentation
/// and so should this
/// and so should this
//~^ ERROR: doc list item without indentation
fn six() {}

View file

@ -30,12 +30,11 @@ error: doc list item without indentation
LL | /// because they don't have the
| ^
|
= help: if this is intended to be part of the list, indent 3 spaces
help: if this should be its own paragraph, add a blank doc comment line
|
LL ~ /// lazy list continuations don't make warnings with this lint
LL + ///
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// because they don't have the
| +++
error: doc list item without indentation
--> tests/ui/doc/doc_lazy_list.rs:16:5
@ -67,12 +66,11 @@ error: doc list item without indentation
LL | /// because they don't have the
| ^
|
= help: if this is intended to be part of the list, indent 4 spaces
help: if this should be its own paragraph, add a blank doc comment line
|
LL ~ /// lazy list continuations don't make warnings with this lint
LL + ///
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// because they don't have the
| ++++
error: doc list item without indentation
--> tests/ui/doc/doc_lazy_list.rs:28:5
@ -104,12 +102,11 @@ error: doc list item without indentation
LL | /// and so should this
| ^^^^
|
= help: if this is intended to be part of the list, indent 2 spaces
help: if this should be its own paragraph, add a blank doc comment line
|
LL ~ /// this will warn on the lazy continuation
LL + ///
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// and so should this
| ++
error: doc list item without indentation
--> tests/ui/doc/doc_lazy_list.rs:56:5

View file

@ -1,5 +1,4 @@
#![warn(clippy::duplicate_underscore_argument)]
#[allow(dead_code, unused)]
fn join_the_dark_side(darth: i32, _darth: i32) {}
//~^ ERROR: `darth` already exists, having another argument having almost the same name ma

View file

@ -1,5 +1,5 @@
error: `darth` already exists, having another argument having almost the same name makes code comprehension and documentation more difficult
--> tests/ui/duplicate_underscore_argument.rs:4:23
--> tests/ui/duplicate_underscore_argument.rs:3:23
|
LL | fn join_the_dark_side(darth: i32, _darth: i32) {}
| ^^^^^

View file

@ -0,0 +1,135 @@
#![warn(clippy::empty_line_after_outer_attr, clippy::empty_line_after_doc_comments)]
//~vvv empty_line_after_doc_comments
/// Meant to be an
/// inner doc comment
/// for the crate
fn first_in_crate() {}
mod m {
//~vvv empty_line_after_doc_comments
/// Meant to be an
/// inner doc comment
/// for the module
fn first_in_module() {}
}
mod some_mod {
//! This doc comment should *NOT* produce a warning
mod some_inner_mod {
fn some_noop() {}
}
//~v empty_line_after_doc_comments
/// # Indented
/// Blank line
fn indented() {}
}
//~v empty_line_after_doc_comments
/// This should produce a warning
fn with_doc_and_newline() {}
// This should *NOT* produce a warning
#[crate_type = "lib"]
/// some comment
fn with_no_newline_and_comment() {}
//~v empty_line_after_doc_comments
/// This doc comment should produce a warning
/** This is also a doc comment and is part of the warning
*/
#[allow(non_camel_case_types)]
#[allow(missing_docs)]
#[allow(dead_code)]
fn three_attributes() {}
mod misattributed {
//~v empty_line_after_doc_comments
/// docs for `old_code`
// fn old_code() {}
fn new_code() {}
//~vv empty_line_after_doc_comments
/// Docs
/// for OldA
// struct OldA;
/// Docs
/// for OldB
// struct OldB;
/// Docs
/// for Multiple
#[allow(dead_code)]
struct Multiple;
}
mod block_comments {
//~v empty_line_after_doc_comments
/**
* Meant to be inner doc comment
*/
fn first_in_module() {}
//~v empty_line_after_doc_comments
/**
* Docs for `old_code`
*/
/* fn old_code() {} */
/**
* Docs for `new_code`
*/
fn new_code() {}
//~v empty_line_after_doc_comments
/// Docs for `old_code2`
/* fn old_code2() {} */
/// Docs for `new_code2`
fn new_code2() {}
}
// This should *NOT* produce a warning
#[doc = "
Returns the escaped value of the textual representation of
"]
pub fn function() -> bool {
true
}
// This should *NOT* produce a warning
#[derive(Clone, Copy)]
pub enum FooFighter {
Bar1,
Bar2,
Bar3,
Bar4,
}
/// Should not lint
// some line comment
/// gaps without an empty line
struct LineComment;
/// This should *NOT* produce a warning because the empty line is inside a block comment
/*
*/
pub struct EmptyInBlockComment;
/// This should *NOT* produce a warning
/* test */
pub struct BlockComment;
/// Ignore the empty line inside a cfg_attr'd out attribute
#[cfg_attr(any(), multiline(
foo = 1
bar = 2
))]
fn empty_line_in_cfg_attr() {}
fn main() {}

View file

@ -0,0 +1,144 @@
#![warn(clippy::empty_line_after_outer_attr, clippy::empty_line_after_doc_comments)]
//~vvv empty_line_after_doc_comments
//! Meant to be an
//! inner doc comment
//! for the crate
fn first_in_crate() {}
mod m {
//~vvv empty_line_after_doc_comments
//! Meant to be an
//! inner doc comment
//! for the module
fn first_in_module() {}
}
mod some_mod {
//! This doc comment should *NOT* produce a warning
mod some_inner_mod {
fn some_noop() {}
}
//~v empty_line_after_doc_comments
/// # Indented
///
/// Blank line
fn indented() {}
}
//~v empty_line_after_doc_comments
/// This should produce a warning
fn with_doc_and_newline() {}
// This should *NOT* produce a warning
#[crate_type = "lib"]
/// some comment
fn with_no_newline_and_comment() {}
//~v empty_line_after_doc_comments
/// This doc comment should produce a warning
/** This is also a doc comment and is part of the warning
*/
#[allow(non_camel_case_types)]
#[allow(missing_docs)]
#[allow(dead_code)]
fn three_attributes() {}
mod misattributed {
//~v empty_line_after_doc_comments
// /// docs for `old_code`
// fn old_code() {}
fn new_code() {}
//~vv empty_line_after_doc_comments
// /// Docs
// /// for OldA
// struct OldA;
// /// Docs
// /// for OldB
// struct OldB;
/// Docs
/// for Multiple
#[allow(dead_code)]
struct Multiple;
}
mod block_comments {
//~v empty_line_after_doc_comments
/*!
* Meant to be inner doc comment
*/
fn first_in_module() {}
//~v empty_line_after_doc_comments
/*
* Docs for `old_code`
*/
/* fn old_code() {} */
/**
* Docs for `new_code`
*/
fn new_code() {}
//~v empty_line_after_doc_comments
// /// Docs for `old_code2`
/* fn old_code2() {} */
/// Docs for `new_code2`
fn new_code2() {}
}
// This should *NOT* produce a warning
#[doc = "
Returns the escaped value of the textual representation of
"]
pub fn function() -> bool {
true
}
// This should *NOT* produce a warning
#[derive(Clone, Copy)]
pub enum FooFighter {
Bar1,
Bar2,
Bar3,
Bar4,
}
/// Should not lint
// some line comment
/// gaps without an empty line
struct LineComment;
/// This should *NOT* produce a warning because the empty line is inside a block comment
/*
*/
pub struct EmptyInBlockComment;
/// This should *NOT* produce a warning
/* test */
pub struct BlockComment;
/// Ignore the empty line inside a cfg_attr'd out attribute
#[cfg_attr(any(), multiline(
foo = 1
bar = 2
))]
fn empty_line_in_cfg_attr() {}
fn main() {}

View file

@ -0,0 +1,147 @@
#![warn(clippy::empty_line_after_outer_attr, clippy::empty_line_after_doc_comments)]
//~vvv empty_line_after_doc_comments
/// Meant to be an
/// inner doc comment
/// for the crate
fn first_in_crate() {}
mod m {
//~vvv empty_line_after_doc_comments
/// Meant to be an
/// inner doc comment
/// for the module
fn first_in_module() {}
}
mod some_mod {
//! This doc comment should *NOT* produce a warning
mod some_inner_mod {
fn some_noop() {}
}
//~v empty_line_after_doc_comments
/// # Indented
/// Blank line
fn indented() {}
}
//~v empty_line_after_doc_comments
/// This should produce a warning
fn with_doc_and_newline() {}
// This should *NOT* produce a warning
#[crate_type = "lib"]
/// some comment
fn with_no_newline_and_comment() {}
//~v empty_line_after_doc_comments
/// This doc comment should produce a warning
/** This is also a doc comment and is part of the warning
*/
#[allow(non_camel_case_types)]
#[allow(missing_docs)]
#[allow(dead_code)]
fn three_attributes() {}
mod misattributed {
//~v empty_line_after_doc_comments
/// docs for `old_code`
// fn old_code() {}
fn new_code() {}
//~vv empty_line_after_doc_comments
/// Docs
/// for OldA
// struct OldA;
/// Docs
/// for OldB
// struct OldB;
/// Docs
/// for Multiple
#[allow(dead_code)]
struct Multiple;
}
mod block_comments {
//~v empty_line_after_doc_comments
/**
* Meant to be inner doc comment
*/
fn first_in_module() {}
//~v empty_line_after_doc_comments
/**
* Docs for `old_code`
*/
/* fn old_code() {} */
/**
* Docs for `new_code`
*/
fn new_code() {}
//~v empty_line_after_doc_comments
/// Docs for `old_code2`
/* fn old_code2() {} */
/// Docs for `new_code2`
fn new_code2() {}
}
// This should *NOT* produce a warning
#[doc = "
Returns the escaped value of the textual representation of
"]
pub fn function() -> bool {
true
}
// This should *NOT* produce a warning
#[derive(Clone, Copy)]
pub enum FooFighter {
Bar1,
Bar2,
Bar3,
Bar4,
}
/// Should not lint
// some line comment
/// gaps without an empty line
struct LineComment;
/// This should *NOT* produce a warning because the empty line is inside a block comment
/*
*/
pub struct EmptyInBlockComment;
/// This should *NOT* produce a warning
/* test */
pub struct BlockComment;
/// Ignore the empty line inside a cfg_attr'd out attribute
#[cfg_attr(any(), multiline(
foo = 1
bar = 2
))]
fn empty_line_in_cfg_attr() {}
fn main() {}

View file

@ -0,0 +1,176 @@
error: empty line after doc comment
--> tests/ui/empty_line_after/doc_comments.rs:6:1
|
LL | / /// for the crate
LL | |
| |_
LL | fn first_in_crate() {}
| ------------------- the comment documents this function
|
= note: `-D clippy::empty-line-after-doc-comments` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::empty_line_after_doc_comments)]`
= help: if the empty line is unintentional remove it
help: if the comment should document the crate use an inner doc comment
|
LL ~ //! Meant to be an
LL ~ //! inner doc comment
LL ~ //! for the crate
|
error: empty line after doc comment
--> tests/ui/empty_line_after/doc_comments.rs:14:5
|
LL | / /// for the module
LL | |
| |_
LL | fn first_in_module() {}
| -------------------- the comment documents this function
|
= help: if the empty line is unintentional remove it
help: if the comment should document the parent module use an inner doc comment
|
LL ~ //! Meant to be an
LL ~ //! inner doc comment
LL ~ //! for the module
|
error: empty line after doc comment
--> tests/ui/empty_line_after/doc_comments.rs:27:5
|
LL | / /// # Indented
LL | |
| |_
LL | /// Blank line
LL | fn indented() {}
| ------------- the comment documents this function
|
= help: if the empty line is unintentional remove it
help: if the documentation should include the empty line include it in the comment
|
LL | ///
|
error: empty line after doc comment
--> tests/ui/empty_line_after/doc_comments.rs:34:1
|
LL | / /// This should produce a warning
LL | |
| |_
LL | fn with_doc_and_newline() {}
| ------------------------- the comment documents this function
|
= help: if the empty line is unintentional remove it
error: empty lines after doc comment
--> tests/ui/empty_line_after/doc_comments.rs:44:1
|
LL | / /// This doc comment should produce a warning
LL | |
LL | | /** This is also a doc comment and is part of the warning
LL | | */
LL | |
| |_
...
LL | fn three_attributes() {}
| --------------------- the comment documents this function
|
= help: if the empty lines are unintentional remove them
error: empty line after doc comment
--> tests/ui/empty_line_after/doc_comments.rs:56:5
|
LL | / /// docs for `old_code`
LL | | // fn old_code() {}
LL | |
| |_
LL | fn new_code() {}
| ------------- the comment documents this function
|
= help: if the empty line is unintentional remove it
help: if the doc comment should not document `new_code` comment it out
|
LL | // /// docs for `old_code`
| ++
error: empty lines after doc comment
--> tests/ui/empty_line_after/doc_comments.rs:63:5
|
LL | / /// for OldA
LL | | // struct OldA;
LL | |
LL | | /// Docs
LL | | /// for OldB
LL | | // struct OldB;
LL | |
| |_
...
LL | struct Multiple;
| --------------- the comment documents this struct
|
= help: if the empty lines are unintentional remove them
help: if the doc comment should not document `Multiple` comment it out
|
LL ~ // /// Docs
LL ~ // /// for OldA
LL | // struct OldA;
LL |
LL ~ // /// Docs
LL ~ // /// for OldB
|
error: empty line after doc comment
--> tests/ui/empty_line_after/doc_comments.rs:78:5
|
LL | / /**
LL | | * Meant to be inner doc comment
LL | | */
LL | |
| |_
LL | fn first_in_module() {}
| -------------------- the comment documents this function
|
= help: if the empty line is unintentional remove it
help: if the comment should document the parent module use an inner doc comment
|
LL | /*!
| ~
error: empty line after doc comment
--> tests/ui/empty_line_after/doc_comments.rs:85:5
|
LL | / /**
LL | | * Docs for `old_code`
LL | | */
LL | | /* fn old_code() {} */
LL | |
| |_
...
LL | fn new_code() {}
| ------------- the comment documents this function
|
= help: if the empty line is unintentional remove it
help: if the doc comment should not document `new_code` comment it out
|
LL - /**
LL + /*
|
error: empty line after doc comment
--> tests/ui/empty_line_after/doc_comments.rs:96:5
|
LL | / /// Docs for `old_code2`
LL | | /* fn old_code2() {} */
LL | |
| |_
LL | /// Docs for `new_code2`
LL | fn new_code2() {}
| -------------- the comment documents this function
|
= help: if the empty line is unintentional remove it
help: if the doc comment should not document `new_code2` comment it out
|
LL | // /// Docs for `old_code2`
| ++
error: aborting due to 10 previous errors

View file

@ -0,0 +1,103 @@
//@aux-build:../auxiliary/proc_macro_attr.rs
#![warn(clippy::empty_line_after_outer_attr, clippy::empty_line_after_doc_comments)]
//~v empty_line_after_outer_attr
#[crate_type = "lib"]
fn first_in_crate() {}
#[macro_use]
extern crate proc_macro_attr;
//~v empty_line_after_outer_attr
#[inline]
/// some comment
fn with_one_newline_and_comment() {}
#[inline]
/// some comment
fn with_no_newline_and_comment() {}
//~v empty_line_after_outer_attr
#[inline]
fn with_one_newline() {}
#[rustfmt::skip]
mod two_lines {
//~v empty_line_after_outer_attr
#[crate_type = "lib"]
fn with_two_newlines() {}
}
//~v empty_line_after_outer_attr
#[doc = "doc attributes should be considered attributes"]
enum Baz {
One,
Two,
}
//~v empty_line_after_outer_attr
#[repr(C)]
struct Foo {
one: isize,
two: isize,
}
//~v empty_line_after_outer_attr
#[allow(dead_code)]
mod foo {}
//~v empty_line_after_outer_attr
#[inline]
// Still lint cases where the empty line does not immediately follow the attribute
fn comment_before_empty_line() {}
#[doc = "
Returns the escaped value of the textual representation of
"]
pub fn function() -> bool {
true
}
#[derive(Clone, Copy)]
pub enum FooFighter {
Bar1,
Bar2,
Bar3,
Bar4,
}
#[crate_type = "lib"]
/*
*/
pub struct EmptyLineInBlockComment;
#[crate_type = "lib"]
/* test */
pub struct BlockComment;
// See https://github.com/rust-lang/rust-clippy/issues/5567
#[rustfmt::skip]
#[fake_async_trait]
pub trait Bazz {
fn foo() -> Vec<u8> {
let _i = "";
vec![]
}
}
#[derive(Clone, Copy)]
#[dummy(string = "first line
second line
")]
pub struct Args;
fn main() {}

View file

@ -0,0 +1,106 @@
//@aux-build:../auxiliary/proc_macro_attr.rs
#![warn(clippy::empty_line_after_outer_attr, clippy::empty_line_after_doc_comments)]
//~v empty_line_after_outer_attr
#![crate_type = "lib"]
fn first_in_crate() {}
#[macro_use]
extern crate proc_macro_attr;
//~v empty_line_after_outer_attr
#[inline]
/// some comment
fn with_one_newline_and_comment() {}
#[inline]
/// some comment
fn with_no_newline_and_comment() {}
//~v empty_line_after_outer_attr
#[inline]
fn with_one_newline() {}
#[rustfmt::skip]
mod two_lines {
//~v empty_line_after_outer_attr
#![crate_type = "lib"]
fn with_two_newlines() {}
}
//~v empty_line_after_outer_attr
#[doc = "doc attributes should be considered attributes"]
enum Baz {
One,
Two,
}
//~v empty_line_after_outer_attr
#[repr(C)]
struct Foo {
one: isize,
two: isize,
}
//~v empty_line_after_outer_attr
#[allow(dead_code)]
mod foo {}
//~v empty_line_after_outer_attr
#[inline]
// Still lint cases where the empty line does not immediately follow the attribute
fn comment_before_empty_line() {}
#[doc = "
Returns the escaped value of the textual representation of
"]
pub fn function() -> bool {
true
}
#[derive(Clone, Copy)]
pub enum FooFighter {
Bar1,
Bar2,
Bar3,
Bar4,
}
#[crate_type = "lib"]
/*
*/
pub struct EmptyLineInBlockComment;
#[crate_type = "lib"]
/* test */
pub struct BlockComment;
// See https://github.com/rust-lang/rust-clippy/issues/5567
#[rustfmt::skip]
#[fake_async_trait]
pub trait Bazz {
fn foo() -> Vec<u8> {
let _i = "";
vec![]
}
}
#[derive(Clone, Copy)]
#[dummy(string = "first line
second line
")]
pub struct Args;
fn main() {}

View file

@ -0,0 +1,112 @@
//@aux-build:../auxiliary/proc_macro_attr.rs
#![warn(clippy::empty_line_after_outer_attr, clippy::empty_line_after_doc_comments)]
//~v empty_line_after_outer_attr
#[crate_type = "lib"]
fn first_in_crate() {}
#[macro_use]
extern crate proc_macro_attr;
//~v empty_line_after_outer_attr
#[inline]
/// some comment
fn with_one_newline_and_comment() {}
#[inline]
/// some comment
fn with_no_newline_and_comment() {}
//~v empty_line_after_outer_attr
#[inline]
fn with_one_newline() {}
#[rustfmt::skip]
mod two_lines {
//~v empty_line_after_outer_attr
#[crate_type = "lib"]
fn with_two_newlines() {}
}
//~v empty_line_after_outer_attr
#[doc = "doc attributes should be considered attributes"]
enum Baz {
One,
Two,
}
//~v empty_line_after_outer_attr
#[repr(C)]
struct Foo {
one: isize,
two: isize,
}
//~v empty_line_after_outer_attr
#[allow(dead_code)]
mod foo {}
//~v empty_line_after_outer_attr
#[inline]
// Still lint cases where the empty line does not immediately follow the attribute
fn comment_before_empty_line() {}
#[doc = "
Returns the escaped value of the textual representation of
"]
pub fn function() -> bool {
true
}
#[derive(Clone, Copy)]
pub enum FooFighter {
Bar1,
Bar2,
Bar3,
Bar4,
}
#[crate_type = "lib"]
/*
*/
pub struct EmptyLineInBlockComment;
#[crate_type = "lib"]
/* test */
pub struct BlockComment;
// See https://github.com/rust-lang/rust-clippy/issues/5567
#[rustfmt::skip]
#[fake_async_trait]
pub trait Bazz {
fn foo() -> Vec<u8> {
let _i = "";
vec![]
}
}
#[derive(Clone, Copy)]
#[dummy(string = "first line
second line
")]
pub struct Args;
fn main() {}

View file

@ -0,0 +1,103 @@
error: empty line after outer attribute
--> tests/ui/empty_line_after/outer_attribute.rs:5:1
|
LL | / #[crate_type = "lib"]
LL | |
| |_
LL | fn first_in_crate() {}
| ------------------- the attribute applies to this function
|
= note: `-D clippy::empty-line-after-outer-attr` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::empty_line_after_outer_attr)]`
= help: if the empty line is unintentional remove it
help: if the attribute should apply to the crate use an inner attribute
|
LL | #![crate_type = "lib"]
| +
error: empty line after outer attribute
--> tests/ui/empty_line_after/outer_attribute.rs:13:1
|
LL | / #[inline]
LL | |
| |_
LL | /// some comment
LL | fn with_one_newline_and_comment() {}
| --------------------------------- the attribute applies to this function
|
= help: if the empty line is unintentional remove it
error: empty line after outer attribute
--> tests/ui/empty_line_after/outer_attribute.rs:23:1
|
LL | / #[inline]
LL | |
| |_
LL | fn with_one_newline() {}
| --------------------- the attribute applies to this function
|
= help: if the empty line is unintentional remove it
error: empty lines after outer attribute
--> tests/ui/empty_line_after/outer_attribute.rs:30:5
|
LL | / #[crate_type = "lib"]
LL | |
LL | |
| |_
LL | fn with_two_newlines() {}
| ---------------------- the attribute applies to this function
|
= help: if the empty lines are unintentional remove them
help: if the attribute should apply to the parent module use an inner attribute
|
LL | #![crate_type = "lib"]
| +
error: empty line after outer attribute
--> tests/ui/empty_line_after/outer_attribute.rs:37:1
|
LL | / #[doc = "doc attributes should be considered attributes"]
LL | |
| |_
LL | enum Baz {
| -------- the attribute applies to this enum
|
= help: if the empty line is unintentional remove it
error: empty line after outer attribute
--> tests/ui/empty_line_after/outer_attribute.rs:45:1
|
LL | / #[repr(C)]
LL | |
| |_
LL | struct Foo {
| ---------- the attribute applies to this struct
|
= help: if the empty line is unintentional remove it
error: empty line after outer attribute
--> tests/ui/empty_line_after/outer_attribute.rs:53:1
|
LL | / #[allow(dead_code)]
LL | |
| |_
LL | mod foo {}
| ------- the attribute applies to this module
|
= help: if the empty line is unintentional remove it
error: empty line after outer attribute
--> tests/ui/empty_line_after/outer_attribute.rs:58:1
|
LL | / #[inline]
LL | | // Still lint cases where the empty line does not immediately follow the attribute
LL | |
| |_
LL | fn comment_before_empty_line() {}
| ------------------------------ the attribute applies to this function
|
= help: if the empty line is unintentional remove it
error: aborting due to 8 previous errors

View file

@ -1,132 +0,0 @@
//@aux-build:proc_macro_attr.rs
#![warn(clippy::empty_line_after_doc_comments)]
#![allow(clippy::assertions_on_constants, clippy::duplicated_attributes)]
#![feature(custom_inner_attributes)]
#![rustfmt::skip]
#[macro_use]
extern crate proc_macro_attr;
mod some_mod {
//! This doc comment should *NOT* produce a warning
mod some_inner_mod {
fn some_noop() {}
}
}
/// This should produce a warning
fn with_doc_and_newline() { assert!(true)}
// This should *NOT* produce a warning
#[crate_type = "lib"]
/// some comment
fn with_one_newline_and_comment() { assert!(true) }
// This should *NOT* produce a warning
#[crate_type = "lib"]
/// some comment
fn with_no_newline_and_comment() { assert!(true) }
// This should *NOT* produce a warning
#[crate_type = "lib"]
fn with_one_newline() { assert!(true) }
// This should *NOT* produce a warning
#[crate_type = "lib"]
fn with_two_newlines() { assert!(true) }
// This should *NOT* produce a warning
#[crate_type = "lib"]
enum Baz {
One,
Two
}
// This should *NOT* produce a warning
#[crate_type = "lib"]
struct Foo {
one: isize,
two: isize
}
// This should *NOT* produce a warning
#[crate_type = "lib"]
mod foo {
}
/// This doc comment should produce a warning
/** This is also a doc comment and should produce a warning
*/
// This should *NOT* produce a warning
#[allow(non_camel_case_types)]
#[allow(missing_docs)]
#[allow(missing_docs)]
fn three_attributes() { assert!(true) }
// This should *NOT* produce a warning
#[doc = "
Returns the escaped value of the textual representation of
"]
pub fn function() -> bool {
true
}
// This should *NOT* produce a warning
#[derive(Clone, Copy)]
pub enum FooFighter {
Bar1,
Bar2,
Bar3,
Bar4
}
// This should *NOT* produce a warning because the empty line is inside a block comment
#[crate_type = "lib"]
/*
*/
pub struct S;
// This should *NOT* produce a warning
#[crate_type = "lib"]
/* test */
pub struct T;
// This should *NOT* produce a warning
// See https://github.com/rust-lang/rust-clippy/issues/5567
#[fake_async_trait]
pub trait Bazz {
fn foo() -> Vec<u8> {
let _i = "";
vec![]
}
}
#[derive(Clone, Copy)]
#[dummy(string = "first line
second line
")]
pub struct Args;
fn main() {}

View file

@ -1,37 +0,0 @@
error: found an empty line after a doc comment. Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?
--> tests/ui/empty_line_after_doc_comments.rs:18:1
|
LL | / /// This should produce a warning
LL | |
LL | | fn with_doc_and_newline() { assert!(true)}
| |_
|
= note: `-D clippy::empty-line-after-doc-comments` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::empty_line_after_doc_comments)]`
error: found an empty line after a doc comment. Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?
--> tests/ui/empty_line_after_doc_comments.rs:68:1
|
LL | / /// This doc comment should produce a warning
LL | |
LL | | /** This is also a doc comment and should produce a warning
LL | | */
... |
LL | | #[allow(missing_docs)]
LL | | fn three_attributes() { assert!(true) }
| |_
error: found an empty line after a doc comment. Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?
--> tests/ui/empty_line_after_doc_comments.rs:70:1
|
LL | / /** This is also a doc comment and should produce a warning
LL | | */
LL | |
LL | | // This should *NOT* produce a warning
... |
LL | | #[allow(missing_docs)]
LL | | fn three_attributes() { assert!(true) }
| |_
error: aborting due to 3 previous errors

View file

@ -1,120 +0,0 @@
//@aux-build:proc_macro_attr.rs
#![warn(clippy::empty_line_after_outer_attr)]
#![allow(clippy::assertions_on_constants, clippy::duplicated_attributes)]
#![feature(custom_inner_attributes)]
#![rustfmt::skip]
#[macro_use]
extern crate proc_macro_attr;
// This should produce a warning
#[crate_type = "lib"]
/// some comment
fn with_one_newline_and_comment() { assert!(true) }
// This should not produce a warning
#[crate_type = "lib"]
/// some comment
fn with_no_newline_and_comment() { assert!(true) }
// This should produce a warning
#[crate_type = "lib"]
fn with_one_newline() { assert!(true) }
// This should produce a warning, too
#[crate_type = "lib"]
fn with_two_newlines() { assert!(true) }
// This should produce a warning
#[crate_type = "lib"]
enum Baz {
One,
Two
}
// This should produce a warning
#[crate_type = "lib"]
struct Foo {
one: isize,
two: isize
}
// This should produce a warning
#[crate_type = "lib"]
mod foo {
}
/// This doc comment should not produce a warning
/** This is also a doc comment and should not produce a warning
*/
// This should not produce a warning
#[allow(non_camel_case_types)]
#[allow(missing_docs)]
#[allow(missing_docs)]
fn three_attributes() { assert!(true) }
// This should not produce a warning
#[doc = "
Returns the escaped value of the textual representation of
"]
pub fn function() -> bool {
true
}
// This should not produce a warning
#[derive(Clone, Copy)]
pub enum FooFighter {
Bar1,
Bar2,
Bar3,
Bar4
}
// This should not produce a warning because the empty line is inside a block comment
#[crate_type = "lib"]
/*
*/
pub struct S;
// This should not produce a warning
#[crate_type = "lib"]
/* test */
pub struct T;
// This should not produce a warning
// See https://github.com/rust-lang/rust-clippy/issues/5567
#[fake_async_trait]
pub trait Bazz {
fn foo() -> Vec<u8> {
let _i = "";
vec![]
}
}
#[derive(Clone, Copy)]
#[dummy(string = "first line
second line
")]
pub struct Args;
fn main() {}

View file

@ -1,54 +0,0 @@
error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute?
--> tests/ui/empty_line_after_outer_attribute.rs:11:1
|
LL | / #[crate_type = "lib"]
LL | |
LL | | /// some comment
LL | | fn with_one_newline_and_comment() { assert!(true) }
| |_
|
= note: `-D clippy::empty-line-after-outer-attr` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::empty_line_after_outer_attr)]`
error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute?
--> tests/ui/empty_line_after_outer_attribute.rs:23:1
|
LL | / #[crate_type = "lib"]
LL | |
LL | | fn with_one_newline() { assert!(true) }
| |_
error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute?
--> tests/ui/empty_line_after_outer_attribute.rs:28:1
|
LL | / #[crate_type = "lib"]
... |
LL | | fn with_two_newlines() { assert!(true) }
| |_
error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute?
--> tests/ui/empty_line_after_outer_attribute.rs:35:1
|
LL | / #[crate_type = "lib"]
LL | |
LL | | enum Baz {
| |_
error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute?
--> tests/ui/empty_line_after_outer_attribute.rs:43:1
|
LL | / #[crate_type = "lib"]
LL | |
LL | | struct Foo {
| |_
error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute?
--> tests/ui/empty_line_after_outer_attribute.rs:51:1
|
LL | / #[crate_type = "lib"]
LL | |
LL | | mod foo {
| |_
error: aborting due to 6 previous errors

View file

@ -1,4 +1,4 @@
#[warn(clippy::exit)]
#![warn(clippy::exit)]
fn not_main() {
if true {

View file

@ -1,4 +1,4 @@
#[warn(clippy::exit)]
#![warn(clippy::exit)]
fn also_not_main() {
std::process::exit(3);

View file

@ -1,4 +1,4 @@
#[warn(clippy::exit)]
#![warn(clippy::exit)]
fn main() {
if true {

Some files were not shown because too many files have changed in this diff Show more