From e12bae516bff172789a75788246d4d4825aa0e3a Mon Sep 17 00:00:00 2001 From: kyle o'neill Date: Thu, 6 Jun 2024 00:45:49 -0400 Subject: [PATCH] Add field_scoped_visibility_modifiers lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + .../src/field_scoped_visibility_modifiers.rs | 67 +++++++++++++++++++ clippy_lints/src/lib.rs | 2 + tests/ui/field_scoped_visibility_modifiers.rs | 21 ++++++ .../field_scoped_visibility_modifiers.stderr | 28 ++++++++ 6 files changed, 120 insertions(+) create mode 100644 clippy_lints/src/field_scoped_visibility_modifiers.rs create mode 100644 tests/ui/field_scoped_visibility_modifiers.rs create mode 100644 tests/ui/field_scoped_visibility_modifiers.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index f73bcfdfae31..bf450e6f224a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5304,6 +5304,7 @@ Released 2018-09-13 [`extra_unused_type_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_type_parameters [`fallible_impl_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#fallible_impl_from [`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default +[`field_scoped_visibility_modifiers`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_scoped_visibility_modifiers [`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file [`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map [`filter_map_bool_then`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_bool_then diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index a9f2dd4499a3..542507d164b9 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -178,6 +178,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::explicit_write::EXPLICIT_WRITE_INFO, crate::extra_unused_type_parameters::EXTRA_UNUSED_TYPE_PARAMETERS_INFO, crate::fallible_impl_from::FALLIBLE_IMPL_FROM_INFO, + crate::field_scoped_visibility_modifiers::FIELD_SCOPED_VISIBILITY_MODIFIERS_INFO, crate::float_literal::EXCESSIVE_PRECISION_INFO, crate::float_literal::LOSSY_FLOAT_LITERAL_INFO, crate::floating_point_arithmetic::IMPRECISE_FLOPS_INFO, diff --git a/clippy_lints/src/field_scoped_visibility_modifiers.rs b/clippy_lints/src/field_scoped_visibility_modifiers.rs new file mode 100644 index 000000000000..ddb9e3e95ac9 --- /dev/null +++ b/clippy_lints/src/field_scoped_visibility_modifiers.rs @@ -0,0 +1,67 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_ast::ast::{Item, ItemKind, VisibilityKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of scoped visibility modifiers, like `pub(crate)`, on fields. These + /// make a field visible within a scope between public and private. + /// + /// ### Why restrict this? + /// Scoped visibility modifiers cause a field to be accessible within some scope between + /// public and private, potentially within an entire crate. This allows for fields to be + /// non-private while upholding internal invariants, but can be a code smell. Scoped visibility + /// requires checking a greater area, potentially an entire crate, to verify that an invariant + /// is upheld, and global analysis requires a lot of effort. + /// + /// ### Example + /// ```no_run + /// pub mod public_module { + /// struct MyStruct { + /// pub(crate) first_field: bool, + /// pub(super) second_field: bool + /// } + /// } + /// ``` + /// Use instead: + /// ```no_run + /// pub mod public_module { + /// struct MyStruct { + /// pub first_field: bool, + /// pub second_field: bool + /// } + /// } + /// ``` + #[clippy::version = "1.78.0"] + pub FIELD_SCOPED_VISIBILITY_MODIFIERS, + restriction, + "checks for usage of a scoped visibility modifier, like `pub(crate)`, on fields" +} + +declare_lint_pass!(FieldScopedVisibilityModifiers => [FIELD_SCOPED_VISIBILITY_MODIFIERS]); + +impl EarlyLintPass for FieldScopedVisibilityModifiers { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + let ItemKind::Struct(ref st, _) = item.kind else { + return; + }; + for field in st.fields() { + let VisibilityKind::Restricted { path, .. } = &field.vis.kind else { + continue; + }; + if !path.segments.is_empty() && path.segments[0].ident.as_str() == "self" { + // pub(self) is equivalent to not using pub at all, so we ignore it + continue; + } + span_lint_and_help( + cx, + FIELD_SCOPED_VISIBILITY_MODIFIERS, + field.vis.span, + "scoped visibility modifier on a field", + None, + "consider making the field either public or private", + ); + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 6946c2986f48..27dd8eb5561b 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -136,6 +136,7 @@ mod exit; mod explicit_write; mod extra_unused_type_parameters; mod fallible_impl_from; +mod field_scoped_visibility_modifiers; mod float_literal; mod floating_point_arithmetic; mod format; @@ -1167,6 +1168,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { ..Default::default() }) }); + store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/tests/ui/field_scoped_visibility_modifiers.rs b/tests/ui/field_scoped_visibility_modifiers.rs new file mode 100644 index 000000000000..5789dbf9b1d7 --- /dev/null +++ b/tests/ui/field_scoped_visibility_modifiers.rs @@ -0,0 +1,21 @@ +#![warn(clippy::field_scoped_visibility_modifiers)] + +pub mod pub_module { + pub(in crate::pub_module) mod pub_in_path_module {} + pub(super) mod pub_super_module {} + struct MyStruct { + private_field: bool, + pub pub_field: bool, + pub(crate) pub_crate_field: bool, + pub(in crate::pub_module) pub_in_path_field: bool, + pub(super) pub_super_field: bool, + #[allow(clippy::needless_pub_self)] + pub(self) pub_self_field: bool, + } +} +pub(crate) mod pub_crate_module {} + +#[allow(clippy::needless_pub_self)] +pub(self) mod pub_self_module {} + +fn main() {} diff --git a/tests/ui/field_scoped_visibility_modifiers.stderr b/tests/ui/field_scoped_visibility_modifiers.stderr new file mode 100644 index 000000000000..cab829e92a7f --- /dev/null +++ b/tests/ui/field_scoped_visibility_modifiers.stderr @@ -0,0 +1,28 @@ +error: scoped visibility modifier on a field + --> tests/ui/field_scoped_visibility_modifiers.rs:9:9 + | +LL | pub(crate) pub_crate_field: bool, + | ^^^^^^^^^^ + | + = help: consider making the field either public or private + = note: `-D clippy::field-scoped-visibility-modifiers` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::field_scoped_visibility_modifiers)]` + +error: scoped visibility modifier on a field + --> tests/ui/field_scoped_visibility_modifiers.rs:10:9 + | +LL | pub(in crate::pub_module) pub_in_path_field: bool, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider making the field either public or private + +error: scoped visibility modifier on a field + --> tests/ui/field_scoped_visibility_modifiers.rs:11:9 + | +LL | pub(super) pub_super_field: bool, + | ^^^^^^^^^^ + | + = help: consider making the field either public or private + +error: aborting due to 3 previous errors +