11use 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} ;
33use clippy_utils:: visitors:: for_each_expr;
44use clippy_utils:: { is_in_cfg_test, is_test_function} ;
55use rustc_errors:: Applicability ;
66use rustc_hir:: intravisit:: FnKind ;
77use rustc_hir:: { self as hir, Body , ExprKind , FnDecl } ;
8+ use rustc_lexer:: is_ident;
89use rustc_lint:: { LateContext , LateLintPass } ;
910use rustc_session:: impl_lint_pass;
10- use rustc_span:: Span ;
1111use rustc_span:: def_id:: LocalDefId ;
12+ use rustc_span:: { Span , Symbol , edition} ;
1213use std:: ops:: ControlFlow ;
1314
1415declare_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
4445pub struct RedundantTestPrefix {
4546 check_outside_test_cfg : bool ,
46- custom_sufix : String ,
4747}
4848
4949impl 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+ }
0 commit comments