Skip to content

Commit 77b4636

Browse files
committed
fix: resolve review issues
1 parent c89ac83 commit 77b4636

19 files changed

+848
-378
lines changed

clippy_config/src/conf.rs

+4-11
Original file line numberDiff line numberDiff line change
@@ -772,20 +772,13 @@ define_Conf! {
772772
/// exported visibility, or whether they are marked as "pub".
773773
#[lints(pub_underscore_fields)]
774774
pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported,
775-
/// Whether to include functions outside of `#[cfg(test)]` in the linting process or not.
775+
/// Indicates if `redundant_test_prefix` should check functions outside of items marked
776+
/// with `#[cfg(test)]`.
776777
///
777-
/// This option allows running the lint against the integration tests: test functions located
778-
/// there are not inside a node marked with `#[cfg(test)]` annotation (although they are
779-
/// still marked using `#[test]` annotation and thus can have redundant "test_" prefix).
778+
/// This option can be used for integration tests which use the `#[test]` attribute
779+
/// without the `#[cfg(test)]`.
780780
#[lints(redundant_test_prefix)]
781781
redundant_test_prefix_check_outside_cfg_test: bool = false,
782-
/// What suffix to use to avoid function name collisions when `test_` prefix is removed.
783-
///
784-
/// If set to `"_works"`, the lint will suggest renaming `test_foo` to `foo_works`.
785-
/// Suffix is added only when there is a collision with an existing function name,
786-
/// otherwise just `test_` prefix is removed (and no suffix added).
787-
#[lints(redundant_test_prefix)]
788-
redundant_test_prefix_custom_suffix: String = String::from("_works"),
789782
/// Whether to lint only if it's multiline.
790783
#[lints(semicolon_inside_block)]
791784
semicolon_inside_block_ignore_singleline: bool = false,

clippy_lints/src/redundant_test_prefix.rs

+62-25
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use clippy_config::Conf;
2-
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
33
use clippy_utils::visitors::for_each_expr;
44
use clippy_utils::{is_in_cfg_test, is_test_function};
55
use rustc_errors::Applicability;
66
use rustc_hir::intravisit::FnKind;
77
use rustc_hir::{self as hir, Body, ExprKind, FnDecl};
8+
use rustc_lexer::is_ident;
89
use rustc_lint::{LateContext, LateLintPass};
910
use rustc_session::impl_lint_pass;
10-
use rustc_span::Span;
1111
use rustc_span::def_id::LocalDefId;
12+
use rustc_span::{Span, Symbol, edition};
1213
use std::ops::ControlFlow;
1314

1415
declare_clippy_lint! {
@@ -37,20 +38,18 @@ declare_clippy_lint! {
3738
/// ```
3839
#[clippy::version = "1.84.0"]
3940
pub REDUNDANT_TEST_PREFIX,
40-
pedantic,
41+
restriction,
4142
"redundant `test_` prefix in test function name"
4243
}
4344

4445
pub struct RedundantTestPrefix {
4546
check_outside_test_cfg: bool,
46-
custom_sufix: String,
4747
}
4848

4949
impl RedundantTestPrefix {
5050
pub fn new(conf: &'static Conf) -> Self {
5151
Self {
5252
check_outside_test_cfg: conf.redundant_test_prefix_check_outside_cfg_test,
53-
custom_sufix: conf.redundant_test_prefix_custom_suffix.clone(),
5453
}
5554
}
5655
}
@@ -72,31 +71,64 @@ impl<'tcx> LateLintPass<'tcx> for RedundantTestPrefix {
7271
return;
7372
};
7473

74+
// Skip the lint if the function is within a macro expansion.
75+
if ident.span.from_expansion() {
76+
return;
77+
}
78+
79+
// Skip if the function name does not start with `test_`.
80+
if !ident.as_str().starts_with("test_") {
81+
return;
82+
}
83+
7584
// Skip the lint if the function is not within a node marked with `#[cfg(test)]` attribute.
7685
// Continue if the function is inside the node marked with `#[cfg(test)]` or lint is enforced
7786
// via configuration (most likely to include integration tests in lint's scope).
7887
if !(self.check_outside_test_cfg || is_in_cfg_test(cx.tcx, body.id().hir_id)) {
7988
return;
8089
}
8190

82-
if is_test_function(cx.tcx, cx.tcx.local_def_id_to_hir_id(fn_def_id)) && ident.as_str().starts_with("test_") {
91+
if is_test_function(cx.tcx, cx.tcx.local_def_id_to_hir_id(fn_def_id)) {
92+
let msg = "redundant `test_` prefix in test function name";
8393
let mut non_prefixed = ident.as_str().trim_start_matches("test_").to_string();
84-
let mut help_msg = "consider removing the `test_` prefix";
85-
// If `non_prefixed` conflicts with another function in the same module/scope,
86-
// add extra suffix to avoid name conflict.
87-
if name_conflicts(cx, body, &non_prefixed) {
88-
non_prefixed.push_str(&self.custom_sufix);
89-
help_msg = "consider removing the `test_` prefix (suffix avoids name conflict)";
94+
95+
if is_invalid_ident(&non_prefixed) {
96+
// If the prefix-trimmed name is not a valid function name, do not provide an
97+
// automatic fix, just suggest renaming the function.
98+
span_lint_and_help(
99+
cx,
100+
REDUNDANT_TEST_PREFIX,
101+
ident.span,
102+
msg,
103+
None,
104+
"consider function renaming (just removing `test_` prefix will produce invalid function name)",
105+
);
106+
} else if name_conflicts(cx, body, &non_prefixed) {
107+
// If `non_prefixed` conflicts with another function in the same module/scope,
108+
// do not provide an automatic fix, but still emit a fix suggestion.
109+
non_prefixed.push_str("_works");
110+
span_lint_and_sugg(
111+
cx,
112+
REDUNDANT_TEST_PREFIX,
113+
ident.span,
114+
msg,
115+
"consider function renaming (just removing `test_` prefix will cause a name conflict)",
116+
non_prefixed,
117+
Applicability::HasPlaceholders,
118+
)
119+
} else {
120+
// If `non_prefixed` is a valid identifier and does not conflict with another function,
121+
// so we can suggest an auto-fix.
122+
span_lint_and_sugg(
123+
cx,
124+
REDUNDANT_TEST_PREFIX,
125+
ident.span,
126+
msg,
127+
"consider removing the `test_` prefix",
128+
non_prefixed,
129+
Applicability::MachineApplicable,
130+
)
90131
}
91-
span_lint_and_sugg(
92-
cx,
93-
REDUNDANT_TEST_PREFIX,
94-
ident.span,
95-
"redundant `test_` prefix in test function name",
96-
help_msg,
97-
non_prefixed,
98-
Applicability::MachineApplicable,
99-
);
100132
}
101133
}
102134
}
@@ -111,11 +143,11 @@ fn name_conflicts<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, fn_name: &
111143
let id = body.id().hir_id;
112144

113145
// Iterate over items in the same module/scope
114-
let (module, _module_span, _module_hir) = tcx.hir().get_module(tcx.parent_module(id));
146+
let (module, _module_span, _module_hir) = tcx.hir_get_module(tcx.parent_module(id));
115147
for item in module.item_ids {
116-
let item = tcx.hir().item(*item);
117-
if let hir::ItemKind::Fn(..) = item.kind {
118-
if item.ident.name.as_str() == fn_name {
148+
let item = tcx.hir_item(*item);
149+
if let hir::ItemKind::Fn { ident, .. } = item.kind {
150+
if ident.name.as_str() == fn_name {
119151
// Name conflict found
120152
return true;
121153
}
@@ -141,3 +173,8 @@ fn name_conflicts<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, fn_name: &
141173
})
142174
.is_some()
143175
}
176+
177+
fn is_invalid_ident(ident: &str) -> bool {
178+
// The identifier is either a reserved keyword, or starts with an invalid sequence.
179+
Symbol::intern(ident).is_reserved(|| edition::LATEST_STABLE_EDITION) || !is_ident(ident)
180+
}

clippy_utils/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2696,10 +2696,10 @@ pub fn is_test_function(tcx: TyCtxt<'_>, id: HirId) -> bool {
26962696
let node = tcx.hir_node(id);
26972697
once((id, node)).any(|(_id, node)| {
26982698
if let Node::Item(item) = node {
2699-
if let ItemKind::Fn(_, _, _) = item.kind {
2699+
if let ItemKind::Fn { ident, .. } = item.kind {
27002700
// Note that we have sorted the item names in the visitor,
27012701
// so the binary_search gets the same as `contains`, but faster.
2702-
return names.binary_search(&item.ident.name).is_ok();
2702+
return names.binary_search(&ident.name).is_ok();
27032703
}
27042704
}
27052705
false

tests/ui-toml/redundant_test_prefix/custom_suffix/clippy.toml

-2
This file was deleted.

tests/ui-toml/redundant_test_prefix/redundant_test_prefix.custom_suffix.fixed

-55
This file was deleted.

tests/ui-toml/redundant_test_prefix/redundant_test_prefix.custom_suffix.stderr

-29
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,47 @@
1-
//@revisions: default outside_cfg_test custom_suffix
1+
//@revisions: default outside_cfg_test
22
//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/redundant_test_prefix/default
33
//@[outside_cfg_test] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/redundant_test_prefix/outside_cfg_test
4-
//@[custom_suffix] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/redundant_test_prefix/custom_suffix
54
//@compile-flags: --test
65
#![allow(dead_code)]
76
#![warn(clippy::redundant_test_prefix)]
87

98
fn main() {}
109

11-
mod tests_no_annotations {
10+
// Has no `#[cfg(test)]` annotation.
11+
mod tests_no_cfg_annotation {
1212
use super::*;
1313

1414
#[test]
1515
fn test_has_annotation() {
1616
//~[outside_cfg_test]^ redundant_test_prefix
17+
//~[outside_cfg_test]| HELP: consider removing the `test_` prefix
18+
//~[outside_cfg_test]| HELP: to override `-D warnings`
1719
}
1820

1921
fn no_annotation() {}
2022
}
2123

22-
#[test]
23-
fn test_main_module_has_annotation() {
24-
//~[outside_cfg_test]^ redundant_test_prefix
25-
}
26-
27-
fn test_main_module_no_annotation() {}
28-
29-
fn foo() {}
30-
3124
#[cfg(test)]
32-
#[test]
33-
fn foo_works() {
34-
//~^ ERROR: redundant `test_` prefix in test function name
35-
//~| HELP: consider removing the `test_` prefix (suffix avoids name conflict)
25+
mod tests_has_cfg_annotation {
26+
use super::*;
3627

37-
todo!()
38-
// Has prefix, has `#[test]` attribute, within a `#[cfg(test)]`.
39-
// Collision with existing function, so suffix is added.
40-
}
28+
#[test]
29+
fn has_annotation() {
30+
//~^ redundant_test_prefix
31+
//~| HELP: consider removing the `test_` prefix
32+
//~[default]| HELP: to override `-D warnings`
33+
}
4134

42-
fn bar() {}
35+
fn no_annotation() {}
36+
}
4337

4438
#[test]
45-
fn test_bar() {
46-
//~[custom_suffix]^ redundant_test_prefix
47-
48-
todo!()
49-
// Has prefix, has `#[test]` attribute.
50-
// NOT within a `#[cfg(test)]`, but the lint is enabled for integration tests.
51-
// Collision with existing function, so suffix is added.
39+
fn test_main_module_has_annotation() {
40+
//~[outside_cfg_test]^ redundant_test_prefix
41+
//~[outside_cfg_test]| HELP: consider removing the `test_` prefix
5242
}
5343

44+
fn test_main_module_no_annotation() {}
45+
5446
#[cfg(test)]
5547
mod tests {}

tests/ui-toml/redundant_test_prefix/redundant_test_prefix.default.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error: redundant `test_` prefix in test function name
2-
--> tests/ui-toml/redundant_test_prefix/redundant_test_prefix.rs:33:4
2+
--> tests/ui-toml/redundant_test_prefix/redundant_test_prefix.rs:29:8
33
|
4-
LL | fn test_foo() {
5-
| ^^^^^^^^ help: consider removing the `test_` prefix (suffix avoids name conflict): `foo_works`
4+
LL | fn test_has_annotation() {
5+
| ^^^^^^^^^^^^^^^^^^^ help: consider removing the `test_` prefix: `has_annotation`
66
|
77
= note: `-D clippy::redundant-test-prefix` implied by `-D warnings`
88
= help: to override `-D warnings` add `#[allow(clippy::redundant_test_prefix)]`

0 commit comments

Comments
 (0)