Skip to content

Commit 0d85ac1

Browse files
committed
add new lint, pub_underscore_fields
- add a new late pass lint, with config options - add ui tests for both variations of config option - update CHANGELOG.md
1 parent dd857f8 commit 0d85ac1

File tree

12 files changed

+251
-1
lines changed

12 files changed

+251
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5423,6 +5423,7 @@ Released 2018-09-13
54235423
[`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq
54245424
[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
54255425
[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
5426+
[`pub_underscore_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields
54265427
[`pub_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_use
54275428
[`pub_with_shorthand`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_with_shorthand
54285429
[`pub_without_shorthand`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_without_shorthand

clippy_config/src/conf.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::msrvs::Msrv;
2-
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, Rename};
2+
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename};
33
use crate::ClippyConfiguration;
44
use rustc_data_structures::fx::FxHashSet;
55
use rustc_session::Session;
@@ -547,6 +547,11 @@ define_Conf! {
547547
///
548548
/// Whether to also run the listed lints on private items.
549549
(check_private_items: bool = false),
550+
/// Lint: PUB_UNDERSCORE_FIELDS
551+
///
552+
/// Lint "public" fields in a struct that are prefixed with an underscore based on their
553+
/// exported visibility, or whether they are marked as "pub".
554+
(pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PublicallyExported),
550555
}
551556

552557
/// Search for the configuration file.

clippy_config/src/types.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,9 @@ unimplemented_serialize! {
126126
Rename,
127127
MacroMatcher,
128128
}
129+
130+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
131+
pub enum PubUnderscoreFieldsBehaviour {
132+
PublicallyExported,
133+
AllPubFields,
134+
}

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
574574
crate::ptr::MUT_FROM_REF_INFO,
575575
crate::ptr::PTR_ARG_INFO,
576576
crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO,
577+
crate::pub_underscore_fields::PUB_UNDERSCORE_FIELDS_INFO,
577578
crate::pub_use::PUB_USE_INFO,
578579
crate::question_mark::QUESTION_MARK_INFO,
579580
crate::question_mark_used::QUESTION_MARK_USED_INFO,

clippy_lints/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ mod permissions_set_readonly_false;
271271
mod precedence;
272272
mod ptr;
273273
mod ptr_offset_with_cast;
274+
mod pub_underscore_fields;
274275
mod pub_use;
275276
mod question_mark;
276277
mod question_mark_used;
@@ -570,6 +571,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
570571
verbose_bit_mask_threshold,
571572
warn_on_all_wildcard_imports,
572573
check_private_items,
574+
pub_underscore_fields_behavior,
573575

574576
blacklisted_names: _,
575577
cyclomatic_complexity_threshold: _,
@@ -1080,6 +1082,11 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
10801082
store.register_late_pass(|_| Box::new(uninhabited_references::UninhabitedReferences));
10811083
store.register_late_pass(|_| Box::new(ineffective_open_options::IneffectiveOpenOptions));
10821084
store.register_late_pass(|_| Box::new(unconditional_recursion::UnconditionalRecursion));
1085+
store.register_late_pass(move |_| {
1086+
Box::new(pub_underscore_fields::PubUnderscoreFields {
1087+
behavior: pub_underscore_fields_behavior,
1088+
})
1089+
});
10831090
// add lints here, do not remove this comment, it's used in `new_lint`
10841091
}
10851092

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use clippy_config::types::PubUnderscoreFieldsBehaviour;
2+
use clippy_utils::attrs::is_doc_hidden;
3+
use clippy_utils::diagnostics::span_lint_and_help;
4+
use clippy_utils::is_path_lang_item;
5+
use clippy_utils::source::snippet_opt;
6+
use rustc_hir::{FieldDef, Item, ItemKind, LangItem};
7+
use rustc_lint::{LateContext, LateLintPass};
8+
use rustc_session::impl_lint_pass;
9+
use rustc_span::Span;
10+
11+
declare_clippy_lint! {
12+
/// ### What it does
13+
/// Checks whether any field of the struct is prefixed with an `_` (underscore) and also marked
14+
/// `pub` (public)
15+
///
16+
/// ### Why is this bad?
17+
/// Fields prefixed with an `_` are inferred as unused, which suggests it should not be marked
18+
/// as `pub`, because marking it as `pub` infers it will be used.
19+
///
20+
/// ### Example
21+
/// ```rust
22+
/// struct FileHandle {
23+
/// pub _descriptor: usize,
24+
/// }
25+
/// ```
26+
/// Use instead:
27+
/// ```rust
28+
/// struct FileHandle {
29+
/// _descriptor: usize,
30+
/// }
31+
/// ```
32+
///
33+
/// // OR
34+
///
35+
/// ```rust
36+
/// struct FileHandle {
37+
/// pub descriptor: usize,
38+
/// }
39+
/// ```
40+
#[clippy::version = "1.76.0"]
41+
pub PUB_UNDERSCORE_FIELDS,
42+
pedantic,
43+
"struct field prefixed with underscore and marked public"
44+
}
45+
46+
pub struct PubUnderscoreFields {
47+
pub behavior: PubUnderscoreFieldsBehaviour,
48+
}
49+
impl_lint_pass!(PubUnderscoreFields => [PUB_UNDERSCORE_FIELDS]);
50+
51+
impl<'tcx> LateLintPass<'tcx> for PubUnderscoreFields {
52+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
53+
// This lint only pertains to structs.
54+
let ItemKind::Struct(variant_data, _) = &item.kind else {
55+
return;
56+
};
57+
58+
let is_visible = |field: &FieldDef<'_>| match self.behavior {
59+
PubUnderscoreFieldsBehaviour::PublicallyExported => cx.effective_visibilities.is_reachable(field.def_id),
60+
PubUnderscoreFieldsBehaviour::AllPubFields => start_with_pub(cx, field.vis_span),
61+
};
62+
63+
for field in variant_data.fields() {
64+
// Only pertains to fields that start with an underscore, and are public.
65+
if field.ident.as_str().starts_with('_') && is_visible(field)
66+
// We ignore fields that have `#[doc(hidden)]`.
67+
&& !is_doc_hidden(cx.tcx.hir().attrs(field.hir_id))
68+
// We ignore fields that are `PhantomData`.
69+
&& !is_path_lang_item(cx, field.ty, LangItem::PhantomData)
70+
{
71+
span_lint_and_help(
72+
cx,
73+
PUB_UNDERSCORE_FIELDS,
74+
field.vis_span.to(field.ident.span),
75+
"field marked as public but also inferred as unused because it's prefixed with `_`",
76+
None,
77+
"consider removing the underscore, or making the field private",
78+
);
79+
}
80+
}
81+
}
82+
}
83+
84+
fn start_with_pub(cx: &LateContext<'_>, span: Span) -> bool {
85+
snippet_opt(cx, span)
86+
.map(|s| s.as_str().starts_with("pub"))
87+
.unwrap_or(false)
88+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub-underscore-fields-behavior = "AllPubFields"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub-underscore-fields-behavior = "PublicallyExported"
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
error: field marked as public but also inferred as unused because it's prefixed with `_`
2+
--> $DIR/pub_underscore_fields.rs:15:9
3+
|
4+
LL | pub _b: u8,
5+
| ^^^^^^
6+
|
7+
= help: consider removing the underscore, or making the field private
8+
= note: `-D clippy::pub-underscore-fields` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::pub_underscore_fields)]`
10+
11+
error: field marked as public but also inferred as unused because it's prefixed with `_`
12+
--> $DIR/pub_underscore_fields.rs:23:13
13+
|
14+
LL | pub(in crate::inner) _f: Option<()>,
15+
| ^^^^^^^^^^^^^^^^^^^^^^^
16+
|
17+
= help: consider removing the underscore, or making the field private
18+
19+
error: field marked as public but also inferred as unused because it's prefixed with `_`
20+
--> $DIR/pub_underscore_fields.rs:27:13
21+
|
22+
LL | pub _g: String,
23+
| ^^^^^^
24+
|
25+
= help: consider removing the underscore, or making the field private
26+
27+
error: field marked as public but also inferred as unused because it's prefixed with `_`
28+
--> $DIR/pub_underscore_fields.rs:34:9
29+
|
30+
LL | pub _a: usize,
31+
| ^^^^^^
32+
|
33+
= help: consider removing the underscore, or making the field private
34+
35+
error: field marked as public but also inferred as unused because it's prefixed with `_`
36+
--> $DIR/pub_underscore_fields.rs:41:9
37+
|
38+
LL | pub _c: i64,
39+
| ^^^^^^
40+
|
41+
= help: consider removing the underscore, or making the field private
42+
43+
error: field marked as public but also inferred as unused because it's prefixed with `_`
44+
--> $DIR/pub_underscore_fields.rs:44:9
45+
|
46+
LL | pub _e: Option<u8>,
47+
| ^^^^^^
48+
|
49+
= help: consider removing the underscore, or making the field private
50+
51+
error: field marked as public but also inferred as unused because it's prefixed with `_`
52+
--> $DIR/pub_underscore_fields.rs:57:9
53+
|
54+
LL | pub(crate) _b: Option<String>,
55+
| ^^^^^^^^^^^^^
56+
|
57+
= help: consider removing the underscore, or making the field private
58+
59+
error: aborting due to 7 previous errors
60+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error: field marked as public but also inferred as unused because it's prefixed with `_`
2+
--> $DIR/pub_underscore_fields.rs:15:9
3+
|
4+
LL | pub _b: u8,
5+
| ^^^^^^
6+
|
7+
= help: consider removing the underscore, or making the field private
8+
= note: `-D clippy::pub-underscore-fields` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::pub_underscore_fields)]`
10+
11+
error: aborting due to 1 previous error
12+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//@revisions: exported all_pub_fields
2+
//@[all_pub_fields] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/pub_underscore_fields/all_pub_fields
3+
//@[exported] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/pub_underscore_fields/exported
4+
5+
#![allow(unused)]
6+
#![warn(clippy::pub_underscore_fields)]
7+
8+
use std::marker::PhantomData;
9+
10+
pub mod inner {
11+
use std::marker;
12+
13+
pub struct PubSuper {
14+
pub(super) a: usize,
15+
pub _b: u8,
16+
_c: i32,
17+
pub _mark: marker::PhantomData<u8>,
18+
}
19+
20+
mod inner2 {
21+
pub struct PubModVisibility {
22+
pub(in crate::inner) e: bool,
23+
pub(in crate::inner) _f: Option<()>,
24+
}
25+
26+
struct PrivateStructPubField {
27+
pub _g: String,
28+
}
29+
}
30+
}
31+
32+
fn main() {
33+
pub struct StructWithOneViolation {
34+
pub _a: usize,
35+
}
36+
37+
// should handle structs with multiple violations
38+
pub struct StructWithMultipleViolations {
39+
a: u8,
40+
_b: usize,
41+
pub _c: i64,
42+
#[doc(hidden)]
43+
pub d: String,
44+
pub _e: Option<u8>,
45+
}
46+
47+
// shouldn't warn on anonymous fields
48+
pub struct AnonymousFields(pub usize, i32);
49+
50+
// don't warn on empty structs
51+
pub struct Empty1;
52+
pub struct Empty2();
53+
pub struct Empty3 {};
54+
55+
pub struct PubCrate {
56+
pub(crate) a: String,
57+
pub(crate) _b: Option<String>,
58+
}
59+
60+
// shouldn't warn on fields named pub
61+
pub struct NamedPub {
62+
r#pub: bool,
63+
_pub: String,
64+
pub(crate) _mark: PhantomData<u8>,
65+
}
66+
}

tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
4949
missing-docs-in-crate-items
5050
msrv
5151
pass-by-value-size-limit
52+
pub-underscore-fields-behavior
5253
semicolon-inside-block-ignore-singleline
5354
semicolon-outside-block-ignore-multiline
5455
single-char-binding-names-threshold
@@ -124,6 +125,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
124125
missing-docs-in-crate-items
125126
msrv
126127
pass-by-value-size-limit
128+
pub-underscore-fields-behavior
127129
semicolon-inside-block-ignore-singleline
128130
semicolon-outside-block-ignore-multiline
129131
single-char-binding-names-threshold

0 commit comments

Comments
 (0)