about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/as_conversions.rs1
-rw-r--r--clippy_lints/src/assertions_on_constants.rs3
-rw-r--r--clippy_lints/src/atomic_ordering.rs3
-rw-r--r--clippy_lints/src/cognitive_complexity.rs1
-rw-r--r--clippy_lints/src/comparison_chain.rs1
-rw-r--r--clippy_lints/src/copies.rs6
-rw-r--r--clippy_lints/src/copy_iterator.rs2
-rw-r--r--clippy_lints/src/dbg_macro.rs1
-rw-r--r--clippy_lints/src/derive.rs9
-rw-r--r--clippy_lints/src/drop_forget_ref.rs4
-rw-r--r--clippy_lints/src/else_if_without_else.rs1
-rw-r--r--clippy_lints/src/empty_enum.rs18
-rw-r--r--clippy_lints/src/enum_variants.rs1
-rw-r--r--clippy_lints/src/eta_reduction.rs20
-rw-r--r--clippy_lints/src/eval_order_dependence.rs2
-rw-r--r--clippy_lints/src/excessive_bools.rs2
-rw-r--r--clippy_lints/src/formatting.rs9
-rw-r--r--clippy_lints/src/functions.rs1
-rw-r--r--clippy_lints/src/identity_conversion.rs53
-rw-r--r--clippy_lints/src/if_not_else.rs2
-rw-r--r--clippy_lints/src/indexing_slicing.rs3
-rw-r--r--clippy_lints/src/inherent_to_string.rs2
-rw-r--r--clippy_lints/src/int_plus_one.rs15
-rw-r--r--clippy_lints/src/integer_division.rs1
-rw-r--r--clippy_lints/src/large_stack_arrays.rs1
-rw-r--r--clippy_lints/src/let_underscore.rs3
-rw-r--r--clippy_lints/src/lib.rs3
-rw-r--r--clippy_lints/src/loops.rs74
-rw-r--r--clippy_lints/src/main_recursion.rs1
-rw-r--r--clippy_lints/src/matches.rs6
-rw-r--r--clippy_lints/src/mem_replace.rs2
-rw-r--r--clippy_lints/src/methods/mod.rs25
-rw-r--r--clippy_lints/src/misc_early.rs2
-rw-r--r--clippy_lints/src/needless_continue.rs1
-rw-r--r--clippy_lints/src/option_env_unwrap.rs1
-rw-r--r--clippy_lints/src/ptr.rs17
-rw-r--r--clippy_lints/src/regex.rs4
-rw-r--r--clippy_lints/src/returns.rs87
-rw-r--r--clippy_lints/src/trait_bounds.rs1
-rw-r--r--clippy_lints/src/transmute.rs26
-rw-r--r--clippy_lints/src/types.rs4
-rw-r--r--clippy_lints/src/unnamed_address.rs2
-rw-r--r--clippy_lints/src/unused_self.rs1
-rw-r--r--clippy_lints/src/utils/diagnostics.rs24
-rw-r--r--clippy_lints/src/utils/internal_lints.rs259
-rw-r--r--clippy_lints/src/verbose_file_reads.rs2
-rw-r--r--clippy_lints/src/zero_div_zero.rs1
-rw-r--r--doc/adding_lints.md2
-rw-r--r--tests/ui/collapsible_span_lint_calls.fixed91
-rw-r--r--tests/ui/collapsible_span_lint_calls.rs101
-rw-r--r--tests/ui/collapsible_span_lint_calls.stderr49
-rw-r--r--tests/ui/empty_enum.stderr6
52 files changed, 758 insertions, 199 deletions
diff --git a/clippy_lints/src/as_conversions.rs b/clippy_lints/src/as_conversions.rs
index 4d8bbcd3102..0c8efd75514 100644
--- a/clippy_lints/src/as_conversions.rs
+++ b/clippy_lints/src/as_conversions.rs
@@ -50,6 +50,7 @@ impl EarlyLintPass for AsConversions {
                 AS_CONVERSIONS,
                 expr.span,
                 "using a potentially dangerous silent `as` conversion",
+                None,
                 "consider using a safe wrapper for this conversion",
             );
         }
diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs
index e2fe5f2400f..f8a8fdcd3aa 100644
--- a/clippy_lints/src/assertions_on_constants.rs
+++ b/clippy_lints/src/assertions_on_constants.rs
@@ -41,6 +41,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AssertionsOnConstants {
                 } else {
                     "`assert!(true)` will be optimized out by the compiler"
                 },
+                None,
                 "remove it",
             );
         };
@@ -50,6 +51,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AssertionsOnConstants {
                 ASSERTIONS_ON_CONSTANTS,
                 e.span,
                 "`assert!(false)` should probably be replaced",
+                None,
                 "use `panic!()` or `unreachable!()`",
             );
         };
@@ -59,6 +61,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AssertionsOnConstants {
                 ASSERTIONS_ON_CONSTANTS,
                 e.span,
                 &format!("`assert!(false, {})` should probably be replaced", panic_message),
+                None,
                 &format!("use `panic!({})` or `unreachable!({})`", panic_message, panic_message),
             )
         };
diff --git a/clippy_lints/src/atomic_ordering.rs b/clippy_lints/src/atomic_ordering.rs
index d9ff1fe0a1d..73b4cef4725 100644
--- a/clippy_lints/src/atomic_ordering.rs
+++ b/clippy_lints/src/atomic_ordering.rs
@@ -85,6 +85,7 @@ fn check_atomic_load_store(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
                     INVALID_ATOMIC_ORDERING,
                     ordering_arg.span,
                     "atomic loads cannot have `Release` and `AcqRel` ordering",
+                    None,
                     "consider using ordering modes `Acquire`, `SeqCst` or `Relaxed`"
                 );
             } else if method == "store" &&
@@ -94,6 +95,7 @@ fn check_atomic_load_store(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
                     INVALID_ATOMIC_ORDERING,
                     ordering_arg.span,
                     "atomic stores cannot have `Acquire` and `AcqRel` ordering",
+                    None,
                     "consider using ordering modes `Release`, `SeqCst` or `Relaxed`"
                 );
             }
@@ -118,6 +120,7 @@ fn check_memory_fence(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
                 INVALID_ATOMIC_ORDERING,
                 args[0].span,
                 "memory fences cannot have `Relaxed` ordering",
+                None,
                 "consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst`"
             );
         }
diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs
index 93a394b79e5..e842388ac98 100644
--- a/clippy_lints/src/cognitive_complexity.rs
+++ b/clippy_lints/src/cognitive_complexity.rs
@@ -105,6 +105,7 @@ impl CognitiveComplexity {
                     rust_cc,
                     self.limit.limit()
                 ),
+                None,
                 "you could split it up into multiple smaller functions",
             );
         }
diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs
index e0c381ddbfc..96df3ffe3ce 100644
--- a/clippy_lints/src/comparison_chain.rs
+++ b/clippy_lints/src/comparison_chain.rs
@@ -104,6 +104,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ComparisonChain {
             COMPARISON_CHAIN,
             expr.span,
             "`if` chain can be rewritten with `match`",
+            None,
             "Consider rewriting the `if` chain to use `cmp` and `match`.",
         )
     }
diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs
index 1afd401ca68..66722786eab 100644
--- a/clippy_lints/src/copies.rs
+++ b/clippy_lints/src/copies.rs
@@ -183,7 +183,7 @@ fn lint_same_then_else(cx: &LateContext<'_, '_>, blocks: &[&Block<'_>]) {
             IF_SAME_THEN_ELSE,
             j.span,
             "this `if` has identical blocks",
-            i.span,
+            Some(i.span),
             "same as this",
         );
     }
@@ -206,7 +206,7 @@ fn lint_same_cond(cx: &LateContext<'_, '_>, conds: &[&Expr<'_>]) {
             IFS_SAME_COND,
             j.span,
             "this `if` has the same condition as a previous `if`",
-            i.span,
+            Some(i.span),
             "same as this",
         );
     }
@@ -234,7 +234,7 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_, '_>, conds: &[&Expr<'_>]) {
             SAME_FUNCTIONS_IN_IF_CONDITION,
             j.span,
             "this `if` has the same function call as a previous `if`",
-            i.span,
+            Some(i.span),
             "same as this",
         );
     }
diff --git a/clippy_lints/src/copy_iterator.rs b/clippy_lints/src/copy_iterator.rs
index 3e2d5b88e7b..d79aa2ef020 100644
--- a/clippy_lints/src/copy_iterator.rs
+++ b/clippy_lints/src/copy_iterator.rs
@@ -46,7 +46,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CopyIterator {
                     COPY_ITERATOR,
                     item.span,
                     "you are implementing `Iterator` on a `Copy` type",
-                    item.span,
+                    None,
                     "consider implementing `IntoIterator` instead",
                 );
             }
diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs
index f9bf1141a8b..e513dcce64e 100644
--- a/clippy_lints/src/dbg_macro.rs
+++ b/clippy_lints/src/dbg_macro.rs
@@ -48,6 +48,7 @@ impl EarlyLintPass for DbgMacro {
                     DBG_MACRO,
                     mac.span(),
                     "`dbg!` macro is intended as a debugging tool",
+                    None,
                     "ensure to avoid having uses of it in version control",
                 );
             }
diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs
index 7756fdfdd07..f5a358d424f 100644
--- a/clippy_lints/src/derive.rs
+++ b/clippy_lints/src/derive.rs
@@ -1,5 +1,5 @@
 use crate::utils::paths;
