@@ -3,14 +3,17 @@ use crate::types::{
3
3
DisallowedPath , DisallowedPathWithoutReplacement , MacroMatcher , MatchLintBehaviour , PubUnderscoreFieldsBehaviour ,
4
4
Rename , SourceItemOrdering , SourceItemOrderingCategory , SourceItemOrderingModuleItemGroupings ,
5
5
SourceItemOrderingModuleItemKind , SourceItemOrderingTraitAssocItemKind , SourceItemOrderingTraitAssocItemKinds ,
6
+ SourceItemOrderingWithinModuleItemGroupings ,
6
7
} ;
7
8
use clippy_utils:: msrvs:: Msrv ;
9
+ use itertools:: Itertools ;
8
10
use rustc_errors:: Applicability ;
9
11
use rustc_session:: Session ;
10
12
use rustc_span:: edit_distance:: edit_distance;
11
13
use rustc_span:: { BytePos , Pos , SourceFile , Span , SyntaxContext } ;
12
14
use serde:: de:: { IgnoredAny , IntoDeserializer , MapAccess , Visitor } ;
13
15
use serde:: { Deserialize , Deserializer , Serialize } ;
16
+ use std:: collections:: HashMap ;
14
17
use std:: fmt:: { Debug , Display , Formatter } ;
15
18
use std:: ops:: Range ;
16
19
use std:: path:: PathBuf ;
@@ -79,6 +82,7 @@ const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
79
82
#[ derive( Default ) ]
80
83
struct TryConf {
81
84
conf : Conf ,
85
+ value_spans : HashMap < String , Range < usize > > ,
82
86
errors : Vec < ConfError > ,
83
87
warnings : Vec < ConfError > ,
84
88
}
@@ -87,6 +91,7 @@ impl TryConf {
87
91
fn from_toml_error ( file : & SourceFile , error : & toml:: de:: Error ) -> Self {
88
92
Self {
89
93
conf : Conf :: default ( ) ,
94
+ value_spans : HashMap :: default ( ) ,
90
95
errors : vec ! [ ConfError :: from_toml( file, error) ] ,
91
96
warnings : vec ! [ ] ,
92
97
}
@@ -210,6 +215,7 @@ macro_rules! define_Conf {
210
215
}
211
216
212
217
fn visit_map<V >( self , mut map: V ) -> Result <Self :: Value , V :: Error > where V : MapAccess <' de> {
218
+ let mut value_spans = HashMap :: new( ) ;
213
219
let mut errors = Vec :: new( ) ;
214
220
let mut warnings = Vec :: new( ) ;
215
221
$( let mut $name = None ; ) *
@@ -232,6 +238,7 @@ macro_rules! define_Conf {
232
238
}
233
239
None => {
234
240
$name = Some ( value) ;
241
+ value_spans. insert( name. get_ref( ) . as_str( ) . to_string( ) , value_span) ;
235
242
// $new_conf is the same as one of the defined `$name`s, so
236
243
// this variable is defined in line 2 of this function.
237
244
$( match $new_conf {
@@ -250,7 +257,7 @@ macro_rules! define_Conf {
250
257
}
251
258
}
252
259
let conf = Conf { $( $name: $name. unwrap_or_else( defaults:: $name) , ) * } ;
253
- Ok ( TryConf { conf, errors, warnings } )
260
+ Ok ( TryConf { conf, value_spans , errors, warnings } )
254
261
}
255
262
}
256
263
@@ -596,6 +603,13 @@ define_Conf! {
596
603
/// The named groupings of different source item kinds within modules.
597
604
#[ lints( arbitrary_source_item_ordering) ]
598
605
module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS . into( ) ,
606
+ /// Whether the items within module groups should be ordered alphabetically or not.
607
+ ///
608
+ /// This option can be configured to "all", "none", or a list of specific grouping names that should be checked
609
+ /// (e.g. only "enums").
610
+ #[ lints( arbitrary_source_item_ordering) ]
611
+ module_items_ordered_within_groupings: SourceItemOrderingWithinModuleItemGroupings =
612
+ SourceItemOrderingWithinModuleItemGroupings :: None ,
599
613
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
600
614
#[ default_text = "current version" ]
601
615
#[ lints(
@@ -654,6 +668,7 @@ define_Conf! {
654
668
option_as_ref_deref,
655
669
option_map_unwrap_or,
656
670
ptr_as_ptr,
671
+ question_mark,
657
672
redundant_field_names,
658
673
redundant_static_lifetimes,
659
674
repeat_vec_with_capacity,
@@ -815,6 +830,36 @@ fn deserialize(file: &SourceFile) -> TryConf {
815
830
& mut conf. conf . allow_renamed_params_for ,
816
831
DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS ,
817
832
) ;
833
+
834
+ // Confirms that the user has not accidentally configured ordering requirements for groups that
835
+ // aren't configured.
836
+ if let SourceItemOrderingWithinModuleItemGroupings :: Custom ( groupings) =
837
+ & conf. conf . module_items_ordered_within_groupings
838
+ {
839
+ for grouping in groupings {
840
+ if !conf. conf . module_item_order_groupings . is_grouping ( grouping) {
841
+ // Since this isn't fixable by rustfix, don't emit a `Suggestion`. This just adds some useful
842
+ // info for the user instead.
843
+
844
+ let names = conf. conf . module_item_order_groupings . grouping_names ( ) ;
845
+ let suggestion = suggest_candidate ( grouping, names. iter ( ) . map ( String :: as_str) )
846
+ . map ( |s| format ! ( " perhaps you meant `{s}`?" ) )
847
+ . unwrap_or_default ( ) ;
848
+ let names = names. iter ( ) . map ( |s| format ! ( "`{s}`" ) ) . join ( ", " ) ;
849
+ let message = format ! (
850
+ "unknown ordering group: `{grouping}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}"
851
+ ) ;
852
+
853
+ let span = conf
854
+ . value_spans
855
+ . get ( "module_item_order_groupings" )
856
+ . cloned ( )
857
+ . unwrap_or_default ( ) ;
858
+ conf. errors . push ( ConfError :: spanned ( file, message, None , span) ) ;
859
+ }
860
+ }
861
+ }
862
+
818
863
// TODO: THIS SHOULD BE TESTED, this comment will be gone soon
819
864
if conf. conf . allowed_idents_below_min_chars . iter ( ) . any ( |e| e == ".." ) {
820
865
conf. conf
@@ -860,6 +905,7 @@ impl Conf {
860
905
861
906
let TryConf {
862
907
mut conf,
908
+ value_spans : _,
863
909
errors,
864
910
warnings,
865
911
} = match path {
@@ -950,17 +996,10 @@ impl serde::de::Error for FieldError {
950
996
}
951
997
}
952
998
953
- let suggestion = expected
954
- . iter ( )
955
- . filter_map ( |expected| {
956
- let dist = edit_distance ( field, expected, 4 ) ?;
957
- Some ( ( dist, expected) )
958
- } )
959
- . min_by_key ( |& ( dist, _) | dist)
960
- . map ( |( _, suggestion) | Suggestion {
961
- message : "perhaps you meant" ,
962
- suggestion,
963
- } ) ;
999
+ let suggestion = suggest_candidate ( field, expected) . map ( |suggestion| Suggestion {
1000
+ message : "perhaps you meant" ,
1001
+ suggestion,
1002
+ } ) ;
964
1003
965
1004
Self { error : msg, suggestion }
966
1005
}
@@ -998,6 +1037,22 @@ fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
998
1037
( rows, column_widths)
999
1038
}
1000
1039
1040
+ /// Given a user-provided value that couldn't be matched to a known option, finds the most likely
1041
+ /// candidate among candidates that the user might have meant.
1042
+ fn suggest_candidate < ' a , I > ( value : & str , candidates : I ) -> Option < & ' a str >
1043
+ where
1044
+ I : IntoIterator < Item = & ' a str > ,
1045
+ {
1046
+ candidates
1047
+ . into_iter ( )
1048
+ . filter_map ( |expected| {
1049
+ let dist = edit_distance ( value, expected, 4 ) ?;
1050
+ Some ( ( dist, expected) )
1051
+ } )
1052
+ . min_by_key ( |& ( dist, _) | dist)
1053
+ . map ( |( _, suggestion) | suggestion)
1054
+ }
1055
+
1001
1056
#[ cfg( test) ]
1002
1057
mod tests {
1003
1058
use serde:: de:: IgnoredAny ;
0 commit comments