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