-use crate::utils::{is_automatically_derived, is_copy, match_path, span_lint_and_then};
+use crate::utils::{is_automatically_derived, is_copy, match_path, span_lint_and_note, span_lint_and_then};
 use if_chain::if_chain;
 use rustc_hir::{Item, ItemKind, TraitRef};
 use rustc_lint::{LateContext, LateLintPass};
@@ -163,14 +163,13 @@ fn check_copy_clone<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item: &Item<'_>, trait
             _ => (),
         }
 
-        span_lint_and_then(
+        span_lint_and_note(
             cx,
             EXPL_IMPL_CLONE_ON_COPY,
             item.span,
             "you are implementing `Clone` explicitly on a `Copy` type",
-            |diag| {
-                diag.span_note(item.span, "consider deriving `Clone` or removing `Copy`");
-            },
+            Some(item.span),
+            "consider deriving `Clone` or removing `Copy`",
         );
     }
 }
diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs
index 9a60b2f027a..9de9056c140 100644
--- a/clippy_lints/src/drop_forget_ref.rs
+++ b/clippy_lints/src/drop_forget_ref.rs
@@ -135,7 +135,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DropForgetRef {
                                        lint,
                                        expr.span,
                                        &msg,
-                                       arg.span,
+                                       Some(arg.span),
                                        &format!("argument has type `{}`", arg_ty));
                 } else if is_copy(cx, arg_ty) {
                     if match_def_path(cx, def_id, &paths::DROP) {
@@ -151,7 +151,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DropForgetRef {
                                        lint,
                                        expr.span,
                                        &msg,
-                                       arg.span,
+                                       Some(arg.span),
                                        &format!("argument has type {}", arg_ty));
                 }
             }
diff --git a/clippy_lints/src/else_if_without_else.rs b/clippy_lints/src/else_if_without_else.rs
index fb10ca48074..95123e6ff6f 100644
--- a/clippy_lints/src/else_if_without_else.rs
+++ b/clippy_lints/src/else_if_without_else.rs
@@ -61,6 +61,7 @@ impl EarlyLintPass for ElseIfWithoutElse {
                     ELSE_IF_WITHOUT_ELSE,
                     els.span,
                     "`if` expression with an `else if`, but without a final `else`",
+                    None,
                     "add an `else` block here",
                 );
             }
diff --git a/clippy_lints/src/empty_enum.rs b/clippy_lints/src/empty_enum.rs
index 77ae6dbde72..3bfef6f4bed 100644
--- a/clippy_lints/src/empty_enum.rs
+++ b/clippy_lints/src/empty_enum.rs
@@ -1,6 +1,6 @@
 //! lint when there is an enum with no variants
 
-use crate::utils::span_lint_and_then;
+use crate::utils::span_lint_and_help;
 use rustc_hir::{Item, ItemKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -45,13 +45,15 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for EmptyEnum {
             let ty = cx.tcx.type_of(did);
             let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
             if adt.variants.is_empty() {
-                span_lint_and_then(cx, EMPTY_ENUM, item.span, "enum with no variants", |diag| {
-                    diag.span_help(
-                        item.span,
-                        "consider using the uninhabited type `!` (never type) or a wrapper \
-                         around it to introduce a type which can't be instantiated",
-                    );
-                });
+                span_lint_and_help(
+                    cx,
+                    EMPTY_ENUM,
+                    item.span,
+                    "enum with no variants",
+                    None,
+                    "consider using the uninhabited type `!` (never type) or a wrapper \
+                    around it to introduce a type which can't be instantiated",
+                );
             }
         }
     }
diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs
index 882020a7adc..a5871cf0cd4 100644
--- a/clippy_lints/src/enum_variants.rs
+++ b/clippy_lints/src/enum_variants.rs
@@ -206,6 +206,7 @@ fn check_variant(
         lint,
         span,
         &format!("All variants have the same {}fix: `{}`", what, value),
+        None,
         &format!(
             "remove the {}fixes and use full paths to \
              the variants instead of glob imports",
diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs
index 3d27d8d5c8a..e3e1136b676 100644
--- a/clippy_lints/src/eta_reduction.rs
+++ b/clippy_lints/src/eta_reduction.rs
@@ -7,7 +7,8 @@ use rustc_middle::ty::{self, Ty};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 
 use crate::utils::{
-    implements_trait, is_adjusted, iter_input_pats, snippet_opt, span_lint_and_then, type_is_unsafe_function,
+    implements_trait, is_adjusted, iter_input_pats, snippet_opt, span_lint_and_sugg, span_lint_and_then,
+    type_is_unsafe_function,
 };
 
 declare_clippy_lint! {
@@ -131,14 +132,15 @@ fn check_closure(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
             if let Some(name) = get_ufcs_type_name(cx, method_def_id, &args[0]);
 
             then {
-                span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure found", |diag| {
-                    diag.span_suggestion(
-                        expr.span,
-                        "remove closure as shown",
-                        format!("{}::{}", name, path.ident.name),
-                        Applicability::MachineApplicable,
-                    );
-                });
+                span_lint_and_sugg(
+                    cx,
+                    REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
+                    expr.span,
+                    "redundant closure found",
+                    "remove closure as shown",
+                    format!("{}::{}", name, path.ident.name),
+                    Applicability::MachineApplicable,
+                );
             }
         );
     }
diff --git a/clippy_lints/src/eval_order_dependence.rs b/clippy_lints/src/eval_order_dependence.rs
index 48b761260a5..5206266ccf2 100644
--- a/clippy_lints/src/eval_order_dependence.rs
+++ b/clippy_lints/src/eval_order_dependence.rs
@@ -310,7 +310,7 @@ impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> {
                             EVAL_ORDER_DEPENDENCE,
                             expr.span,
                             "unsequenced read of a variable",
-                            self.write_expr.span,
+                            Some(self.write_expr.span),
                             "whether read occurs before this write depends on evaluation order"
                         );
                     }
diff --git a/clippy_lints/src/excessive_bools.rs b/clippy_lints/src/excessive_bools.rs
index ddbc3c377a2..82ca4baacb7 100644
--- a/clippy_lints/src/excessive_bools.rs
+++ b/clippy_lints/src/excessive_bools.rs
@@ -114,6 +114,7 @@ impl ExcessiveBools {
                 FN_PARAMS_EXCESSIVE_BOOLS,
                 span,
                 &format!("more than {} bools in function parameters", self.max_fn_params_bools),
+                None,
                 "consider refactoring bools into two-variant enums",
             );
         }
@@ -153,6 +154,7 @@ impl EarlyLintPass for ExcessiveBools {
                         STRUCT_EXCESSIVE_BOOLS,
                         item.span,
                         &format!("more than {} bools in a struct", self.max_struct_bools),
+                        None,
                         "consider using a state machine or refactoring bools into two-variant enums",
                     );
                 }
diff --git a/clippy_lints/src/formatting.rs b/clippy_lints/src/formatting.rs
index 8f5f82b0a2c..eb4b7a826f2 100644
--- a/clippy_lints/src/formatting.rs
+++ b/clippy_lints/src/formatting.rs
@@ -149,7 +149,7 @@ fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
                                  really are doing `.. = ({op} ..)`",
                                 op = op
                             ),
-                            eqop_span,
+                            None,
                             &format!("to remove this lint, use either `{op}=` or `= {op}`", op = op),
                         );
                     }
@@ -188,6 +188,7 @@ fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) {
                     binop = binop_str,
                     unop = unop_str
                 ),
+                None,
                 &format!(
                     "put a space between `{binop}` and `{unop}` and remove the space after `{unop}`",
                     binop = binop_str,
@@ -226,7 +227,7 @@ fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
                 SUSPICIOUS_ELSE_FORMATTING,
                 else_span,
                 &format!("this is an `else {}` but the formatting might hide it", else_desc),
-                else_span,
+                None,
                 &format!(
                     "to remove this lint, remove the `else` or remove the new line between \
                      `else` and `{}`",
@@ -265,7 +266,7 @@ fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
                         POSSIBLE_MISSING_COMMA,
                         lint_span,
                         "possibly missing a comma here",
-                        lint_span,
+                        None,
                         "to remove this lint, add a comma or write the expr in a single line",
                     );
                 }
@@ -296,7 +297,7 @@ fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
                     SUSPICIOUS_ELSE_FORMATTING,
                     else_span,
                     &format!("this looks like {} but the `else` is missing", looks_like),
-                    else_span,
+                    None,
                     &format!(
                         "to remove this lint, add the missing `else` or add a new line before {}",
                         next_thing,
diff --git a/clippy_lints/src/functions.rs b/clippy_lints/src/functions.rs
index 7100bad996c..c8c562fe29f 100644
--- a/clippy_lints/src/functions.rs
+++ b/clippy_lints/src/functions.rs
@@ -431,6 +431,7 @@ fn check_needless_must_use(
             DOUBLE_MUST_USE,
             fn_header_span,
             "this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
+            None,
             "either add some descriptive text or remove the attribute",
         );
     }
