]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
Merge commit 'b40ea209e7f14c8193ddfc98143967b6a2f4f5c9' into clippyup
[rust.git] / clippy_lints / src / utils / conf.rs
1 //! Read configurations files.
2
3 #![deny(clippy::missing_docs_in_private_items)]
4
5 use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem};
6 use rustc_span::source_map;
7 use source_map::Span;
8 use std::lazy::SyncLazy;
9 use std::path::{Path, PathBuf};
10 use std::sync::Mutex;
11 use std::{env, fmt, fs, io};
12
13 /// Gets the configuration file from arguments.
14 pub fn file_from_args(args: &[NestedMetaItem]) -> Result<Option<PathBuf>, (&'static str, Span)> {
15     for arg in args.iter().filter_map(NestedMetaItem::meta_item) {
16         if arg.has_name(sym!(conf_file)) {
17             return match arg.kind {
18                 MetaItemKind::Word | MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)),
19                 MetaItemKind::NameValue(ref value) => {
20                     if let LitKind::Str(ref file, _) = value.kind {
21                         Ok(Some(file.to_string().into()))
22                     } else {
23                         Err(("`conf_file` value must be a string", value.span))
24                     }
25                 },
26             };
27         }
28     }
29
30     Ok(None)
31 }
32
33 /// Error from reading a configuration file.
34 #[derive(Debug)]
35 pub enum Error {
36     /// An I/O error.
37     Io(io::Error),
38     /// Not valid toml or doesn't fit the expected config format
39     Toml(String),
40 }
41
42 impl fmt::Display for Error {
43     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44         match self {
45             Self::Io(err) => err.fmt(f),
46             Self::Toml(err) => err.fmt(f),
47         }
48     }
49 }
50
51 impl From<io::Error> for Error {
52     fn from(e: io::Error) -> Self {
53         Self::Io(e)
54     }
55 }
56
57 /// Vec of errors that might be collected during config toml parsing
58 static ERRORS: SyncLazy<Mutex<Vec<Error>>> = SyncLazy::new(|| Mutex::new(Vec::new()));
59
60 macro_rules! define_Conf {
61     ($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => {
62         mod helpers {
63             use serde::Deserialize;
64             /// Type used to store lint configuration.
65             #[derive(Deserialize)]
66             #[serde(rename_all = "kebab-case", deny_unknown_fields)]
67             pub struct Conf {
68                 $(
69                     #[$doc]
70                     #[serde(default = $config_str)]
71                     #[serde(with = $config_str)]
72                     pub $config: $Ty,
73                 )+
74                 #[allow(dead_code)]
75                 #[serde(default)]
76                 third_party: Option<::toml::Value>,
77             }
78
79             $(
80                 mod $config {
81                     use serde::Deserialize;
82                     pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> {
83                         use super::super::{ERRORS, Error};
84
85                         Ok(
86                             <$Ty>::deserialize(deserializer).unwrap_or_else(|e| {
87                                 ERRORS
88                                     .lock()
89                                     .expect("no threading here")
90                                     .push(Error::Toml(e.to_string()));
91                                 super::$config()
92                             })
93                         )
94                     }
95                 }
96
97                 #[must_use]
98                 fn $config() -> $Ty {
99                     let x = $default;
100                     x
101                 }
102             )+
103         }
104     };
105 }
106
107 pub use self::helpers::Conf;
108 define_Conf! {
109     /// Lint: 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, NEEDLESS_QUESTION_MARK, PTR_AS_PTR. The minimum rust version that the project supports
110     (msrv, "msrv": Option<String>, None),
111     /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
112     (blacklisted_names, "blacklisted_names": Vec<String>, ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
113     /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
114     (cognitive_complexity_threshold, "cognitive_complexity_threshold": u64, 25),
115     /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
116     (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold": Option<u64>, None),
117     /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
118     (doc_valid_idents, "doc_valid_idents": Vec<String>, [
119         "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
120         "DirectX",
121         "ECMAScript",
122         "GPLv2", "GPLv3",
123         "GitHub", "GitLab",
124         "IPv4", "IPv6",
125         "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript",
126         "NaN", "NaNs",
127         "OAuth", "GraphQL",
128         "OCaml",
129         "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
130         "WebGL",
131         "TensorFlow",
132         "TrueType",
133         "iOS", "macOS",
134         "TeX", "LaTeX", "BibTeX", "BibLaTeX",
135         "MinGW",
136         "CamelCase",
137     ].iter().map(ToString::to_string).collect()),
138     /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
139     (too_many_arguments_threshold, "too_many_arguments_threshold": u64, 7),
140     /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
141     (type_complexity_threshold, "type_complexity_threshold": u64, 250),
142     /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
143     (single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4),
144     /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
145     (too_large_for_stack, "too_large_for_stack": u64, 200),
146     /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
147     (enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3),
148     /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
149     (enum_variant_size_threshold, "enum_variant_size_threshold": u64, 200),
150     /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
151     (verbose_bit_mask_threshold, "verbose_bit_mask_threshold": u64, 1),
152     /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
153     (literal_representation_threshold, "literal_representation_threshold": u64, 16384),
154     /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
155     (trivial_copy_size_limit, "trivial_copy_size_limit": Option<u64>, None),
156     /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value.
157     (pass_by_value_size_limit, "pass_by_value_size_limit": u64, 256),
158     /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
159     (too_many_lines_threshold, "too_many_lines_threshold": u64, 100),
160     /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack
161     (array_size_threshold, "array_size_threshold": u64, 512_000),
162     /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
163     (vec_box_size_threshold, "vec_box_size_threshold": u64, 4096),
164     /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted
165     (max_trait_bounds, "max_trait_bounds": u64, 3),
166     /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
167     (max_struct_bools, "max_struct_bools": u64, 3),
168     /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
169     (max_fn_params_bools, "max_fn_params_bools": u64, 3),
170     /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests).
171     (warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports": bool, false),
172     /// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths.
173     (disallowed_methods, "disallowed_methods": Vec<String>, Vec::<String>::new()),
174     /// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators.
175     (unreadable_literal_lint_fractions, "unreadable_literal_lint_fractions": bool, true),
176     /// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other
177     (upper_case_acronyms_aggressive, "upper_case_acronyms_aggressive": bool, false),
178     /// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest.
179     (cargo_ignore_publish, "cargo_ignore_publish": bool, false),
180 }
181
182 impl Default for Conf {
183     #[must_use]
184     fn default() -> Self {
185         toml::from_str("").expect("we never error on empty config files")
186     }
187 }
188
189 /// Search for the configuration file.
190 pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
191     /// Possible filename to search for.
192     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
193
194     // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
195     // If neither of those exist, use ".".
196     let mut current = env::var_os("CLIPPY_CONF_DIR")
197         .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
198         .map_or_else(|| PathBuf::from("."), PathBuf::from);
199     loop {
200         for config_file_name in &CONFIG_FILE_NAMES {
201             let config_file = current.join(config_file_name);
202             match fs::metadata(&config_file) {
203                 // Only return if it's a file to handle the unlikely situation of a directory named
204                 // `clippy.toml`.
205                 Ok(ref md) if !md.is_dir() => return Ok(Some(config_file)),
206                 // Return the error if it's something other than `NotFound`; otherwise we didn't
207                 // find the project file yet, and continue searching.
208                 Err(e) if e.kind() != io::ErrorKind::NotFound => return Err(e),
209                 _ => {},
210             }
211         }
212
213         // If the current directory has no parent, we're done searching.
214         if !current.pop() {
215             return Ok(None);
216         }
217     }
218 }
219
220 /// Produces a `Conf` filled with the default values and forwards the errors
221 ///
222 /// Used internally for convenience
223 fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
224     (Conf::default(), errors)
225 }
226
227 /// Read the `toml` configuration file.
228 ///
229 /// In case of error, the function tries to continue as much as possible.
230 pub fn read(path: &Path) -> (Conf, Vec<Error>) {
231     let content = match fs::read_to_string(path) {
232         Ok(content) => content,
233         Err(err) => return default(vec![err.into()]),
234     };
235
236     assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
237     match toml::from_str(&content) {
238         Ok(toml) => {
239             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
240
241             let toml_ref: &Conf = &toml;
242
243             let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;
244
245             if cyc_field.is_some() {
246                 let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
247                 errors.push(Error::Toml(cyc_err));
248             }
249
250             (toml, errors)
251         },
252         Err(e) => {
253             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
254             errors.push(Error::Toml(e.to_string()));
255
256             default(errors)
257         },
258     }
259 }