From 9ee211af9fb04802a23be3557c606d833e288d82 Mon Sep 17 00:00:00 2001
From: "Samuel E. Moelius III" <sam@moeli.us>
Date: Wed, 25 May 2022 19:22:44 -0400
Subject: [PATCH] Fix `empty_line_after_outer_attribute` false positive

---
 Cargo.toml                                    |  1 +
 clippy_lints/src/attrs.rs                     | 23 +++++++++++++++----
 tests/compile-test.rs                         |  8 +++++--
 tests/ui/empty_line_after_outer_attribute.rs  | 10 ++++++++
 .../empty_line_after_outer_attribute.stderr   | 12 +++++-----
 5 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 3c8b758d53d..d23d681df00 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,6 +40,7 @@ filetime = "0.2"
 rustc-workspace-hack = "1.0"
 
 # UI test dependencies
+clap = { version = "3.1", features = ["derive"] }
 clippy_utils = { path = "clippy_utils" }
 derive-new = "0.5"
 if_chain = "1.0"
diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs
index 3de91f3d24a..7105ce4b292 100644
--- a/clippy_lints/src/attrs.rs
+++ b/clippy_lints/src/attrs.rs
@@ -585,15 +585,21 @@ impl EarlyLintPass for EarlyAttributes {
 }
 
 fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
-    for attr in &item.attrs {
+    let mut iter = item.attrs.iter().peekable();
+    while let Some(attr) = iter.next() {
         if matches!(attr.kind, AttrKind::Normal(..))
             && 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_item = Span::new(attr.span.hi(), 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) = snippet_opt(cx, end_of_attr_to_item) {
+            if let Some(snippet) = snippet_opt(cx, end_of_attr_to_next_attr_or_item) {
                 let lines = snippet.split('\n').collect::<Vec<_>>();
                 let lines = without_block_comments(lines);
 
@@ -623,8 +629,15 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: Opti
         if feature_item.has_name(sym::rustfmt);
         // check for `rustfmt_skip` and `rustfmt::skip`
         if let Some(skip_item) = &items[1].meta_item();
-        if skip_item.has_name(sym!(rustfmt_skip)) ||
-            skip_item.path.segments.last().expect("empty path in attribute").ident.name == sym::skip;
+        if skip_item.has_name(sym!(rustfmt_skip))
+            || skip_item
+                .path
+                .segments
+                .last()
+                .expect("empty path in attribute")
+                .ident
+                .name
+                == sym::skip;
         // Only lint outer attributes, because custom inner attributes are unstable
         // Tracking issue: https://github.com/rust-lang/rust/issues/54726
         if attr.style == AttrStyle::Outer;
diff --git a/tests/compile-test.rs b/tests/compile-test.rs
index c9710e3db8e..18099c45fee 100644
--- a/tests/compile-test.rs
+++ b/tests/compile-test.rs
@@ -22,6 +22,7 @@ const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal");
 
 /// All crates used in UI tests are listed here
 static TEST_DEPENDENCIES: &[&str] = &[
+    "clap",
     "clippy_utils",
     "derive_new",
     "futures",
@@ -40,6 +41,8 @@ static TEST_DEPENDENCIES: &[&str] = &[
 // Test dependencies may need an `extern crate` here to ensure that they show up
 // in the depinfo file (otherwise cargo thinks they are unused)
 #[allow(unused_extern_crates)]
+extern crate clap;
+#[allow(unused_extern_crates)]
 extern crate clippy_utils;
 #[allow(unused_extern_crates)]
 extern crate derive_new;
@@ -109,8 +112,9 @@ static EXTERN_FLAGS: SyncLazy<String> = SyncLazy::new(|| {
         not_found.is_empty(),
         "dependencies not found in depinfo: {:?}\n\
         help: Make sure the `-Z binary-dep-depinfo` rust flag is enabled\n\
-        help: Try adding to dev-dependencies in Cargo.toml",
-        not_found
+        help: Try adding to dev-dependencies in Cargo.toml\n\
+        help: Be sure to also add `extern crate ...;` to tests/compile-test.rs",
+        not_found,
     );
     crates
         .into_iter()
diff --git a/tests/ui/empty_line_after_outer_attribute.rs b/tests/ui/empty_line_after_outer_attribute.rs
index 3e92bca986a..d15c84d7438 100644
--- a/tests/ui/empty_line_after_outer_attribute.rs
+++ b/tests/ui/empty_line_after_outer_attribute.rs
@@ -4,6 +4,9 @@
 #![feature(custom_inner_attributes)]
 #![rustfmt::skip]
 
+#[macro_use]
+extern crate clap;
+
 #[macro_use]
 extern crate proc_macro_attr;
 
@@ -110,4 +113,11 @@ pub trait Bazz {
     }
 }
 
+#[derive(clap::Parser)]
+#[clap(after_help = "This ia a help message.
+
+You're welcome.
+")]
+pub struct Args;
+
 fn main() {}
diff --git a/tests/ui/empty_line_after_outer_attribute.stderr b/tests/ui/empty_line_after_outer_attribute.stderr
index 594fca44a32..acc3edef9b9 100644
--- a/tests/ui/empty_line_after_outer_attribute.stderr
+++ b/tests/ui/empty_line_after_outer_attribute.stderr
@@ -1,5 +1,5 @@
 error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute?
-  --> $DIR/empty_line_after_outer_attribute.rs:11:1
+  --> $DIR/empty_line_after_outer_attribute.rs:14:1
    |
 LL | / #[crate_type = "lib"]
 LL | |
@@ -10,7 +10,7 @@ LL | | fn with_one_newline_and_comment() { assert!(true) }
    = note: `-D clippy::empty-line-after-outer-attr` implied by `-D warnings`
 
 error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute?
-  --> $DIR/empty_line_after_outer_attribute.rs:23:1
+  --> $DIR/empty_line_after_outer_attribute.rs:26:1
    |
 LL | / #[crate_type = "lib"]
 LL | |
@@ -18,7 +18,7 @@ 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?
-  --> $DIR/empty_line_after_outer_attribute.rs:28:1
+  --> $DIR/empty_line_after_outer_attribute.rs:31:1
    |
 LL | / #[crate_type = "lib"]
 LL | |
@@ -27,7 +27,7 @@ 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?
-  --> $DIR/empty_line_after_outer_attribute.rs:35:1
+  --> $DIR/empty_line_after_outer_attribute.rs:38:1
    |
 LL | / #[crate_type = "lib"]
 LL | |
@@ -35,7 +35,7 @@ LL | | enum Baz {
    | |_
 
 error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute?
-  --> $DIR/empty_line_after_outer_attribute.rs:43:1
+  --> $DIR/empty_line_after_outer_attribute.rs:46:1
    |
 LL | / #[crate_type = "lib"]
 LL | |
@@ -43,7 +43,7 @@ LL | | struct Foo {
    | |_
 
 error: found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute?
-  --> $DIR/empty_line_after_outer_attribute.rs:51:1
+  --> $DIR/empty_line_after_outer_attribute.rs:54:1
    |
 LL | / #[crate_type = "lib"]
 LL | |