diff --git a/clippy_lints/src/identity_conversion.rs b/clippy_lints/src/identity_conversion.rs
index 4b7c2c4156e..33a9478f058 100644
--- a/clippy_lints/src/identity_conversion.rs
+++ b/clippy_lints/src/identity_conversion.rs
@@ -1,5 +1,5 @@
 use crate::utils::{
-    match_def_path, match_trait_method, paths, same_tys, snippet, snippet_with_macro_callsite, span_lint_and_then,
+    match_def_path, match_trait_method, paths, same_tys, snippet, snippet_with_macro_callsite, span_lint_and_sugg,
 };
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, HirId, MatchSource};
@@ -58,14 +58,15 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IdentityConversion {
                     if same_tys(cx, a, b) {
                         let sugg = snippet_with_macro_callsite(cx, args[0].span, "<expr>").to_string();
 
-                        span_lint_and_then(cx, IDENTITY_CONVERSION, e.span, "identical conversion", |diag| {
-                            diag.span_suggestion(
-                                e.span,
-                                "consider removing `.into()`",
-                                sugg,
-                                Applicability::MachineApplicable, // snippet
-                            );
-                        });
+                        span_lint_and_sugg(
+                            cx,
+                            IDENTITY_CONVERSION,
+                            e.span,
+                            "identical conversion",
+                            "consider removing `.into()`",
+                            sugg,
+                            Applicability::MachineApplicable, // snippet
+                        );
                     }
                 }
                 if match_trait_method(cx, e, &paths::INTO_ITERATOR) && &*name.ident.as_str() == "into_iter" {
@@ -73,14 +74,15 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IdentityConversion {
                     let b = cx.tables.expr_ty(&args[0]);
                     if same_tys(cx, a, b) {
                         let sugg = snippet(cx, args[0].span, "<expr>").into_owned();
-                        span_lint_and_then(cx, IDENTITY_CONVERSION, e.span, "identical conversion", |diag| {
-                            diag.span_suggestion(
-                                e.span,
-                                "consider removing `.into_iter()`",
-                                sugg,
-                                Applicability::MachineApplicable, // snippet
-                            );
-                        });
+                        span_lint_and_sugg(
+                            cx,
+                            IDENTITY_CONVERSION,
+                            e.span,
+                            "identical conversion",
+                            "consider removing `.into_iter()`",
+                            sugg,
+                            Applicability::MachineApplicable, // snippet
+                        );
                     }
                 }
             },
@@ -95,14 +97,15 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IdentityConversion {
                                 let sugg = snippet(cx, args[0].span.source_callsite(), "<expr>").into_owned();
                                 let sugg_msg =
                                     format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));
-                                span_lint_and_then(cx, IDENTITY_CONVERSION, e.span, "identical conversion", |diag| {
-                                    diag.span_suggestion(
-                                        e.span,
-                                        &sugg_msg,
-                                        sugg,
-                                        Applicability::MachineApplicable, // snippet
-                                    );
-                                });
+                                span_lint_and_sugg(
+                                    cx,
+                                    IDENTITY_CONVERSION,
+                                    e.span,
+                                    "identical conversion",
+                                    &sugg_msg,
+                                    sugg,
+                                    Applicability::MachineApplicable, // snippet
+                                );
                             }
                         }
                     }
diff --git a/clippy_lints/src/if_not_else.rs b/clippy_lints/src/if_not_else.rs
index 271df5b03e3..c11e291f98e 100644
--- a/clippy_lints/src/if_not_else.rs
+++ b/clippy_lints/src/if_not_else.rs
@@ -61,6 +61,7 @@ impl EarlyLintPass for IfNotElse {
                             IF_NOT_ELSE,
                             item.span,
                             "Unnecessary boolean `not` operation",
+                            None,
                             "remove the `!` and swap the blocks of the `if`/`else`",
                         );
                     },
@@ -70,6 +71,7 @@ impl EarlyLintPass for IfNotElse {
                             IF_NOT_ELSE,
                             item.span,
                             "Unnecessary `!=` operation",
+                            None,
                             "change to `==` and swap the blocks of the `if`/`else`",
                         );
                     },
diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs
index a2b1085a36e..c5808dd540b 100644
--- a/clippy_lints/src/indexing_slicing.rs
+++ b/clippy_lints/src/indexing_slicing.rs
@@ -138,7 +138,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IndexingSlicing {
                     (None, None) => return, // [..] is ok.
                 };
 
-                span_lint_and_help(cx, INDEXING_SLICING, expr.span, "slicing may panic.", help_msg);
+                span_lint_and_help(cx, INDEXING_SLICING, expr.span, "slicing may panic.", None, help_msg);
             } else {
                 // Catchall non-range index, i.e., [n] or [n << m]
                 if let ty::Array(..) = ty.kind {
@@ -154,6 +154,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IndexingSlicing {
                     INDEXING_SLICING,
                     expr.span,
                     "indexing may panic.",
+                    None,
                     "Consider using `.get(n)` or `.get_mut(n)` instead",
                 );
             }
diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs
index ca8e834e347..e343d690f6c 100644
--- a/clippy_lints/src/inherent_to_string.rs
+++ b/clippy_lints/src/inherent_to_string.rs
@@ -137,6 +137,7 @@ fn show_lint(cx: &LateContext<'_, '_>, item: &ImplItem<'_>) {
                 "type `{}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`",
                 self_type.to_string()
             ),
+            None,
             &format!("remove the inherent method from type `{}`", self_type.to_string())
         );
     } else {
@@ -148,6 +149,7 @@ fn show_lint(cx: &LateContext<'_, '_>, item: &ImplItem<'_>) {
                 "implementation of inherent method `to_string(&self) -> String` for type `{}`",
                 self_type.to_string()
             ),
+            None,
             &format!("implement trait `Display` for type `{}` instead", self_type.to_string()),
         );
     }
diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs
index 2392ad4d7a1..d5dbd495680 100644
--- a/clippy_lints/src/int_plus_one.rs
+++ b/clippy_lints/src/int_plus_one.rs
@@ -5,7 +5,7 @@ use rustc_errors::Applicability;
 use rustc_lint::{EarlyContext, EarlyLintPass};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 
-use crate::utils::{snippet_opt, span_lint_and_then};
+use crate::utils::{snippet_opt, span_lint_and_sugg};
 
 declare_clippy_lint! {
     /// **What it does:** Checks for usage of `x >= y + 1` or `x - 1 >= y` (and `<=`) in a block
@@ -149,19 +149,14 @@ impl IntPlusOne {
     }
 
     fn emit_warning(cx: &EarlyContext<'_>, block: &Expr, recommendation: String) {
-        span_lint_and_then(
+        span_lint_and_sugg(
             cx,
             INT_PLUS_ONE,
             block.span,
             "Unnecessary `>= y + 1` or `x - 1 >=`",
-            |diag| {
-                diag.span_suggestion(
-                    block.span,
-                    "change it to",
-                    recommendation,
-                    Applicability::MachineApplicable, // snippet
-                );
-            },
+            "change it to",
+            recommendation,
+            Applicability::MachineApplicable, // snippet
         );
     }
 }
diff --git a/clippy_lints/src/integer_division.rs b/clippy_lints/src/integer_division.rs
index 053d66e6af7..fe34d33fe65 100644
--- a/clippy_lints/src/integer_division.rs
+++ b/clippy_lints/src/integer_division.rs
@@ -35,6 +35,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IntegerDivision {
                 INTEGER_DIVISION,
                 expr.span,
                 "integer division",
+                None,
                 "division of integers may cause loss of precision. consider using floats.",
             );
         }
diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs
index f67fce9697a..deb57db1678 100644
--- a/clippy_lints/src/large_stack_arrays.rs
+++ b/clippy_lints/src/large_stack_arrays.rs
@@ -57,6 +57,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LargeStackArrays {
                         "allocating a local array larger than {} bytes",
                         self.maximum_allowed_size
                     ),
+                    None,
                     &format!(
                         "consider allocating on the heap with `vec!{}.into_boxed_slice()`",
                         snippet(cx, expr.span, "[...]")
diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs
index f8f84f3d42d..710dec8d33f 100644
--- a/clippy_lints/src/let_underscore.rs
+++ b/clippy_lints/src/let_underscore.rs
@@ -90,6 +90,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetUnderscore {
                         LET_UNDERSCORE_LOCK,
                         local.span,
                         "non-binding let on a synchronization lock",
+                        None,
                         "consider using an underscore-prefixed named \
                             binding or dropping explicitly with `std::mem::drop`"
                     )
@@ -99,6 +100,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetUnderscore {
                         LET_UNDERSCORE_MUST_USE,
                         local.span,
                         "non-binding let on an expression with `#[must_use]` type",
+                        None,
                         "consider explicitly using expression value"
                     )
                 } else if is_must_use_func_call(cx, init) {
@@ -107,6 +109,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetUnderscore {
                         LET_UNDERSCORE_MUST_USE,
                         local.span,
                         "non-binding let on a result of a `#[must_use]` function",
+                        None,
                         "consider explicitly using function result"
                     )
                 }
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index b8415fa3af1..19c46476263 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -838,6 +838,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         &unwrap::UNNECESSARY_UNWRAP,
         &use_self::USE_SELF,
         &utils::internal_lints::CLIPPY_LINTS_INTERNAL,
+        &utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
         &utils::internal_lints::COMPILER_LINT_FUNCTIONS,
         &utils::internal_lints::DEFAULT_LINT,
         &utils::internal_lints::LINT_WITHOUT_LINT_PASS,
@@ -1051,6 +1052,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|| box unnamed_address::UnnamedAddress);
     store.register_late_pass(|| box dereference::Dereferencing);
     store.register_late_pass(|| box future_not_send::FutureNotSend);
