Add field_scoped_visibility_modifiers lint

This commit is contained in:
kyle oneill 2024-06-16 15:54:48 -04:00
parent a2c9782128
commit 3405ce3bca
6 changed files with 128 additions and 0 deletions

View file

@ -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,

View file

@ -0,0 +1,75 @@
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 {
/// first_field: bool,
/// second_field: bool
/// }
/// impl MyStruct {
/// pub(crate) fn get_first_field(&self) -> bool {
/// self.first_field
/// }
/// pub(super) fn get_second_field(&self) -> bool {
/// self.second_field
/// }
/// }
/// }
/// ```
#[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.name == rustc_span::symbol::kw::SelfLower {
// 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 private and adding a scoped visibility method for it",
);
}
}
}

View file

@ -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;
@ -1169,6 +1170,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
})
});
store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv())));
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`
}