]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
Merge commit '984330a6ee3c4d15626685d6dc8b7b759ff630bd' into clippyup
[rust.git] / clippy_lints / src / utils / conf.rs
1 //! Read configurations files.
2
3 #![allow(clippy::module_name_repetitions)]
4
5 use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
6 use serde::Deserialize;
7 use std::error::Error;
8 use std::path::{Path, PathBuf};
9 use std::{env, fmt, fs, io};
10
11 /// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint.
12 #[derive(Clone, Debug, Deserialize)]
13 pub struct Rename {
14     pub path: String,
15     pub rename: String,
16 }
17
18 /// A single disallowed method, used by the `DISALLOWED_METHODS` lint.
19 #[derive(Clone, Debug, Deserialize)]
20 #[serde(untagged)]
21 pub enum DisallowedMethod {
22     Simple(String),
23     WithReason { path: String, reason: Option<String> },
24 }
25
26 impl DisallowedMethod {
27     pub fn path(&self) -> &str {
28         let (Self::Simple(path) | Self::WithReason { path, .. }) = self;
29
30         path
31     }
32 }
33
34 /// A single disallowed type, used by the `DISALLOWED_TYPES` lint.
35 #[derive(Clone, Debug, Deserialize)]
36 #[serde(untagged)]
37 pub enum DisallowedType {
38     Simple(String),
39     WithReason { path: String, reason: Option<String> },
40 }
41
42 /// Conf with parse errors
43 #[derive(Default)]
44 pub struct TryConf {
45     pub conf: Conf,
46     pub errors: Vec<String>,
47 }
48
49 impl TryConf {
50     fn from_error(error: impl Error) -> Self {
51         Self {
52             conf: Conf::default(),
53             errors: vec![error.to_string()],
54         }
55     }
56 }
57
58 macro_rules! define_Conf {
59     ($(
60         $(#[doc = $doc:literal])+
61         $(#[conf_deprecated($dep:literal)])?
62         ($name:ident: $ty:ty = $default:expr),
63     )*) => {
64         /// Clippy lint configuration
65         pub struct Conf {
66             $($(#[doc = $doc])+ pub $name: $ty,)*
67         }
68
69         mod defaults {
70             $(pub fn $name() -> $ty { $default })*
71         }
72
73         impl Default for Conf {
74             fn default() -> Self {
75                 Self { $($name: defaults::$name(),)* }
76             }
77         }
78
79         impl<'de> Deserialize<'de> for TryConf {
80             fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
81                 deserializer.deserialize_map(ConfVisitor)
82             }
83         }
84
85         #[derive(Deserialize)]
86         #[serde(field_identifier, rename_all = "kebab-case")]
87         #[allow(non_camel_case_types)]
88         enum Field { $($name,)* third_party, }
89
90         struct ConfVisitor;
91
92         impl<'de> Visitor<'de> for ConfVisitor {
93             type Value = TryConf;
94
95             fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96                 formatter.write_str("Conf")
97             }
98
99             fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
100                 let mut errors = Vec::new();
101                 $(let mut $name = None;)*
102                 // could get `Field` here directly, but get `str` first for diagnostics
103                 while let Some(name) = map.next_key::<&str>()? {
104                     match Field::deserialize(name.into_deserializer())? {
105                         $(Field::$name => {
106                             $(errors.push(format!("deprecated field `{}`. {}", name, $dep));)?
107                             match map.next_value() {
108                                 Err(e) => errors.push(e.to_string()),
109                                 Ok(value) => match $name {
110                                     Some(_) => errors.push(format!("duplicate field `{}`", name)),
111                                     None => $name = Some(value),
112                                 }
113                             }
114                         })*
115                         // white-listed; ignore
116                         Field::third_party => drop(map.next_value::<IgnoredAny>())
117                     }
118                 }
119                 let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
120                 Ok(TryConf { conf, errors })
121             }
122         }
123
124         #[cfg(feature = "internal")]
125         pub mod metadata {
126             use crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
127
128             macro_rules! wrap_option {
129                 () => (None);
130                 ($x:literal) => (Some($x));
131             }
132
133             pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
134                 vec![
135                     $(
136                         {
137                             let deprecation_reason = wrap_option!($($dep)?);
138
139                             ClippyConfiguration::new(
140                                 stringify!($name),
141                                 stringify!($ty),
142                                 format!("{:?}", super::defaults::$name()),
143                                 concat!($($doc, '\n',)*),
144                                 deprecation_reason,
145                             )
146                         },
147                     )+
148                 ]
149             }
150         }
151     };
152 }
153
154 define_Conf! {
155     /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX.
156     ///
157     /// Suppress lints whenever the suggested change would cause breakage for other crates.
158     (avoid_breaking_exported_api: bool = true),
159     /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, 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, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED.
160     ///
161     /// The minimum rust version that the project supports
162     (msrv: Option<String> = None),
163     /// Lint: BLACKLISTED_NAME.
164     ///
165     /// The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
166     (blacklisted_names: Vec<String> = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
167     /// Lint: COGNITIVE_COMPLEXITY.
168     ///
169     /// The maximum cognitive complexity a function can have
170     (cognitive_complexity_threshold: u64 = 25),
171     /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY.
172     ///
173     /// Use the Cognitive Complexity lint instead.
174     #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")]
175     (cyclomatic_complexity_threshold: Option<u64> = None),
176     /// Lint: DOC_MARKDOWN.
177     ///
178     /// The list of words this lint should not consider as identifiers needing ticks
179     (doc_valid_idents: Vec<String> = [
180         "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
181         "DirectX",
182         "ECMAScript",
183         "GPLv2", "GPLv3",
184         "GitHub", "GitLab",
185         "IPv4", "IPv6",
186         "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript",
187         "NaN", "NaNs",
188         "OAuth", "GraphQL",
189         "OCaml",
190         "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
191         "WebGL",
192         "TensorFlow",
193         "TrueType",
194         "iOS", "macOS", "FreeBSD",
195         "TeX", "LaTeX", "BibTeX", "BibLaTeX",
196         "MinGW",
197         "CamelCase",
198     ].iter().map(ToString::to_string).collect()),
199     /// Lint: TOO_MANY_ARGUMENTS.
200     ///
201     /// The maximum number of argument a function or method can have
202     (too_many_arguments_threshold: u64 = 7),
203     /// Lint: TYPE_COMPLEXITY.
204     ///
205     /// The maximum complexity a type can have
206     (type_complexity_threshold: u64 = 250),
207     /// Lint: MANY_SINGLE_CHAR_NAMES.
208     ///
209     /// The maximum number of single char bindings a scope may have
210     (single_char_binding_names_threshold: u64 = 4),
211     /// Lint: BOXED_LOCAL, USELESS_VEC.
212     ///
213     /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
214     (too_large_for_stack: u64 = 200),
215     /// Lint: ENUM_VARIANT_NAMES.
216     ///
217     /// The minimum number of enum variants for the lints about variant names to trigger
218     (enum_variant_name_threshold: u64 = 3),
219     /// Lint: LARGE_ENUM_VARIANT.
220     ///
221     /// The maximum size of an enum's variant to avoid box suggestion
222     (enum_variant_size_threshold: u64 = 200),
223     /// Lint: VERBOSE_BIT_MASK.
224     ///
225     /// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
226     (verbose_bit_mask_threshold: u64 = 1),
227     /// Lint: DECIMAL_LITERAL_REPRESENTATION.
228     ///
229     /// The lower bound for linting decimal literals
230     (literal_representation_threshold: u64 = 16384),
231     /// Lint: TRIVIALLY_COPY_PASS_BY_REF.
232     ///
233     /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
234     (trivial_copy_size_limit: Option<u64> = None),
235     /// Lint: LARGE_TYPE_PASS_BY_MOVE.
236     ///
237     /// The minimum size (in bytes) to consider a type for passing by reference instead of by value.
238     (pass_by_value_size_limit: u64 = 256),
239     /// Lint: TOO_MANY_LINES.
240     ///
241     /// The maximum number of lines a function or method can have
242     (too_many_lines_threshold: u64 = 100),
243     /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS.
244     ///
245     /// The maximum allowed size for arrays on the stack
246     (array_size_threshold: u64 = 512_000),
247     /// Lint: VEC_BOX.
248     ///
249     /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed
250     (vec_box_size_threshold: u64 = 4096),
251     /// Lint: TYPE_REPETITION_IN_BOUNDS.
252     ///
253     /// The maximum number of bounds a trait can have to be linted
254     (max_trait_bounds: u64 = 3),
255     /// Lint: STRUCT_EXCESSIVE_BOOLS.
256     ///
257     /// The maximum number of bool fields a struct can have
258     (max_struct_bools: u64 = 3),
259     /// Lint: FN_PARAMS_EXCESSIVE_BOOLS.
260     ///
261     /// The maximum number of bool parameters a function can have
262     (max_fn_params_bools: u64 = 3),
263     /// Lint: WILDCARD_IMPORTS.
264     ///
265     /// Whether to allow certain wildcard imports (prelude, super in tests).
266     (warn_on_all_wildcard_imports: bool = false),
267     /// Lint: DISALLOWED_METHODS.
268     ///
269     /// The list of disallowed methods, written as fully qualified paths.
270     (disallowed_methods: Vec<crate::utils::conf::DisallowedMethod> = Vec::new()),
271     /// Lint: DISALLOWED_TYPES.
272     ///
273     /// The list of disallowed types, written as fully qualified paths.
274     (disallowed_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
275     /// Lint: UNREADABLE_LITERAL.
276     ///
277     /// Should the fraction of a decimal be linted to include separators.
278     (unreadable_literal_lint_fractions: bool = true),
279     /// Lint: UPPER_CASE_ACRONYMS.
280     ///
281     /// Enables verbose mode. Triggers if there is more than one uppercase char next to each other
282     (upper_case_acronyms_aggressive: bool = false),
283     /// Lint: _CARGO_COMMON_METADATA.
284     ///
285     /// For internal testing only, ignores the current `publish` settings in the Cargo manifest.
286     (cargo_ignore_publish: bool = false),
287     /// Lint: NONSTANDARD_MACRO_BRACES.
288     ///
289     /// Enforce the named macros always use the braces specified.
290     ///
291     /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
292     /// is could be used with a full path two `MacroMatcher`s have to be added one with the full path
293     /// `crate_name::macro_name` and one with just the macro name.
294     (standard_macro_braces: Vec<crate::nonstandard_macro_braces::MacroMatcher> = Vec::new()),
295     /// Lint: MISSING_ENFORCED_IMPORT_RENAMES.
296     ///
297     /// The list of imports to always rename, a fully qualified path followed by the rename.
298     (enforced_import_renames: Vec<crate::utils::conf::Rename> = Vec::new()),
299     /// Lint: DISALLOWED_SCRIPT_IDENTS.
300     ///
301     /// The list of unicode scripts allowed to be used in the scope.
302     (allowed_scripts: Vec<String> = ["Latin"].iter().map(ToString::to_string).collect()),
303     /// Lint: NON_SEND_FIELDS_IN_SEND_TY.
304     ///
305     /// Whether to apply the raw pointer heuristic to determine if a type is `Send`.
306     (enable_raw_pointer_heuristic_for_send: bool = true),
307     /// Lint: INDEX_REFUTABLE_SLICE.
308     ///
309     /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
310     /// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed.
311     /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
312     (max_suggested_slice_pattern_length: u64 = 3),
313 }
314
315 /// Search for the configuration file.
316 pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
317     /// Possible filename to search for.
318     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
319
320     // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
321     // If neither of those exist, use ".".
322     let mut current = env::var_os("CLIPPY_CONF_DIR")
323         .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
324         .map_or_else(|| PathBuf::from("."), PathBuf::from);
325
326     let mut found_config: Option<PathBuf> = None;
327
328     loop {
329         for config_file_name in &CONFIG_FILE_NAMES {
330             if let Ok(config_file) = current.join(config_file_name).canonicalize() {
331                 match fs::metadata(&config_file) {
332                     Err(e) if e.kind() == io::ErrorKind::NotFound => {},
333                     Err(e) => return Err(e),
334                     Ok(md) if md.is_dir() => {},
335                     Ok(_) => {
336                         // warn if we happen to find two config files #8323
337                         if let Some(ref found_config_) = found_config {
338                             eprintln!(
339                                 "Using config file `{}`\nWarning: `{}` will be ignored.",
340                                 found_config_.display(),
341                                 config_file.display(),
342                             );
343                         } else {
344                             found_config = Some(config_file);
345                         }
346                     },
347                 }
348             }
349         }
350
351         if found_config.is_some() {
352             return Ok(found_config);
353         }
354
355         // If the current directory has no parent, we're done searching.
356         if !current.pop() {
357             return Ok(None);
358         }
359     }
360 }
361
362 /// Read the `toml` configuration file.
363 ///
364 /// In case of error, the function tries to continue as much as possible.
365 pub fn read(path: &Path) -> TryConf {
366     let content = match fs::read_to_string(path) {
367         Err(e) => return TryConf::from_error(e),
368         Ok(content) => content,
369     };
370     toml::from_str(&content).unwrap_or_else(TryConf::from_error)
371 }