+    store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls);
 
     store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
         LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1162,6 +1164,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
 
     store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
         LintId::of(&utils::internal_lints::CLIPPY_LINTS_INTERNAL),
+        LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
         LintId::of(&utils::internal_lints::COMPILER_LINT_FUNCTIONS),
         LintId::of(&utils::internal_lints::DEFAULT_LINT),
         LintId::of(&utils::internal_lints::LINT_WITHOUT_LINT_PASS),
diff --git a/clippy_lints/src/loops.rs b/clippy_lints/src/loops.rs
index 3172bbfb406..d76df908f09 100644
--- a/clippy_lints/src/loops.rs
+++ b/clippy_lints/src/loops.rs
@@ -1402,6 +1402,7 @@ fn check_arg_type(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>) {
                  `if let` statement.",
                 snippet(cx, arg.span, "_")
             ),
+            None,
             &format!(
                 "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`",
                 snippet(cx, pat.span, "_"),
@@ -1418,6 +1419,7 @@ fn check_arg_type(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>) {
                  `if let` statement.",
                 snippet(cx, arg.span, "_")
             ),
+            None,
             &format!(
                 "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`",
                 snippet(cx, pat.span, "_"),
@@ -2471,45 +2473,53 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, '
                 match_type(cx, ty, &paths::HASHMAP) {
                 if method.ident.name == sym!(len) {
                     let span = shorten_needless_collect_span(expr);
-                    span_lint_and_then(cx, NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, |diag| {
-                        diag.span_suggestion(
-                            span,
-                            "replace with",
-                            ".count()".to_string(),
-                            Applicability::MachineApplicable,
-                        );
-                    });
+                    span_lint_and_sugg(
+                        cx,
+                        NEEDLESS_COLLECT,
+                        span,
+                        NEEDLESS_COLLECT_MSG,
+                        "replace with",
+                        ".count()".to_string(),
+                        Applicability::MachineApplicable,
+                    );
                 }
                 if method.ident.name == sym!(is_empty) {
                     let span = shorten_needless_collect_span(expr);
-                    span_lint_and_then(cx, NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, |diag| {
-                        diag.span_suggestion(
-                            span,
-                            "replace with",
-                            ".next().is_none()".to_string(),
-                            Applicability::MachineApplicable,
-                        );
-                    });
+                    span_lint_and_sugg(
+                        cx,
+                        NEEDLESS_COLLECT,
+                        span,
+                        NEEDLESS_COLLECT_MSG,
+                        "replace with",
+                        ".next().is_none()".to_string(),
+                        Applicability::MachineApplicable,
+                    );
                 }
                 if method.ident.name == sym!(contains) {
                     let contains_arg = snippet(cx, args[1].span, "??");
                     let span = shorten_needless_collect_span(expr);
-                    span_lint_and_then(cx, NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, |diag| {
-                        let (arg, pred) = if contains_arg.starts_with('&') {
-                            ("x", &contains_arg[1..])
-                        } else {
-                            ("&x", &*contains_arg)
-                        };
-                        diag.span_suggestion(
-                            span,
-                            "replace with",
-                            format!(
-                                ".any(|{}| x == {})",
-                                arg, pred
-                            ),
-                            Applicability::MachineApplicable,
-                        );
-                    });
+                    span_lint_and_then(
+                        cx,
+                        NEEDLESS_COLLECT,
+                        span,
+                        NEEDLESS_COLLECT_MSG,
+                        |diag| {
+                            let (arg, pred) = if contains_arg.starts_with('&') {
+                                ("x", &contains_arg[1..])
+                            } else {
+                                ("&x", &*contains_arg)
+                            };
+                            diag.span_suggestion(
+                                span,
+                                "replace with",
+                                format!(
+                                    ".any(|{}| x == {})",
+                                    arg, pred
+                                ),
+                                Applicability::MachineApplicable,
+                            );
+                        }
+                    );
                 }
             }
         }
diff --git a/clippy_lints/src/main_recursion.rs b/clippy_lints/src/main_recursion.rs
index 7854873509e..8a0e47a3d31 100644
--- a/clippy_lints/src/main_recursion.rs
+++ b/clippy_lints/src/main_recursion.rs
@@ -53,6 +53,7 @@ impl LateLintPass<'_, '_> for MainRecursion {
                     MAIN_RECURSION,
                     func.span,
                     &format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")),
+                    None,
                     "consider using another function for this recursion"
                 )
             }
diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs
index a3a05fd1caa..270e306e15f 100644
--- a/clippy_lints/src/matches.rs
+++ b/clippy_lints/src/matches.rs
@@ -441,6 +441,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Matches {
                     REST_PAT_IN_FULLY_BOUND_STRUCTS,
                     pat.span,
                     "unnecessary use of `..` pattern in struct binding. All fields were already bound",
+                    None,
                     "consider removing `..` from this binding",
                 );
             }
@@ -636,7 +637,7 @@ fn check_overlapping_arms<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ex: &'tcx Expr<'
                     MATCH_OVERLAPPING_ARM,
                     start.span,
                     "some ranges overlap",
-                    end.span,
+                    Some(end.span),
                     "overlaps with this",
                 );
             }
@@ -674,7 +675,7 @@ fn check_wild_err_arm(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>])
                                 MATCH_WILD_ERR_ARM,
                                 arm.pat.span,
                                 &format!("`Err({})` matches all errors", &ident_bind_name),
-                                arm.pat.span,
+                                None,
                                 "match each error separately or use the error output",
                             );
                         }
@@ -887,6 +888,7 @@ fn check_wild_in_or_pats(cx: &LateContext<'_, '_>, arms: &[Arm<'_>]) {
                     WILDCARD_IN_OR_PATTERNS,
                     arm.pat.span,
                     "wildcard pattern covers any other pattern as it will match anyway.",
+                    None,
                     "Consider handling `_` separately.",
                 );
             }
diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs
index 69639f45323..ab6865bf0f3 100644
--- a/clippy_lints/src/mem_replace.rs
+++ b/clippy_lints/src/mem_replace.rs
@@ -148,6 +148,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_, '_>, src: &Expr<'_>, expr_span
                         MEM_REPLACE_WITH_UNINIT,
                         expr_span,
                         "replacing with `mem::uninitialized()`",
+                        None,
                         "consider using the `take_mut` crate instead",
                     );
                 } else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id) &&
@@ -157,6 +158,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_, '_>, src: &Expr<'_>, expr_span
                         MEM_REPLACE_WITH_UNINIT,
                         expr_span,
                         "replacing with `mem::zeroed()`",
+                        None,
                         "consider using a default value or the `take_mut` crate instead",
                     );
                 }
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 2337380c7dd..7c652229d33 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -2255,6 +2255,7 @@ fn lint_iter_nth<'a, 'tcx>(
         ITER_NTH,
         expr.span,
         &format!("called `.iter{0}().nth()` on a {1}", mut_str, caller_type),
+        None,
         &format!("calling `.get{}()` is both faster and more readable", mut_str),
     );
 }
@@ -2364,6 +2365,7 @@ fn lint_iter_skip_next(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) {
             ITER_SKIP_NEXT,
             expr.span,
             "called `skip(x).next()` on an iterator",
+            None,
             "this is more succinctly expressed by calling `nth(x)`",
         );
     }
