@@ -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(
@@ -815,6 +829,36 @@ fn deserialize(file: &SourceFile) -> TryConf {
815
829
& mut conf. conf . allow_renamed_params_for ,
816
830
DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS ,
817
831
) ;
832
+
833
+ // Confirms that the user has not accidentally configured ordering requirements for groups that
834
+ // aren't configured.
835
+ if let SourceItemOrderingWithinModuleItemGroupings :: Custom ( groupings) =
836
+ & conf. conf . module_items_ordered_within_groupings
837
+ {
838
+ for grouping in groupings {
839
+ if !conf. conf . module_item_order_groupings . is_grouping ( grouping) {
840
+ // Since this isn't fixable by rustfix, don't emit a `Suggestion`. This just adds some useful
841
+ // info for the user instead.
842
+
843
+ let names = conf. conf . module_item_order_groupings . grouping_names ( ) ;
844
+ let suggestion = suggest_candidate ( grouping, names. iter ( ) . map ( String :: as_str) )
845
+ . map ( |s| format ! ( " perhaps you meant `{s}`?" ) )
846
+ . unwrap_or_default ( ) ;
847
+ let names = names. iter ( ) . map ( |s| format ! ( "`{s}`" ) ) . join ( ", " ) ;
848
+ let message = format ! (
849
+ "unknown ordering group: `{grouping}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}"
850
+ ) ;
851
+
852
+ let span = conf
853
+ . value_spans
854
+ . get ( "module_item_order_groupings" )
855
+ . cloned ( )
856
+ . unwrap_or_default ( ) ;
857
+ conf. errors . push ( ConfError :: spanned ( file, message, None , span) ) ;
858
+ }
859
+ }
860
+ }
861
+
818
862
// TODO: THIS SHOULD BE TESTED, this comment will be gone soon
819
863
if conf. conf . allowed_idents_below_min_chars . iter ( ) . any ( |e| e == ".." ) {
820
864
conf. conf
@@ -860,6 +904,7 @@ impl Conf {
860
904
861
905
let TryConf {
862
906
mut conf,
907
+ value_spans : _,
863
908
errors,
864
909
warnings,
865
910
} = match path {
@@ -950,17 +995,10 @@ impl serde::de::Error for FieldError {
950
995
}
951
996
}
952
997
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
- } ) ;
998
+ let suggestion = suggest_candidate ( field, expected) . map ( |suggestion| Suggestion {
999
+ message : "perhaps you meant" ,
1000
+ suggestion,
1001
+ } ) ;
964
1002
965
1003
Self { error : msg, suggestion }
966
1004
}
@@ -998,6 +1036,22 @@ fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
998
1036
( rows, column_widths)
999
1037
}
1000
1038
1039
+ /// Given a user-provided value that couldn't be matched to a known option, finds the most likely
1040
+ /// candidate among candidates that the user might have meant.
1041
+ fn suggest_candidate < ' a , I > ( value : & str , candidates : I ) -> Option < & ' a str >
1042
+ where
1043
+ I : IntoIterator < Item = & ' a str > ,
1044
+ {
1045
+ candidates
1046
+ . into_iter ( )
1047
+ . filter_map ( |expected| {
1048
+ let dist = edit_distance ( value, expected, 4 ) ?;
1049
+ Some ( ( dist, expected) )
1050
+ } )
1051
+ . min_by_key ( |& ( dist, _) | dist)
1052
+ . map ( |( _, suggestion) | suggestion)
1053
+ }
1054
+
1001
1055
#[ cfg( test) ]
1002
1056
mod tests {
1003
1057
use serde:: de:: IgnoredAny ;
0 commit comments