11//! Read configurations files.
22
3- #![ deny ( clippy:: missing_docs_in_private_items ) ]
3+ #![ allow ( clippy:: module_name_repetitions ) ]
44
5- use std:: lazy:: SyncLazy ;
5+ use serde:: de:: { Deserializer , IgnoredAny , IntoDeserializer , MapAccess , Visitor } ;
6+ use serde:: Deserialize ;
7+ use std:: error:: Error ;
68use std:: path:: { Path , PathBuf } ;
7- use std:: sync:: Mutex ;
89use std:: { env, fmt, fs, io} ;
910
10- /// Error from reading a configuration file.
11- #[ derive( Debug ) ]
12- pub enum Error {
13- /// An I/O error.
14- Io ( io:: Error ) ,
15- /// Not valid toml or doesn't fit the expected config format
16- Toml ( String ) ,
11+ /// Conf with parse errors
12+ #[ derive( Default ) ]
13+ pub struct TryConf {
14+ pub conf : Conf ,
15+ pub errors : Vec < String > ,
1716}
1817
19- impl fmt :: Display for Error {
20- fn fmt ( & self , f : & mut fmt :: Formatter < ' _ > ) -> fmt :: Result {
21- match self {
22- Self :: Io ( err ) => err . fmt ( f ) ,
23- Self :: Toml ( err ) => err . fmt ( f ) ,
18+ impl TryConf {
19+ fn from_error ( error : impl Error ) -> Self {
20+ Self {
21+ conf : Conf :: default ( ) ,
22+ errors : vec ! [ error . to_string ( ) ] ,
2423 }
2524 }
2625}
2726
28- impl From < io:: Error > for Error {
29- fn from ( e : io:: Error ) -> Self {
30- Self :: Io ( e)
31- }
32- }
27+ macro_rules! define_Conf {
28+ ( $(
29+ #[ $doc: meta]
30+ $( #[ conf_deprecated( $dep: literal) ] ) ?
31+ ( $name: ident: $ty: ty $( = $default: expr) ?) ,
32+ ) * ) => {
33+ /// Clippy lint configuration
34+ pub struct Conf {
35+ $( #[ $doc] pub $name: $ty, ) *
36+ }
3337
34- /// Vec of errors that might be collected during config toml parsing
35- static ERRORS : SyncLazy < Mutex < Vec < Error > > > = SyncLazy :: new ( || Mutex :: new ( Vec :: new ( ) ) ) ;
38+ mod defaults {
39+ $( pub fn $name( ) -> $ty { define_Conf!( @default $( $default) ?) } ) *
40+ }
3641
37- macro_rules! define_Conf {
38- ( $( #[ $doc: meta] ( $config: ident, $config_str: literal: $Ty: ty, $default: expr) , ) +) => {
39- mod helpers {
40- use serde:: Deserialize ;
41- /// Type used to store lint configuration.
42- #[ derive( Deserialize ) ]
43- #[ serde( rename_all = "kebab-case" , deny_unknown_fields) ]
44- pub struct Conf {
45- $(
46- #[ $doc]
47- #[ serde( default = $config_str) ]
48- #[ serde( with = $config_str) ]
49- pub $config: $Ty,
50- ) +
51- #[ allow( dead_code) ]
52- #[ serde( default ) ]
53- third_party: Option <:: toml:: Value >,
42+ impl Default for Conf {
43+ fn default ( ) -> Self {
44+ Self { $( $name: defaults:: $name( ) , ) * }
5445 }
46+ }
5547
56- $(
57- mod $config {
58- use serde:: Deserialize ;
59- pub fn deserialize<' de, D : serde:: Deserializer <' de>>( deserializer: D ) -> Result <$Ty, D :: Error > {
60- use super :: super :: { ERRORS , Error } ;
61-
62- Ok (
63- <$Ty>:: deserialize( deserializer) . unwrap_or_else( |e| {
64- ERRORS
65- . lock( )
66- . expect( "no threading here" )
67- . push( Error :: Toml ( e. to_string( ) ) ) ;
68- super :: $config( )
69- } )
70- )
71- }
72- }
48+ impl <' de> Deserialize <' de> for TryConf {
49+ fn deserialize<D >( deserializer: D ) -> Result <Self , D :: Error > where D : Deserializer <' de> {
50+ deserializer. deserialize_map( ConfVisitor )
51+ }
52+ }
53+
54+ #[ derive( Deserialize ) ]
55+ #[ serde( field_identifier, rename_all = "kebab-case" ) ]
56+ #[ allow( non_camel_case_types) ]
57+ enum Field { $( $name, ) * third_party, }
58+
59+ struct ConfVisitor ;
60+
61+ impl <' de> Visitor <' de> for ConfVisitor {
62+ type Value = TryConf ;
63+
64+ fn expecting( & self , formatter: & mut fmt:: Formatter <' _>) -> fmt:: Result {
65+ formatter. write_str( "Conf" )
66+ }
7367
74- #[ must_use]
75- fn $config( ) -> $Ty {
76- let x = $default;
77- x
68+ fn visit_map<V >( self , mut map: V ) -> Result <Self :: Value , V :: Error > where V : MapAccess <' de> {
69+ let mut errors = Vec :: new( ) ;
70+ $( let mut $name = None ; ) *
71+ // could get `Field` here directly, but get `str` first for diagnostics
72+ while let Some ( name) = map. next_key:: <& str >( ) ? {
73+ match Field :: deserialize( name. into_deserializer( ) ) ? {
74+ $( Field :: $name => {
75+ $( errors. push( format!( "deprecated field `{}`. {}" , name, $dep) ) ; ) ?
76+ match map. next_value( ) {
77+ Err ( e) => errors. push( e. to_string( ) ) ,
78+ Ok ( value) => match $name {
79+ Some ( _) => errors. push( format!( "duplicate field `{}`" , name) ) ,
80+ None => $name = Some ( value) ,
81+ }
82+ }
83+ } ) *
84+ // white-listed; ignore
85+ Field :: third_party => drop( map. next_value:: <IgnoredAny >( ) )
86+ }
7887 }
79- ) +
88+ let conf = Conf { $( $name: $name. unwrap_or_else( defaults:: $name) , ) * } ;
89+ Ok ( TryConf { conf, errors } )
90+ }
8091 }
8192 } ;
93+ ( @default ) => ( Default :: default ( ) ) ;
94+ ( @default $default: expr) => ( $default) ;
8295}
8396
84- pub use self :: helpers:: Conf ;
8597define_Conf ! {
8698 /// Lint: CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR. The minimum rust version that the project supports
87- ( msrv, "msrv" : Option <String >, None ) ,
99+ ( msrv: Option <String >) ,
88100 /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
89- ( blacklisted_names, "blacklisted_names" : Vec <String >, [ "foo" , "baz" , "quux" ] . iter( ) . map( ToString :: to_string) . collect( ) ) ,
101+ ( blacklisted_names: Vec <String > = [ "foo" , "baz" , "quux" ] . iter( ) . map( ToString :: to_string) . collect( ) ) ,
90102 /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
91- ( cognitive_complexity_threshold, "cognitive_complexity_threshold" : u64 , 25 ) ,
103+ ( cognitive_complexity_threshold: u64 = 25 ) ,
92104 /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
93- ( cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold" : Option <u64 >, None ) ,
105+ #[ conf_deprecated( "Please use `cognitive-complexity-threshold` instead." ) ]
106+ ( cyclomatic_complexity_threshold: Option <u64 >) ,
94107 /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
95- ( doc_valid_idents, "doc_valid_idents" : Vec <String >, [
108+ ( doc_valid_idents: Vec <String > = [
96109 "KiB" , "MiB" , "GiB" , "TiB" , "PiB" , "EiB" ,
97110 "DirectX" ,
98111 "ECMAScript" ,
@@ -113,54 +126,47 @@ define_Conf! {
113126 "CamelCase" ,
114127 ] . iter( ) . map( ToString :: to_string) . collect( ) ) ,
115128 /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
116- ( too_many_arguments_threshold, "too_many_arguments_threshold" : u64 , 7 ) ,
129+ ( too_many_arguments_threshold: u64 = 7 ) ,
117130 /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
118- ( type_complexity_threshold, "type_complexity_threshold" : u64 , 250 ) ,
131+ ( type_complexity_threshold: u64 = 250 ) ,
119132 /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
120- ( single_char_binding_names_threshold, "single_char_binding_names_threshold" : u64 , 4 ) ,
133+ ( single_char_binding_names_threshold: u64 = 4 ) ,
121134 /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
122- ( too_large_for_stack, "too_large_for_stack" : u64 , 200 ) ,
135+ ( too_large_for_stack: u64 = 200 ) ,
123136 /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
124- ( enum_variant_name_threshold, "enum_variant_name_threshold" : u64 , 3 ) ,
137+ ( enum_variant_name_threshold: u64 = 3 ) ,
125138 /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
126- ( enum_variant_size_threshold, "enum_variant_size_threshold" : u64 , 200 ) ,
139+ ( enum_variant_size_threshold: u64 = 200 ) ,
127140 /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
128- ( verbose_bit_mask_threshold, "verbose_bit_mask_threshold" : u64 , 1 ) ,
141+ ( verbose_bit_mask_threshold: u64 = 1 ) ,
129142 /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
130- ( literal_representation_threshold, "literal_representation_threshold" : u64 , 16384 ) ,
143+ ( literal_representation_threshold: u64 = 16384 ) ,
131144 /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
132- ( trivial_copy_size_limit, "trivial_copy_size_limit" : Option <u64 >, None ) ,
145+ ( trivial_copy_size_limit: Option <u64 >) ,
133146 /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value.
134- ( pass_by_value_size_limit, "pass_by_value_size_limit" : u64 , 256 ) ,
147+ ( pass_by_value_size_limit: u64 = 256 ) ,
135148 /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
136- ( too_many_lines_threshold, "too_many_lines_threshold" : u64 , 100 ) ,
149+ ( too_many_lines_threshold: u64 = 100 ) ,
137150 /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack
138- ( array_size_threshold, "array_size_threshold" : u64 , 512_000 ) ,
151+ ( array_size_threshold: u64 = 512_000 ) ,
139152 /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
140- ( vec_box_size_threshold, "vec_box_size_threshold" : u64 , 4096 ) ,
153+ ( vec_box_size_threshold: u64 = 4096 ) ,
141154 /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted
142- ( max_trait_bounds, "max_trait_bounds" : u64 , 3 ) ,
155+ ( max_trait_bounds: u64 = 3 ) ,
143156 /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
144- ( max_struct_bools, "max_struct_bools" : u64 , 3 ) ,
157+ ( max_struct_bools: u64 = 3 ) ,
145158 /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
146- ( max_fn_params_bools, "max_fn_params_bools" : u64 , 3 ) ,
159+ ( max_fn_params_bools: u64 = 3 ) ,
147160 /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests).
148- ( warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports" : bool , false ) ,
161+ ( warn_on_all_wildcard_imports: bool ) ,
149162 /// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths.
150- ( disallowed_methods, "disallowed_methods" : Vec <String >, Vec :: < String > :: new ( ) ) ,
163+ ( disallowed_methods: Vec <String >) ,
151164 /// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators.
152- ( unreadable_literal_lint_fractions, "unreadable_literal_lint_fractions" : bool , true ) ,
165+ ( unreadable_literal_lint_fractions: bool = true ) ,
153166 /// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other
154- ( upper_case_acronyms_aggressive, "upper_case_acronyms_aggressive" : bool , false ) ,
167+ ( upper_case_acronyms_aggressive: bool ) ,
155168 /// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest.
156- ( cargo_ignore_publish, "cargo_ignore_publish" : bool , false ) ,
157- }
158-
159- impl Default for Conf {
160- #[ must_use]
161- fn default ( ) -> Self {
162- toml:: from_str ( "" ) . expect ( "we never error on empty config files" )
163- }
169+ ( cargo_ignore_publish: bool ) ,
164170}
165171
166172/// Search for the configuration file.
@@ -194,43 +200,13 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
194200 }
195201}
196202
197- /// Produces a `Conf` filled with the default values and forwards the errors
198- ///
199- /// Used internally for convenience
200- fn default ( errors : Vec < Error > ) -> ( Conf , Vec < Error > ) {
201- ( Conf :: default ( ) , errors)
202- }
203-
204203/// Read the `toml` configuration file.
205204///
206205/// In case of error, the function tries to continue as much as possible.
207- pub fn read ( path : & Path ) -> ( Conf , Vec < Error > ) {
206+ pub fn read ( path : & Path ) -> TryConf {
208207 let content = match fs:: read_to_string ( path) {
208+ Err ( e) => return TryConf :: from_error ( e) ,
209209 Ok ( content) => content,
210- Err ( err) => return default ( vec ! [ err. into( ) ] ) ,
211210 } ;
212-
213- assert ! ( ERRORS . lock( ) . expect( "no threading -> mutex always safe" ) . is_empty( ) ) ;
214- match toml:: from_str ( & content) {
215- Ok ( toml) => {
216- let mut errors = ERRORS . lock ( ) . expect ( "no threading -> mutex always safe" ) . split_off ( 0 ) ;
217-
218- let toml_ref: & Conf = & toml;
219-
220- let cyc_field: Option < u64 > = toml_ref. cyclomatic_complexity_threshold ;
221-
222- if cyc_field. is_some ( ) {
223- let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead." . to_string ( ) ;
224- errors. push ( Error :: Toml ( cyc_err) ) ;
225- }
226-
227- ( toml, errors)
228- } ,
229- Err ( e) => {
230- let mut errors = ERRORS . lock ( ) . expect ( "no threading -> mutex always safe" ) . split_off ( 0 ) ;
231- errors. push ( Error :: Toml ( e. to_string ( ) ) ) ;
232-
233- default ( errors)
234- } ,
235- }
211+ toml:: from_str ( & content) . unwrap_or_else ( TryConf :: from_error)
236212}
0 commit comments