@@ -2431,6 +2433,7 @@ fn lint_unwrap(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, unwrap_args: &[hi
             lint,
             expr.span,
             &format!("used `unwrap()` on `{}` value", kind,),
+            None,
             &format!(
                 "if you don't want to handle the `{}` case gracefully, consider \
                  using `expect()` to provide a better panic message",
@@ -2458,6 +2461,7 @@ fn lint_expect(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, expect_args: &[hi
             lint,
             expr.span,
             &format!("used `expect()` on `{}` value", kind,),
+            None,
             &format!("if this value is an `{}`, it will panic", none_value,),
         );
     }
@@ -2478,6 +2482,7 @@ fn lint_ok_expect(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, ok_args: &[hir
                 OK_EXPECT,
                 expr.span,
                 "called `ok().expect()` on a `Result` value",
+                None,
                 "you can call `expect()` directly on the `Result`",
             );
         }
@@ -2572,7 +2577,7 @@ fn lint_map_unwrap_or_else<'a, 'tcx>(
                 },
                 expr.span,
                 msg,
-                expr.span,
+                None,
                 &format!(
                     "replace `map({0}).unwrap_or_else({1})` with `map_or_else({1}, {0})`",
                     map_snippet, unwrap_snippet,
@@ -2752,7 +2757,7 @@ fn lint_filter_next<'a, 'tcx>(
                 FILTER_NEXT,
                 expr.span,
                 msg,
-                expr.span,
+                None,
                 &format!("replace `filter({0}).next()` with `find({0})`", filter_snippet),
             );
         } else {
@@ -2774,6 +2779,7 @@ fn lint_skip_while_next<'a, 'tcx>(
             SKIP_WHILE_NEXT,
             expr.span,
             "called `skip_while(p).next()` on an `Iterator`",
+            None,
             "this is more succinctly expressed by calling `.find(!p)` instead",
         );
     }
@@ -2790,7 +2796,7 @@ fn lint_filter_map<'a, 'tcx>(
     if match_trait_method(cx, expr, &paths::ITERATOR) {
         let msg = "called `filter(p).map(q)` on an `Iterator`";
         let hint = "this is more succinctly expressed by calling `.filter_map(..)` instead";
-        span_lint_and_help(cx, FILTER_MAP, expr.span, msg, hint);
+        span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
     }
 }
 
@@ -2810,7 +2816,7 @@ fn lint_filter_map_next<'a, 'tcx>(
                 FILTER_MAP_NEXT,
                 expr.span,
                 msg,
-                expr.span,
+                None,
                 &format!("replace `filter_map({0}).next()` with `find_map({0})`", filter_snippet),
             );
         } else {
@@ -2830,7 +2836,7 @@ fn lint_find_map<'a, 'tcx>(
     if match_trait_method(cx, &map_args[0], &paths::ITERATOR) {
         let msg = "called `find(p).map(q)` on an `Iterator`";
         let hint = "this is more succinctly expressed by calling `.find_map(..)` instead";
-        span_lint_and_help(cx, FIND_MAP, expr.span, msg, hint);
+        span_lint_and_help(cx, FIND_MAP, expr.span, msg, None, hint);
     }
 }
 
@@ -2845,7 +2851,7 @@ fn lint_filter_map_map<'a, 'tcx>(
     if match_trait_method(cx, expr, &paths::ITERATOR) {
         let msg = "called `filter_map(p).map(q)` on an `Iterator`";
         let hint = "this is more succinctly expressed by only calling `.filter_map(..)` instead";
-        span_lint_and_help(cx, FILTER_MAP, expr.span, msg, hint);
+        span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
     }
 }
 
@@ -2861,7 +2867,7 @@ fn lint_filter_flat_map<'a, 'tcx>(
         let msg = "called `filter(p).flat_map(q)` on an `Iterator`";
         let hint = "this is more succinctly expressed by calling `.flat_map(..)` \
                     and filtering by returning `iter::empty()`";
-        span_lint_and_help(cx, FILTER_MAP, expr.span, msg, hint);
+        span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
     }
 }
 
@@ -2877,7 +2883,7 @@ fn lint_filter_map_flat_map<'a, 'tcx>(
         let msg = "called `filter_map(p).flat_map(q)` on an `Iterator`";
         let hint = "this is more succinctly expressed by calling `.flat_map(..)` \
                     and filtering by returning `iter::empty()`";
-        span_lint_and_help(cx, FILTER_MAP, expr.span, msg, hint);
+        span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
     }
 }
 
@@ -3260,6 +3266,7 @@ fn lint_suspicious_map(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) {
         SUSPICIOUS_MAP,
         expr.span,
         "this call to `map()` won't have an effect on the call to `count()`",
+        None,
         "make sure you did not confuse `map` with `filter` or `for_each`",
     );
 }
@@ -3640,7 +3647,7 @@ fn lint_filetype_is_file(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &
     }
     let lint_msg = format!("`{}FileType::is_file()` only {} regular files", lint_unary, verb);
     let help_msg = format!("use `{}FileType::is_dir()` instead", help_unary);
-    span_lint_and_help(cx, FILETYPE_IS_FILE, span, &lint_msg, &help_msg);
+    span_lint_and_help(cx, FILETYPE_IS_FILE, span, &lint_msg, None, &help_msg);
 }
 
 fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool {
diff --git a/clippy_lints/src/misc_early.rs b/clippy_lints/src/misc_early.rs
index 75bbf0514c2..adfd8dfb1c1 100644
--- a/clippy_lints/src/misc_early.rs
+++ b/clippy_lints/src/misc_early.rs
@@ -313,6 +313,7 @@ impl EarlyLintPass for MiscEarlyLints {
                     UNNEEDED_FIELD_PATTERN,
                     pat.span,
                     "All the struct fields are matched to a wildcard pattern, consider using `..`.",
+                    None,
                     &format!("Try with `{} {{ .. }}` instead", type_name),
                 );
                 return;
@@ -348,6 +349,7 @@ impl EarlyLintPass for MiscEarlyLints {
                                 field.span,
                                 "You matched a field with a wildcard pattern. Consider using `..` \
                                  instead",
+                                None,
                                 &format!("Try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
                             );
                         }
diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs
index 6be4b1effea..28183810df4 100644
--- a/clippy_lints/src/needless_continue.rs
+++ b/clippy_lints/src/needless_continue.rs
@@ -304,6 +304,7 @@ fn emit_warning<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>, header: &str,
         NEEDLESS_CONTINUE,
         expr.span,
         message,
+        None,
         &format!("{}\n{}", header, snip),
     );
 }
diff --git a/clippy_lints/src/option_env_unwrap.rs b/clippy_lints/src/option_env_unwrap.rs
index 96ab4238008..66dfa20edb5 100644
--- a/clippy_lints/src/option_env_unwrap.rs
+++ b/clippy_lints/src/option_env_unwrap.rs
@@ -46,6 +46,7 @@ impl EarlyLintPass for OptionEnvUnwrap {
                     OPTION_ENV_UNWRAP,
                     expr.span,
                     "this will panic at run-time if the environment variable doesn't exist at compile-time",
+                    None,
                     "consider using the `env!` macro instead"
                 );
             }
diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs
index c190ed42e3f..1e2afb7a674 100644
--- a/clippy_lints/src/ptr.rs
+++ b/clippy_lints/src/ptr.rs
@@ -2,8 +2,8 @@
 
 use crate::utils::ptr::get_spans;
 use crate::utils::{
-    is_type_diagnostic_item, match_qpath, match_type, paths, snippet_opt, span_lint, span_lint_and_then,
-    walk_ptrs_hir_ty,
+    is_type_diagnostic_item, match_qpath, match_type, paths, snippet_opt, span_lint, span_lint_and_sugg,
+    span_lint_and_then, walk_ptrs_hir_ty,
 };
 use if_chain::if_chain;
 use rustc_errors::Applicability;
@@ -234,19 +234,14 @@ fn check_fn(cx: &LateContext<'_, '_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_
                     then {
                         let replacement = snippet_opt(cx, inner.span);
                         if let Some(r) = replacement {
-                            span_lint_and_then(
+                            span_lint_and_sugg(
                                 cx,
                                 PTR_ARG,
                                 arg.span,
                                 "using a reference to `Cow` is not recommended.",
-                                |diag| {
-                                    diag.span_suggestion(
-                                        arg.span,
-                                        "change this to",
-                                        "&".to_owned() + &r,
-                                        Applicability::Unspecified,
-                                    );
-                                },
+                                "change this to",
+                                "&".to_owned() + &r,
+                                Applicability::Unspecified,
                             );
                         }
                     }
diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs
index 4bcb9198792..30084e3e1ff 100644
--- a/clippy_lints/src/regex.rs
+++ b/clippy_lints/src/regex.rs
@@ -208,7 +208,7 @@ fn check_regex<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>, utf8:
             match parser.parse(r) {
                 Ok(r) => {
                     if let Some(repl) = is_trivial_regex(&r) {
-                        span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", repl);
+                        span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl);
                     }
                 },
                 Err(regex_syntax::Error::Parse(e)) => {
@@ -236,7 +236,7 @@ fn check_regex<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>, utf8:
         match parser.parse(&r) {
             Ok(r) => {
                 if let Some(repl) = is_trivial_regex(&r) {
-                    span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", repl);
+                    span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl);
                 }
             },
             Err(regex_syntax::Error::Parse(e)) => {
diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs
index f7ab00b7304..5c9117d5b81 100644
--- a/clippy_lints/src/returns.rs
+++ b/clippy_lints/src/returns.rs
@@ -8,7 +8,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::source_map::Span;
 use rustc_span::BytePos;
 
-use crate::utils::{in_macro, match_path_ast, snippet_opt, span_lint_and_then};
+use crate::utils::{in_macro, match_path_ast, snippet_opt, span_lint_and_sugg, span_lint_and_then};
 
 declare_clippy_lint! {
     /// **What it does:** Checks for return statements at the end of a block.
@@ -162,24 +162,26 @@ impl Return {
             },
             None => match replacement {
                 RetReplacement::Empty => {
-                    span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
-                        diag.span_suggestion(
-                            ret_span,
-                            "remove `return`",
-                            String::new(),
-                            Applicability::MachineApplicable,
-                        );
-                    });
+                    span_lint_and_sugg(
+                        cx,
+                        NEEDLESS_RETURN,
+                        ret_span,
+                        "unneeded `return` statement",
+                        "remove `return`",
+                        String::new(),
+                        Applicability::MachineApplicable,
+                    );
                 },
                 RetReplacement::Block => {
-                    span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
-                        diag.span_suggestion(
-                            ret_span,
-                            "replace `return` with an empty block",
-                            "{}".to_string(),
-                            Applicability::MachineApplicable,
-                        );
-                    });
+                    span_lint_and_sugg(
+                        cx,
+                        NEEDLESS_RETURN,
+                        ret_span,
+                        "unneeded `return` statement",
+                        "replace `return` with an empty block",
+                        "{}".to_string(),
+                        Applicability::MachineApplicable,
+                    );
                 },
             },
         }
@@ -259,14 +261,15 @@ impl EarlyLintPass for Return {
                 } else {
                     (ty.span, Applicability::MaybeIncorrect)
                 };
