]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/utils/conf.rs
Merge commit '3ae8faff4d46ad92f194c2a4b941c3152a701b31' into clippyup
[rust.git] / src / tools / clippy / 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 /// Conf with parse errors
12 #[derive(Default)]
13 pub struct TryConf {
14     pub conf: Conf,
15     pub errors: Vec<String>,
16 }
17
18 impl TryConf {
19     fn from_error(error: impl Error) -> Self {
20         Self {
21             conf: Conf::default(),
22             errors: vec![error.to_string()],
23         }
24     }
25 }
26
27 macro_rules! define_Conf {
28     ($(
29         #[doc = $doc:literal]
30         $(#[conf_deprecated($dep:literal)])?
31         ($name:ident: $ty:ty = $default:expr),
32     )*) => {
33         /// Clippy lint configuration
34         pub struct Conf {
35             $(#[doc = $doc] pub $name: $ty,)*
36         }
37
38         mod defaults {
39             $(pub fn $name() -> $ty { $default })*
40         }
41
42         impl Default for Conf {
43             fn default() -> Self {
44                 Self { $($name: defaults::$name(),)* }
45             }
46         }
47
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             }
67
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                     }
87                 }
88                 let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
89                 Ok(TryConf { conf, errors })
90             }
91         }
92
93         #[cfg(feature = "metadata-collector-lint")]
94         pub mod metadata {
95             use crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
96
97             macro_rules! wrap_option {
98                 () => (None);
99                 ($x:literal) => (Some($x));
100             }
101
102             pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
103                 vec![
104                     $(
105                         {
106                             let deprecation_reason = wrap_option!($($dep)?);
107
108                             ClippyConfiguration::new(
109                                 stringify!($name),
110                                 stringify!($ty),
111                                 format!("{:?}", super::defaults::$name()),
112                                 $doc,
113                                 deprecation_reason,
114                             )
115                         },
116                     )+
117                 ]
118             }
119         }
120     };
121 }
122
123 // N.B., this macro is parsed by util/lintlib.py
124 define_Conf! {
125     /// 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.
126     (avoid_breaking_exported_api: bool = true),
127     /// 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
128     (msrv: Option<String> = None),
129     /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
130     (blacklisted_names: Vec<String> = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
131     /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
132     (cognitive_complexity_threshold: u64 = 25),
133     /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
134     #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")]
135     (cyclomatic_complexity_threshold: Option<u64> = None),
136     /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
137     (doc_valid_idents: Vec<String> = [
138         "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
139         "DirectX",
140         "ECMAScript",
141         "GPLv2", "GPLv3",
142         "GitHub", "GitLab",
143         "IPv4", "IPv6",
144         "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript",
145         "NaN", "NaNs",
146         "OAuth", "GraphQL",
147         "OCaml",
148         "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
149         "WebGL",
150         "TensorFlow",
151         "TrueType",
152         "iOS", "macOS",
153         "TeX", "LaTeX", "BibTeX", "BibLaTeX",
154         "MinGW",
155         "CamelCase",
156     ].iter().map(ToString::to_string).collect()),
157     /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
158     (too_many_arguments_threshold: u64 = 7),
159     /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
160     (type_complexity_threshold: u64 = 250),
161     /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
162     (single_char_binding_names_threshold: u64 = 4),
163     /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
164     (too_large_for_stack: u64 = 200),
165     /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
166     (enum_variant_name_threshold: u64 = 3),
167     /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
168     (enum_variant_size_threshold: u64 = 200),
169     /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
170     (verbose_bit_mask_threshold: u64 = 1),
171     /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
172     (literal_representation_threshold: u64 = 16384),
173     /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
174     (trivial_copy_size_limit: Option<u64> = None),
175     /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value.
176     (pass_by_value_size_limit: u64 = 256),
177     /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
178     (too_many_lines_threshold: u64 = 100),
179     /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack
180     (array_size_threshold: u64 = 512_000),
181     /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
182     (vec_box_size_threshold: u64 = 4096),
183     /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted
184     (max_trait_bounds: u64 = 3),
185     /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
186     (max_struct_bools: u64 = 3),
187     /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
188     (max_fn_params_bools: u64 = 3),
189     /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests).
190     (warn_on_all_wildcard_imports: bool = false),
191     /// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths.
192     (disallowed_methods: Vec<String> = Vec::new()),
193     /// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators.
194     (unreadable_literal_lint_fractions: bool = true),
195     /// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other
196     (upper_case_acronyms_aggressive: bool = false),
197     /// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest.
198     (cargo_ignore_publish: bool = false),
199 }
200
201 /// Search for the configuration file.
202 pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
203     /// Possible filename to search for.
204     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
205
206     // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
207     // If neither of those exist, use ".".
208     let mut current = env::var_os("CLIPPY_CONF_DIR")
209         .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
210         .map_or_else(|| PathBuf::from("."), PathBuf::from);
211     loop {
212         for config_file_name in &CONFIG_FILE_NAMES {
213             if let Ok(config_file) = current.join(config_file_name).canonicalize() {
214                 match fs::metadata(&config_file) {
215                     Err(e) if e.kind() == io::ErrorKind::NotFound => {},
216                     Err(e) => return Err(e),
217                     Ok(md) if md.is_dir() => {},
218                     Ok(_) => return Ok(Some(config_file)),
219                 }
220             }
221         }
222
223         // If the current directory has no parent, we're done searching.
224         if !current.pop() {
225             return Ok(None);
226         }
227     }
228 }
229
230 /// Read the `toml` configuration file.
231 ///
232 /// In case of error, the function tries to continue as much as possible.
233 pub fn read(path: &Path) -> TryConf {
234     let content = match fs::read_to_string(path) {
235         Err(e) => return TryConf::from_error(e),
236         Ok(content) => content,
237     };
238     toml::from_str(&content).unwrap_or_else(TryConf::from_error)
239 }