]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
Auto merge of #7407 - m-ou-se:doc-hidden-variants, r=flip1995
[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 /// Conf with parse errors
19 #[derive(Default)]
20 pub struct TryConf {
21     pub conf: Conf,
22     pub errors: Vec<String>,
23 }
24
25 impl TryConf {
26     fn from_error(error: impl Error) -> Self {
27         Self {
28             conf: Conf::default(),
29             errors: vec![error.to_string()],
30         }
31     }
32 }
33
34 /// Note that the configuration parsing currently doesn't support documentation that will
35 /// that spans over several lines. This will be possible with the new implementation
36 /// See (rust-clippy#7172)
37 macro_rules! define_Conf {
38     ($(
39         #[doc = $doc:literal]
40         $(#[conf_deprecated($dep:literal)])?
41         ($name:ident: $ty:ty = $default:expr),
42     )*) => {
43         /// Clippy lint configuration
44         pub struct Conf {
45             $(#[doc = $doc] pub $name: $ty,)*
46         }
47
48         mod defaults {
49             $(pub fn $name() -> $ty { $default })*
50         }
51
52         impl Default for Conf {
53             fn default() -> Self {
54                 Self { $($name: defaults::$name(),)* }
55             }
56         }
57
58         impl<'de> Deserialize<'de> for TryConf {
59             fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
60                 deserializer.deserialize_map(ConfVisitor)
61             }
62         }
63
64         #[derive(Deserialize)]
65         #[serde(field_identifier, rename_all = "kebab-case")]
66         #[allow(non_camel_case_types)]
67         enum Field { $($name,)* third_party, }
68
69         struct ConfVisitor;
70
71         impl<'de> Visitor<'de> for ConfVisitor {
72             type Value = TryConf;
73
74             fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
75                 formatter.write_str("Conf")
76             }
77
78             fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
79                 let mut errors = Vec::new();
80                 $(let mut $name = None;)*
81                 // could get `Field` here directly, but get `str` first for diagnostics
82                 while let Some(name) = map.next_key::<&str>()? {
83                     match Field::deserialize(name.into_deserializer())? {
84                         $(Field::$name => {
85                             $(errors.push(format!("deprecated field `{}`. {}", name, $dep));)?
86                             match map.next_value() {
87                                 Err(e) => errors.push(e.to_string()),
88                                 Ok(value) => match $name {
89                                     Some(_) => errors.push(format!("duplicate field `{}`", name)),
90                                     None => $name = Some(value),
91                                 }
92                             }
93                         })*
94                         // white-listed; ignore
95                         Field::third_party => drop(map.next_value::<IgnoredAny>())
96                     }
97                 }
98                 let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
99                 Ok(TryConf { conf, errors })
100             }
101         }
102
103         #[cfg(feature = "metadata-collector-lint")]
104         pub mod metadata {
105             use crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
106
107             macro_rules! wrap_option {
108                 () => (None);
109                 ($x:literal) => (Some($x));
110             }
111
112             pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
113                 vec![
114                     $(
115                         {
116                             let deprecation_reason = wrap_option!($($dep)?);
117
118                             ClippyConfiguration::new(
119                                 stringify!($name),
120                                 stringify!($ty),
121                                 format!("{:?}", super::defaults::$name()),
122                                 $doc,
123                                 deprecation_reason,
124                             )
125                         },
126                     )+
127                 ]
128             }
129         }
130     };
131 }
132
133 // N.B., this macro is parsed by util/lintlib.py
134 define_Conf! {
135     /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION. Suppress lints whenever the suggested change would cause breakage for other crates.
136     (avoid_breaking_exported_api: bool = true),
137     /// Lint: 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. The minimum rust version that the project supports
138     (msrv: Option<String> = None),
139     /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
140     (blacklisted_names: Vec<String> = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
141     /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
142     (cognitive_complexity_threshold: u64 = 25),
143     /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
144     #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")]
145     (cyclomatic_complexity_threshold: Option<u64> = None),
146     /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
147     (doc_valid_idents: Vec<String> = [
148         "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
149         "DirectX",
150         "ECMAScript",
151         "GPLv2", "GPLv3",
152         "GitHub", "GitLab",
153         "IPv4", "IPv6",
154         "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript",
155         "NaN", "NaNs",
156         "OAuth", "GraphQL",
157         "OCaml",
158         "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
159         "WebGL",
160         "TensorFlow",
161         "TrueType",
162         "iOS", "macOS", "FreeBSD",
163         "TeX", "LaTeX", "BibTeX", "BibLaTeX",
164         "MinGW",
165         "CamelCase",
166     ].iter().map(ToString::to_string).collect()),
167     /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
168     (too_many_arguments_threshold: u64 = 7),
169     /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
170     (type_complexity_threshold: u64 = 250),
171     /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
172     (single_char_binding_names_threshold: u64 = 4),
173     /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
174     (too_large_for_stack: u64 = 200),
175     /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
176     (enum_variant_name_threshold: u64 = 3),
177     /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
178     (enum_variant_size_threshold: u64 = 200),
179     /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
180     (verbose_bit_mask_threshold: u64 = 1),
181     /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
182     (literal_representation_threshold: u64 = 16384),
183     /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
184     (trivial_copy_size_limit: Option<u64> = None),
185     /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value.
186     (pass_by_value_size_limit: u64 = 256),
187     /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
188     (too_many_lines_threshold: u64 = 100),
189     /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack
190     (array_size_threshold: u64 = 512_000),
191     /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
192     (vec_box_size_threshold: u64 = 4096),
193     /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted
194     (max_trait_bounds: u64 = 3),
195     /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bool fields a struct can have
196     (max_struct_bools: u64 = 3),
197     /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bool parameters a function can have
198     (max_fn_params_bools: u64 = 3),
199     /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests).
200     (warn_on_all_wildcard_imports: bool = false),
201     /// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths.
202     (disallowed_methods: Vec<String> = Vec::new()),
203     /// Lint: DISALLOWED_TYPE. The list of disallowed types, written as fully qualified paths.
204     (disallowed_types: Vec<String> = Vec::new()),
205     /// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators.
206     (unreadable_literal_lint_fractions: bool = true),
207     /// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other
208     (upper_case_acronyms_aggressive: bool = false),
209     /// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest.
210     (cargo_ignore_publish: bool = false),
211     /// Lint: NONSTANDARD_MACRO_BRACES. Enforce the named macros always use the braces specified. <br> A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro is could be used with a full path two `MacroMatcher`s have to be added one with the full path `crate_name::macro_name` and one with just the macro name.
212     (standard_macro_braces: Vec<crate::nonstandard_macro_braces::MacroMatcher> = Vec::new()),
213     /// Lint: MISSING_ENFORCED_IMPORT_RENAMES. The list of imports to always rename, a fully qualified path followed by the rename.
214     (enforced_import_renames: Vec<crate::utils::conf::Rename> = Vec::new()),
215     /// Lint: RESTRICTED_SCRIPTS. The list of unicode scripts allowed to be used in the scope.
216     (allowed_scripts: Vec<String> = vec!["Latin".to_string()]),
217 }
218
219 /// Search for the configuration file.
220 pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
221     /// Possible filename to search for.
222     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
223
224     // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
225     // If neither of those exist, use ".".
226     let mut current = env::var_os("CLIPPY_CONF_DIR")
227         .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
228         .map_or_else(|| PathBuf::from("."), PathBuf::from);
229     loop {
230         for config_file_name in &CONFIG_FILE_NAMES {
231             if let Ok(config_file) = current.join(config_file_name).canonicalize() {
232                 match fs::metadata(&config_file) {
233                     Err(e) if e.kind() == io::ErrorKind::NotFound => {},
234                     Err(e) => return Err(e),
235                     Ok(md) if md.is_dir() => {},
236                     Ok(_) => return Ok(Some(config_file)),
237                 }
238             }
239         }
240
241         // If the current directory has no parent, we're done searching.
242         if !current.pop() {
243             return Ok(None);
244         }
245     }
246 }
247
248 /// Read the `toml` configuration file.
249 ///
250 /// In case of error, the function tries to continue as much as possible.
251 pub fn read(path: &Path) -> TryConf {
252     let content = match fs::read_to_string(path) {
253         Err(e) => return TryConf::from_error(e),
254         Ok(content) => content,
255     };
256     toml::from_str(&content).unwrap_or_else(TryConf::from_error)
257 }