-                span_lint_and_then(cx, UNUSED_UNIT, rspan, "unneeded unit return type", |diag| {
-                    diag.span_suggestion(
-                        rspan,
-                        "remove the `-> ()`",
-                        String::new(),
-                        appl,
-                    );
-                });
+                span_lint_and_sugg(
+                    cx,
+                    UNUSED_UNIT,
+                    rspan,
+                    "unneeded unit return type",
+                    "remove the `-> ()`",
+                    String::new(),
+                    appl,
+                );
             }
         }
     }
@@ -279,14 +282,15 @@ impl EarlyLintPass for Return {
             if is_unit_expr(expr) && !stmt.span.from_expansion();
             then {
                 let sp = expr.span;
-                span_lint_and_then(cx, UNUSED_UNIT, sp, "unneeded unit expression", |diag| {
-                    diag.span_suggestion(
-                        sp,
-                        "remove the final `()`",
-                        String::new(),
-                        Applicability::MachineApplicable,
-                    );
-                });
+                span_lint_and_sugg(
+                    cx,
+                    UNUSED_UNIT,
+                    sp,
+                    "unneeded unit expression",
+                    "remove the final `()`",
+                    String::new(),
+                    Applicability::MachineApplicable,
+                );
             }
         }
     }
@@ -295,14 +299,15 @@ impl EarlyLintPass for Return {
         match e.kind {
             ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
                 if is_unit_expr(expr) && !expr.span.from_expansion() {
-                    span_lint_and_then(cx, UNUSED_UNIT, expr.span, "unneeded `()`", |diag| {
-                        diag.span_suggestion(
-                            expr.span,
-                            "remove the `()`",
-                            String::new(),
-                            Applicability::MachineApplicable,
-                        );
-                    });
+                    span_lint_and_sugg(
+                        cx,
+                        UNUSED_UNIT,
+                        expr.span,
+                        "unneeded `()`",
+                        "remove the `()`",
+                        String::new(),
+                        Applicability::MachineApplicable,
+                    );
                 }
             },
             _ => (),
diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs
index 075df19a71e..67121729663 100644
--- a/clippy_lints/src/trait_bounds.rs
+++ b/clippy_lints/src/trait_bounds.rs
@@ -76,6 +76,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TraitBounds {
                         TYPE_REPETITION_IN_BOUNDS,
                         p.span,
                         "this type has already been used as a bound predicate",
+                        None,
                         &hint_string,
                     );
                 }
diff --git a/clippy_lints/src/transmute.rs b/clippy_lints/src/transmute.rs
index b99d583c4be..e24d2c4f495 100644
--- a/clippy_lints/src/transmute.rs
+++ b/clippy_lints/src/transmute.rs
@@ -1,5 +1,6 @@
 use crate::utils::{
-    is_normalizable, last_path_segment, match_def_path, paths, snippet, span_lint, span_lint_and_then, sugg,
+    is_normalizable, last_path_segment, match_def_path, paths, snippet, span_lint, span_lint_and_sugg,
+    span_lint_and_then, sugg,
 };
 use if_chain::if_chain;
 use rustc_ast::ast;
@@ -441,24 +442,19 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
                                     ""
                                 };
 
-                                span_lint_and_then(
+                                span_lint_and_sugg(
                                     cx,
                                     TRANSMUTE_BYTES_TO_STR,
                                     e.span,
                                     &format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
-                                    |diag| {
-                                        diag.span_suggestion(
-                                            e.span,
-                                            "consider using",
-                                            format!(
-                                                "std::str::from_utf8{}({}).unwrap()",
-                                                postfix,
-                                                snippet(cx, args[0].span, ".."),
-                                            ),
-                                            Applicability::Unspecified,
-                                        );
-                                    }
-                                )
+                                    "consider using",
+                                    format!(
+                                        "std::str::from_utf8{}({}).unwrap()",
+                                        postfix,
+                                        snippet(cx, args[0].span, ".."),
+                                    ),
+                                    Applicability::Unspecified,
+                                );
                             } else {
                                 if cx.tcx.erase_regions(&from_ty) != cx.tcx.erase_regions(&to_ty) {
                                     span_lint_and_then(
diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types.rs
index 153a2b7249c..4d853b99baf 100644
--- a/clippy_lints/src/types.rs
+++ b/clippy_lints/src/types.rs
@@ -343,6 +343,7 @@ impl Types {
                                 BOX_VEC,
                                 hir_ty.span,
                                 "you seem to be trying to use `Box<Vec<T>>`. Consider using just `Vec<T>`",
+                                None,
                                 "`Vec<T>` is already on the heap, `Box<Vec<T>>` makes an extra allocation.",
                             );
                             return; // don't recurse into the type
@@ -437,6 +438,7 @@ impl Types {
                             LINKEDLIST,
                             hir_ty.span,
                             "I see you're using a LinkedList! Perhaps you meant some other data structure?",
+                            None,
                             "a `VecDeque` might work",
                         );
                         return; // don't recurse into the type
@@ -1900,7 +1902,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AbsurdExtremeComparisons {
                         conclusion
                     );
 
-                    span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, &help);
+                    span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help);
                 }
             }
         }
diff --git a/clippy_lints/src/unnamed_address.rs b/clippy_lints/src/unnamed_address.rs
index b6473fc594e..4e077b95b5c 100644
--- a/clippy_lints/src/unnamed_address.rs
+++ b/clippy_lints/src/unnamed_address.rs
@@ -89,6 +89,7 @@ impl LateLintPass<'_, '_> for UnnamedAddress {
                     VTABLE_ADDRESS_COMPARISONS,
                     expr.span,
                     "comparing trait object pointers compares a non-unique vtable address",
+                    None,
                     "consider extracting and comparing data pointers only",
                 );
             }
@@ -109,6 +110,7 @@ impl LateLintPass<'_, '_> for UnnamedAddress {
                     VTABLE_ADDRESS_COMPARISONS,
                     expr.span,
                     "comparing trait object pointers compares a non-unique vtable address",
+                    None,
                     "consider extracting and comparing data pointers only",
                 );
             }
diff --git a/clippy_lints/src/unused_self.rs b/clippy_lints/src/unused_self.rs
index 4483059e9ec..3d5e2f9fd21 100644
--- a/clippy_lints/src/unused_self.rs
+++ b/clippy_lints/src/unused_self.rs
@@ -69,6 +69,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedSelf {
                         UNUSED_SELF,
                         self_param.span,
                         "unused `self` argument",
+                        None,
                         "consider refactoring to a associated function",
                     );
                     return;
diff --git a/clippy_lints/src/utils/diagnostics.rs b/clippy_lints/src/utils/diagnostics.rs
index 4ad6689f3e0..093ef319108 100644
--- a/clippy_lints/src/utils/diagnostics.rs
+++ b/clippy_lints/src/utils/diagnostics.rs
@@ -62,10 +62,21 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
 ///    |
 ///    = help: Consider using `f64::NAN` if you would like a constant representing NaN
 /// ```
