diff options
Diffstat (limited to '')
-rw-r--r-- | src/tools/clippy/clippy_lints/src/exhaustive_items.rs | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/exhaustive_items.rs b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs new file mode 100644 index 000000000..173d41b4b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs @@ -0,0 +1,111 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::indent_of; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Warns on any exported `enum`s that are not tagged `#[non_exhaustive]` + /// + /// ### Why is this bad? + /// Exhaustive enums are typically fine, but a project which does + /// not wish to make a stability commitment around exported enums may wish to + /// disable them by default. + /// + /// ### Example + /// ```rust + /// enum Foo { + /// Bar, + /// Baz + /// } + /// ``` + /// Use instead: + /// ```rust + /// #[non_exhaustive] + /// enum Foo { + /// Bar, + /// Baz + /// } + /// ``` + #[clippy::version = "1.51.0"] + pub EXHAUSTIVE_ENUMS, + restriction, + "detects exported enums that have not been marked #[non_exhaustive]" +} + +declare_clippy_lint! { + /// ### What it does + /// Warns on any exported `structs`s that are not tagged `#[non_exhaustive]` + /// + /// ### Why is this bad? + /// Exhaustive structs are typically fine, but a project which does + /// not wish to make a stability commitment around exported structs may wish to + /// disable them by default. + /// + /// ### Example + /// ```rust + /// struct Foo { + /// bar: u8, + /// baz: String, + /// } + /// ``` + /// Use instead: + /// ```rust + /// #[non_exhaustive] + /// struct Foo { + /// bar: u8, + /// baz: String, + /// } + /// ``` + #[clippy::version = "1.51.0"] + pub EXHAUSTIVE_STRUCTS, + restriction, + "detects exported structs that have not been marked #[non_exhaustive]" +} + +declare_lint_pass!(ExhaustiveItems => [EXHAUSTIVE_ENUMS, EXHAUSTIVE_STRUCTS]); + +impl LateLintPass<'_> for ExhaustiveItems { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if_chain! { + if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind; + if cx.access_levels.is_exported(item.def_id); + let attrs = cx.tcx.hir().attrs(item.hir_id()); + if !attrs.iter().any(|a| a.has_name(sym::non_exhaustive)); + then { + let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind { + if v.fields().iter().any(|f| { + let def_id = cx.tcx.hir().local_def_id(f.hir_id); + !cx.tcx.visibility(def_id).is_public() + }) { + // skip structs with private fields + return; + } + (EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive") + } else { + (EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive") + }; + let suggestion_span = item.span.shrink_to_lo(); + let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0)); + span_lint_and_then( + cx, + lint, + item.span, + msg, + |diag| { + let sugg = format!("#[non_exhaustive]\n{}", indent); + diag.span_suggestion(suggestion_span, + "try adding #[non_exhaustive]", + sugg, + Applicability::MaybeIncorrect); + } + ); + + } + } + } +} |