@@ -3,14 +3,17 @@ use crate::types::{
33 DisallowedPath , DisallowedPathWithoutReplacement , MacroMatcher , MatchLintBehaviour , PubUnderscoreFieldsBehaviour ,
44 Rename , SourceItemOrdering , SourceItemOrderingCategory , SourceItemOrderingModuleItemGroupings ,
55 SourceItemOrderingModuleItemKind , SourceItemOrderingTraitAssocItemKind , SourceItemOrderingTraitAssocItemKinds ,
6+ SourceItemOrderingWithinModuleItemGroupings ,
67} ;
78use clippy_utils:: msrvs:: Msrv ;
9+ use itertools:: Itertools ;
810use rustc_errors:: Applicability ;
911use rustc_session:: Session ;
1012use rustc_span:: edit_distance:: edit_distance;
1113use rustc_span:: { BytePos , Pos , SourceFile , Span , SyntaxContext } ;
1214use serde:: de:: { IgnoredAny , IntoDeserializer , MapAccess , Visitor } ;
1315use serde:: { Deserialize , Deserializer , Serialize } ;
16+ use std:: collections:: HashMap ;
1417use std:: fmt:: { Debug , Display , Formatter } ;
1518use std:: ops:: Range ;
1619use std:: path:: PathBuf ;
@@ -79,6 +82,7 @@ const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
7982#[ derive( Default ) ]
8083struct TryConf {
8184 conf : Conf ,
85+ value_spans : HashMap < String , Range < usize > > ,
8286 errors : Vec < ConfError > ,
8387 warnings : Vec < ConfError > ,
8488}
@@ -87,6 +91,7 @@ impl TryConf {
8791 fn from_toml_error ( file : & SourceFile , error : & toml:: de:: Error ) -> Self {
8892 Self {
8993 conf : Conf :: default ( ) ,
94+ value_spans : HashMap :: default ( ) ,
9095 errors : vec ! [ ConfError :: from_toml( file, error) ] ,
9196 warnings : vec ! [ ] ,
9297 }
@@ -210,6 +215,7 @@ macro_rules! define_Conf {
210215 }
211216
212217 fn visit_map<V >( self , mut map: V ) -> Result <Self :: Value , V :: Error > where V : MapAccess <' de> {
218+ let mut value_spans = HashMap :: new( ) ;
213219 let mut errors = Vec :: new( ) ;
214220 let mut warnings = Vec :: new( ) ;
215221 $( let mut $name = None ; ) *
@@ -232,6 +238,7 @@ macro_rules! define_Conf {
232238 }
233239 None => {
234240 $name = Some ( value) ;
241+ value_spans. insert( name. get_ref( ) . as_str( ) . to_string( ) , value_span) ;
235242 // $new_conf is the same as one of the defined `$name`s, so
236243 // this variable is defined in line 2 of this function.
237244 $( match $new_conf {
@@ -250,7 +257,7 @@ macro_rules! define_Conf {
250257 }
251258 }
252259 let conf = Conf { $( $name: $name. unwrap_or_else( defaults:: $name) , ) * } ;
253- Ok ( TryConf { conf, errors, warnings } )
260+ Ok ( TryConf { conf, value_spans , errors, warnings } )
254261 }
255262 }
256263
@@ -596,6 +603,13 @@ define_Conf! {
596603 /// The named groupings of different source item kinds within modules.
597604 #[ lints( arbitrary_source_item_ordering) ]
598605 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 ,
599613 /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
600614 #[ default_text = "current version" ]
601615 #[ lints(
@@ -815,6 +829,36 @@ fn deserialize(file: &SourceFile) -> TryConf {
815829 & mut conf. conf . allow_renamed_params_for ,
816830 DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS ,
817831 ) ;
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+
818862 // TODO: THIS SHOULD BE TESTED, this comment will be gone soon
819863 if conf. conf . allowed_idents_below_min_chars . iter ( ) . any ( |e| e == ".." ) {
820864 conf. conf
@@ -860,6 +904,7 @@ impl Conf {
860904
861905 let TryConf {
862906 mut conf,
907+ value_spans : _,
863908 errors,
864909 warnings,
865910 } = match path {
@@ -950,17 +995,10 @@ impl serde::de::Error for FieldError {
950995 }
951996 }
952997
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+ } ) ;
9641002
9651003 Self { error : msg, suggestion }
9661004 }
@@ -998,6 +1036,22 @@ fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
9981036 ( rows, column_widths)
9991037}
10001038
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+
10011055#[ cfg( test) ]
10021056mod tests {
10031057 use serde:: de:: IgnoredAny ;
0 commit comments