-pub fn span_lint_and_help<'a, T: LintContext>(cx: &'a T, lint: &'static Lint, span: Span, msg: &str, help: &str) {
+pub fn span_lint_and_help<'a, T: LintContext>(
+    cx: &'a T,
+    lint: &'static Lint,
+    span: Span,
+    msg: &str,
+    help_span: Option<Span>,
+    help: &str,
+) {
     cx.struct_span_lint(lint, span, |diag| {
         let mut diag = diag.build(msg);
-        diag.help(help);
+        if let Some(help_span) = help_span {
+            diag.span_help(help_span, help);
+        } else {
+            diag.help(help);
+        }
         docs_link(&mut diag, lint);
         diag.emit();
     });
@@ -97,15 +108,15 @@ pub fn span_lint_and_note<'a, T: LintContext>(
     lint: &'static Lint,
     span: Span,
     msg: &str,
-    note_span: Span,
+    note_span: Option<Span>,
     note: &str,
 ) {
     cx.struct_span_lint(lint, span, |diag| {
         let mut diag = diag.build(msg);
-        if note_span == span {
-            diag.note(note);
-        } else {
+        if let Some(note_span) = note_span {
             diag.span_note(note_span, note);
+        } else {
+            diag.note(note);
         }
         docs_link(&mut diag, lint);
         diag.emit();
@@ -166,6 +177,7 @@ pub fn span_lint_hir_and_then(
 ///     |
 ///     = note: `-D fold-any` implied by `-D warnings`
 /// ```
+#[allow(clippy::collapsible_span_lint_calls)]
 pub fn span_lint_and_sugg<'a, T: LintContext>(
     cx: &'a T,
     lint: &'static Lint,
diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs
index bc2200800de..6eb6c2d98e9 100644
--- a/clippy_lints/src/utils/internal_lints.rs
+++ b/clippy_lints/src/utils/internal_lints.rs
@@ -1,6 +1,7 @@
+use crate::utils::SpanlessEq;
 use crate::utils::{
-    is_expn_of, match_def_path, match_type, method_calls, paths, span_lint, span_lint_and_help, span_lint_and_sugg,
-    walk_ptrs_ty,
+    is_expn_of, match_def_path, match_qpath, match_type, method_calls, paths, snippet, span_lint, span_lint_and_help,
+    span_lint_and_sugg, walk_ptrs_ty,
 };
 use if_chain::if_chain;
 use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, Name, NodeId};
@@ -10,13 +11,15 @@ use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
-use rustc_hir::{Crate, Expr, ExprKind, HirId, Item, MutTy, Mutability, Path, Ty, TyKind};
+use rustc_hir::{Crate, Expr, ExprKind, HirId, Item, MutTy, Mutability, Path, StmtKind, Ty, TyKind};
 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
 use rustc_middle::hir::map::Map;
 use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
 use rustc_span::source_map::{Span, Spanned};
 use rustc_span::symbol::SymbolStr;
 
+use std::borrow::{Borrow, Cow};
+
 declare_clippy_lint! {
     /// **What it does:** Checks for various things we like to keep tidy in clippy.
     ///
@@ -142,6 +145,67 @@ declare_clippy_lint! {
     "found 'default lint description' in a lint declaration"
 }
 
+declare_clippy_lint! {
+    /// **What it does:** Lints `span_lint_and_then` function calls, where the
+    /// closure argument has only one statement and that statement is a method
+    /// call to `span_suggestion`, `span_help`, `span_note` (using the same
+    /// span), `help` or `note`.
+    ///
+    /// These usages of `span_lint_and_then` should be replaced with one of the
+    /// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or
+    /// `span_lint_and_note`.
+    ///
+    /// **Why is this bad?** Using the wrapper `span_lint_and_*` functions, is more
+    /// convenient, readable and less error prone.
+    ///
+    /// **Known problems:** None
+    ///
+    /// *Example:**
+    /// Bad:
+    /// ```rust,ignore
+    /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+    ///     diag.span_suggestion(
+    ///         expr.span,
+    ///         help_msg,
+    ///         sugg.to_string(),
+    ///         Applicability::MachineApplicable,
+    ///     );
+    /// });
+    /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+    ///     diag.span_help(expr.span, help_msg);
+    /// });
+    /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+    ///     diag.help(help_msg);
+    /// });
+    /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+    ///     diag.span_note(expr.span, note_msg);
+    /// });
+    /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+    ///     diag.note(note_msg);
+    /// });
+    /// ```
+    ///
+    /// Good:
+    /// ```rust,ignore
+    /// span_lint_and_sugg(
+    ///     cx,
+    ///     TEST_LINT,
+    ///     expr.span,
+    ///     lint_msg,
+    ///     help_msg,
+    ///     sugg.to_string(),
+    ///     Applicability::MachineApplicable,
+    /// );
+    /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg);
+    /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg);
+    /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg);
+    /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg);
+    /// ```
+    pub COLLAPSIBLE_SPAN_LINT_CALLS,
+    internal,
+    "found collapsible `span_lint_and_then` calls"
+}
+
 declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
 
 impl EarlyLintPass for ClippyLintsInternal {
@@ -194,9 +258,10 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LintWithoutLintPass {
                 if_chain! {
                     if let ExprKind::AddrOf(_, _, ref inner_exp) = expr.kind;
                     if let ExprKind::Struct(_, ref fields, _) = inner_exp.kind;
-                    let field = fields.iter()
-                                      .find(|f| f.ident.as_str() == "desc")
-                                      .expect("lints must have a description field");
+                    let field = fields
+                        .iter()
+                        .find(|f| f.ident.as_str() == "desc")
+                        .expect("lints must have a description field");
                     if let ExprKind::Lit(Spanned {
                         node: LitKind::Str(ref sym, _),
                         ..
@@ -339,6 +404,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CompilerLintFunctions {
                     COMPILER_LINT_FUNCTIONS,
                     path.ident.span,
                     "usage of a compiler lint function",
+                    None,
                     &format!("please use the Clippy variant of this function: `{}`", sugg),
                 );
             }
@@ -391,3 +457,184 @@ fn is_trigger_fn(fn_kind: FnKind<'_>) -> bool {
         FnKind::Closure(..) => false,
     }
 }
+
+declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]);
+
+impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CollapsibleCalls {
+    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) {
+        if_chain! {
+            if let ExprKind::Call(ref func, ref and_then_args) = expr.kind;
+            if let ExprKind::Path(ref path) = func.kind;
+            if match_qpath(path, &["span_lint_and_then"]);
+            if and_then_args.len() == 5;
+            if let ExprKind::Closure(_, _, body_id, _, _) = &and_then_args[4].kind;
+            let body = cx.tcx.hir().body(*body_id);
+            if let ExprKind::Block(block, _) = &body.value.kind;
+            let stmts = &block.stmts;
+            if stmts.len() == 1 && block.expr.is_none();
+            if let StmtKind::Semi(only_expr) = &stmts[0].kind;
+            if let ExprKind::MethodCall(ref ps, _, ref span_call_args) = &only_expr.kind;
+            let and_then_snippets = get_and_then_snippets(cx, and_then_args);
+            let mut sle = SpanlessEq::new(cx).ignore_fn();
+            then {
+                match &*ps.ident.as_str() {
+                    "span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
+                        suggest_suggestion(cx, expr, &and_then_snippets, &span_suggestion_snippets(cx, span_call_args));
+                    },
+                    "span_help" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
+                        let help_snippet = snippet(cx, span_call_args[2].span, r#""...""#);
+                        suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true);
+                    },
+                    "span_note" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
+                        let note_snippet = snippet(cx, span_call_args[2].span, r#""...""#);
+                        suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true);
+                    },
+                    "help" => {
+                        let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+                        suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false);
+                    }
+                    "note" => {
+                        let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+                        suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false);
+                    }
+                    _  => (),
+                }
+            }
+        }
+    }
+}
+
+struct AndThenSnippets<'a> {
+    cx: Cow<'a, str>,
+    lint: Cow<'a, str>,
+    span: Cow<'a, str>,
+    msg: Cow<'a, str>,
+}
+
+fn get_and_then_snippets<'a, 'hir>(
+    cx: &LateContext<'_, '_>,
+    and_then_snippets: &'hir [Expr<'hir>],
+) -> AndThenSnippets<'a> {
+    let cx_snippet = snippet(cx, and_then_snippets[0].span, "cx");
+    let lint_snippet = snippet(cx, and_then_snippets[1].span, "..");
+    let span_snippet = snippet(cx, and_then_snippets[2].span, "span");
+    let msg_snippet = snippet(cx, and_then_snippets[3].span, r#""...""#);
+
+    AndThenSnippets {
+        cx: cx_snippet,
+        lint: lint_snippet,
+        span: span_snippet,
+        msg: msg_snippet,
+    }
+}
+
+struct SpanSuggestionSnippets<'a> {
+    help: Cow<'a, str>,
+    sugg: Cow<'a, str>,
+    applicability: Cow<'a, str>,
+}
+
+fn span_suggestion_snippets<'a, 'hir>(
+    cx: &LateContext<'_, '_>,
+    span_call_args: &'hir [Expr<'hir>],
+) -> SpanSuggestionSnippets<'a> {
+    let help_snippet = snippet(cx, span_call_args[2].span, r#""...""#);
+    let sugg_snippet = snippet(cx, span_call_args[3].span, "..");
+    let applicability_snippet = snippet(cx, span_call_args[4].span, "Applicability::MachineApplicable");
+
+    SpanSuggestionSnippets {
+        help: help_snippet,
+        sugg: sugg_snippet,
+        applicability: applicability_snippet,
+    }
+}
+
+fn suggest_suggestion(
+    cx: &LateContext<'_, '_>,
+    expr: &Expr<'_>,
+    and_then_snippets: &AndThenSnippets<'_>,
+    span_suggestion_snippets: &SpanSuggestionSnippets<'_>,
+) {
+    span_lint_and_sugg(
+        cx,
+        COLLAPSIBLE_SPAN_LINT_CALLS,
+        expr.span,
+        "this call is collapsible",
+        "collapse into",
+        format!(
+            "span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})",
+            and_then_snippets.cx,
+            and_then_snippets.lint,
+            and_then_snippets.span,
+            and_then_snippets.msg,
+            span_suggestion_snippets.help,
+            span_suggestion_snippets.sugg,
+            span_suggestion_snippets.applicability
+        ),
+        Applicability::MachineApplicable,
+    );
+}
+
+fn suggest_help(
+    cx: &LateContext<'_, '_>,
+    expr: &Expr<'_>,
+    and_then_snippets: &AndThenSnippets<'_>,
+    help: &str,
+    with_span: bool,
+) {
+    let option_span = if with_span {
+        format!("Some({})", and_then_snippets.span)
+    } else {
+        "None".to_string()
+    };
+
+    span_lint_and_sugg(
+        cx,
+        COLLAPSIBLE_SPAN_LINT_CALLS,
+        expr.span,
+        "this call is collapsible",
+        "collapse into",
+        format!(
+            "span_lint_and_help({}, {}, {}, {}, {}, {})",
+            and_then_snippets.cx,
+            and_then_snippets.lint,
+            and_then_snippets.span,
+            and_then_snippets.msg,
+            &option_span,
+            help
+        ),
+        Applicability::MachineApplicable,
+    );
+}
+
+fn suggest_note(
+    cx: &LateContext<'_, '_>,
+    expr: &Expr<'_>,
+    and_then_snippets: &AndThenSnippets<'_>,
+    note: &str,
+    with_span: bool,
+) {
+    let note_span = if with_span {
+        format!("Some({})", and_then_snippets.span)
+    } else {
+        "None".to_string()
+    };
+
+    span_lint_and_sugg(
+        cx,
+        COLLAPSIBLE_SPAN_LINT_CALLS,
+        expr.span,
+        "this call is collspible",
+        "collapse into",
+        format!(
+            "span_lint_and_note({}, {}, {}, {}, {}, {})",
+            and_then_snippets.cx,
+            and_then_snippets.lint,
+            and_then_snippets.span,
+            and_then_snippets.msg,
+            note_span,
+            note
+        ),
+        Applicability::MachineApplicable,
+    );
+}
diff --git a/clippy_lints/src/verbose_file_reads.rs b/clippy_lints/src/verbose_file_reads.rs
index 55d7983249a..4d8d4438d88 100644
--- a/clippy_lints/src/verbose_file_reads.rs
+++ b/clippy_lints/src/verbose_file_reads.rs
@@ -40,6 +40,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for VerboseFileReads {
                 VERBOSE_FILE_READS,
                 expr.span,
                 "use of `File::read_to_end`",
+                None,
                 "consider using `fs::read` instead",
             );
         } else if is_file_read_to_string(cx, expr) {
@@ -48,6 +49,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for VerboseFileReads {
                 VERBOSE_FILE_READS,
                 expr.span,
                 "use of `File::read_to_string`",
+                None,
                 "consider using `fs::read_to_string` instead",
             )
         }
diff --git a/clippy_lints/src/zero_div_zero.rs b/clippy_lints/src/zero_div_zero.rs
index afd10d9ed53..fb4700d8743 100644
--- a/clippy_lints/src/zero_div_zero.rs
+++ b/clippy_lints/src/zero_div_zero.rs
@@ -49,6 +49,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ZeroDiv {
                     ZERO_DIVIDED_BY_ZERO,
                     expr.span,
                     "constant division of `0.0` with `0.0` will always result in NaN",
+                    None,
                     &format!(
                         "Consider using `{}::NAN` if you would like a constant representing NaN",
                         float_type,
diff --git a/doc/adding_lints.md b/doc/adding_lints.md
index a66d4e66add..94d6ccb316e 100644
--- a/doc/adding_lints.md
+++ b/doc/adding_lints.md
@@ -265,6 +265,7 @@ impl EarlyLintPass for FooFunctions {
             FOO_FUNCTIONS,
             span,
             "function named `foo`",
+            None,
             "consider using a more meaningful name"
         );
     }
@@ -296,6 +297,7 @@ impl EarlyLintPass for FooFunctions {
                 FOO_FUNCTIONS,
                 span,
                 "function named `foo`",
+                None,
                 "consider using a more meaningful name"
             );
         }
diff --git a/tests/ui/collapsible_span_lint_calls.fixed b/tests/ui/collapsible_span_lint_calls.fixed
new file mode 100644
index 00000000000..e588c23345e
--- /dev/null
+++ b/tests/ui/collapsible_span_lint_calls.fixed
@@ -0,0 +1,91 @@
+// run-rustfix
+#![deny(clippy::internal)]
+#![feature(rustc_private)]
+
+extern crate rustc_ast;
+extern crate rustc_errors;
+extern crate rustc_lint;
+extern crate rustc_session;
+extern crate rustc_span;
+
+use rustc_ast::ast::Expr;
+use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+#[allow(unused_variables)]
+pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F)
+where
+    F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>),
+{
+}
+
+#[allow(unused_variables)]
+fn span_lint_and_help<'a, T: LintContext>(
+    cx: &'a T,
+    lint: &'static Lint,
+    span: Span,
+    msg: &str,
+    option_span: Option<Span>,
+    help: &str,
+) {
+}
+
+#[allow(unused_variables)]
+fn span_lint_and_note<'a, T: LintContext>(
+    cx: &'a T,
+    lint: &'static Lint,
+    span: Span,
+    msg: &str,
+    note_span: Option<Span>,
+    note: &str,
+) {
+}
+
+#[allow(unused_variables)]
+fn span_lint_and_sugg<'a, T: LintContext>(
+    cx: &'a T,
+    lint: &'static Lint,
+    sp: Span,
+    msg: &str,
+    help: &str,
+    sugg: String,
+    applicability: Applicability,
+) {
+}
+
+declare_tool_lint! {
+    pub clippy::TEST_LINT,
+    Warn,
+    "",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(Pass => [TEST_LINT]);
+
+impl EarlyLintPass for Pass {
+    fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
+        let lint_msg = "lint message";
+        let help_msg = "help message";
+        let note_msg = "note message";
+        let sugg = "new_call()";
+        let predicate = true;
+
+        span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable);
+        span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg);
+        span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg);
+        span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg);
+        span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg);
+
+        // This expr shouldn't trigger this lint.
+        span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+            db.note(note_msg);
+            if predicate {
+                db.note(note_msg);
+            }
+        })
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/collapsible_span_lint_calls.rs b/tests/ui/collapsible_span_lint_calls.rs
new file mode 100644
index 00000000000..d5dd3bb562b
--- /dev/null
+++ b/tests/ui/collapsible_span_lint_calls.rs
@@ -0,0 +1,101 @@
+// run-rustfix
+#![deny(clippy::internal)]
+#![feature(rustc_private)]
+
+extern crate rustc_ast;
+extern crate rustc_errors;
+extern crate rustc_lint;
+extern crate rustc_session;
+extern crate rustc_span;
+
+use rustc_ast::ast::Expr;
+use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+#[allow(unused_variables)]
+pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F)
+where
+    F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>),
+{
+}
+
+#[allow(unused_variables)]
+fn span_lint_and_help<'a, T: LintContext>(
+    cx: &'a T,
+    lint: &'static Lint,
+    span: Span,
+    msg: &str,
+    option_span: Option<Span>,
+    help: &str,
+) {
+}
+
+#[allow(unused_variables)]
+fn span_lint_and_note<'a, T: LintContext>(
+    cx: &'a T,
+    lint: &'static Lint,
+    span: Span,
+    msg: &str,
+    note_span: Option<Span>,
+    note: &str,
+) {
+}
+
+#[allow(unused_variables)]
+fn span_lint_and_sugg<'a, T: LintContext>(
+    cx: &'a T,
+    lint: &'static Lint,
+    sp: Span,
+    msg: &str,
+    help: &str,
+    sugg: String,
+    applicability: Applicability,
+) {
+}
+
+declare_tool_lint! {
+    pub clippy::TEST_LINT,
+    Warn,
+    "",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(Pass => [TEST_LINT]);
+
+impl EarlyLintPass for Pass {
+    fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
+        let lint_msg = "lint message";
+        let help_msg = "help message";
+        let note_msg = "note message";
+        let sugg = "new_call()";
+        let predicate = true;
+
+        span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+            db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable);
+        });
+        span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+            db.span_help(expr.span, help_msg);
+        });
+        span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+            db.help(help_msg);
+        });
+        span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+            db.span_note(expr.span, note_msg);
+        });
+        span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+            db.note(note_msg);
+        });
+
+        // This expr shouldn't trigger this lint.
+        span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+            db.note(note_msg);
+            if predicate {
+                db.note(note_msg);
+            }
+        })
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/collapsible_span_lint_calls.stderr b/tests/ui/collapsible_span_lint_calls.stderr
new file mode 100644
index 00000000000..874d4a9f255
--- /dev/null
+++ b/tests/ui/collapsible_span_lint_calls.stderr
@@ -0,0 +1,49 @@
+error: this call is collapsible
+  --> $DIR/collapsible_span_lint_calls.rs:75:9
+   |
+LL | /         span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+LL | |             db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable);
+LL | |         });
+   | |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable)`
+   |
+note: the lint level is defined here
+  --> $DIR/collapsible_span_lint_calls.rs:2:9
+   |
+LL | #![deny(clippy::internal)]
+   |         ^^^^^^^^^^^^^^^^
+   = note: `#[deny(clippy::collapsible_span_lint_calls)]` implied by `#[deny(clippy::internal)]`
+
+error: this call is collapsible
+  --> $DIR/collapsible_span_lint_calls.rs:78:9
+   |
+LL | /         span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+LL | |             db.span_help(expr.span, help_msg);
+LL | |         });
+   | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg)`
+
+error: this call is collapsible
+  --> $DIR/collapsible_span_lint_calls.rs:81:9
+   |
+LL | /         span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+LL | |             db.help(help_msg);
+LL | |         });
+   | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg)`
+
+error: this call is collspible
+  --> $DIR/collapsible_span_lint_calls.rs:84:9
+   |
+LL | /         span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+LL | |             db.span_note(expr.span, note_msg);
+LL | |         });
+   | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg)`
+
+error: this call is collspible
+  --> $DIR/collapsible_span_lint_calls.rs:87:9
+   |
+LL | /         span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+LL | |             db.note(note_msg);
+LL | |         });
+   | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg)`
+
+error: aborting due to 5 previous errors
+
diff --git a/tests/ui/empty_enum.stderr b/tests/ui/empty_enum.stderr
index b1e4eb27755..466dfbe7cee 100644
--- a/tests/ui/empty_enum.stderr
+++ b/tests/ui/empty_enum.stderr
@@ -5,11 +5,7 @@ LL | enum Empty {}
    | ^^^^^^^^^^^^^
    |
    = note: `-D clippy::empty-enum` implied by `-D warnings`
-help: consider using the uninhabited type `!` (never type) or a wrapper around it to introduce a type which can't be instantiated
-  --> $DIR/empty_enum.rs:4:1
-   |
-LL | enum Empty {}
-   | ^^^^^^^^^^^^^
+   = help: consider using the uninhabited type `!` (never type) or a wrapper around it to introduce a type which can't be instantiated
 
 error: aborting due to previous error