]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
a1d7bebc7b5654801c487989c1152757cf6eaeae
[rust.git] / clippy_lints / src / utils / conf.rs
1 //! Read configurations files.
2
3 #![deny(clippy::missing_docs_in_private_items)]
4
5 use lazy_static::lazy_static;
6 use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem};
7 use rustc_span::source_map;
8 use source_map::Span;
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.check_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 lazy_static! {
58     static ref ERRORS: Mutex<Vec<Error>> = Mutex::new(Vec::new());
59 }
60
61 macro_rules! define_Conf {
62     ($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => {
63         mod helpers {
64             use serde::Deserialize;
65             /// Type used to store lint configuration.
66             #[derive(Deserialize)]
67             #[serde(rename_all = "kebab-case", deny_unknown_fields)]
68             pub struct Conf {
69                 $(
70                     #[$doc]
71                     #[serde(default = $config_str)]
72                     #[serde(with = $config_str)]
73                     pub $config: $Ty,
74                 )+
75                 #[allow(dead_code)]
76                 #[serde(default)]
77                 third_party: Option<::toml::Value>,
78             }
79
80             $(
81                 mod $config {
82                     use serde::Deserialize;
83                     crate fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> {
84                         use super::super::{ERRORS, Error};
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: BLACKLISTED_NAME. The list of blacklisted names to lint about
110     (blacklisted_names, "blacklisted_names": Vec<String>, ["foo", "bar", "baz", "quux"].iter().map(ToString::to_string).collect()),
111     /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
112     (cognitive_complexity_threshold, "cognitive_complexity_threshold": u64, 25),
113     /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
114     (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold": Option<u64>, None),
115     /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
116     (doc_valid_idents, "doc_valid_idents": Vec<String>, [
117         "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
118         "DirectX",
119         "ECMAScript",
120         "GPLv2", "GPLv3",
121         "GitHub", "GitLab",
122         "IPv4", "IPv6",
123         "JavaScript",
124         "NaN", "NaNs",
125         "OAuth",
126         "OpenGL", "OpenSSH", "OpenSSL", "OpenStreetMap",
127         "TrueType",
128         "iOS", "macOS",
129         "TeX", "LaTeX", "BibTeX", "BibLaTeX",
130         "MinGW",
131         "CamelCase",
132     ].iter().map(ToString::to_string).collect()),
133     /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
134     (too_many_arguments_threshold, "too_many_arguments_threshold": u64, 7),
135     /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
136     (type_complexity_threshold, "type_complexity_threshold": u64, 250),
137     /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
138     (single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 5),
139     /// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
140     (too_large_for_stack, "too_large_for_stack": u64, 200),
141     /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
142     (enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3),
143     /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
144     (enum_variant_size_threshold, "enum_variant_size_threshold": u64, 200),
145     /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
146     (verbose_bit_mask_threshold, "verbose_bit_mask_threshold": u64, 1),
147     /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
148     (literal_representation_threshold, "literal_representation_threshold": u64, 16384),
149     /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
150     (trivial_copy_size_limit, "trivial_copy_size_limit": Option<u64>, None),
151     /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
152     (too_many_lines_threshold, "too_many_lines_threshold": u64, 100),
153     /// Lint: LARGE_STACK_ARRAYS. The maximum allowed size for arrays on the stack
154     (array_size_threshold, "array_size_threshold": u64, 512_000),
155     /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
156     (vec_box_size_threshold, "vec_box_size_threshold": u64, 4096),
157     /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
158     (max_struct_bools, "max_struct_bools": u64, 3),
159     /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
160     (max_fn_params_bools, "max_fn_params_bools": u64, 3),
161 }
162
163 impl Default for Conf {
164     #[must_use]
165     fn default() -> Self {
166         toml::from_str("").expect("we never error on empty config files")
167     }
168 }
169
170 /// Search for the configuration file.
171 pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
172     /// Possible filename to search for.
173     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
174
175     // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
176     // If neither of those exist, use ".".
177     let mut current = env::var_os("CLIPPY_CONF_DIR")
178         .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
179         .map_or_else(|| PathBuf::from("."), PathBuf::from);
180     loop {
181         for config_file_name in &CONFIG_FILE_NAMES {
182             let config_file = current.join(config_file_name);
183             match fs::metadata(&config_file) {
184                 // Only return if it's a file to handle the unlikely situation of a directory named
185                 // `clippy.toml`.
186                 Ok(ref md) if !md.is_dir() => return Ok(Some(config_file)),
187                 // Return the error if it's something other than `NotFound`; otherwise we didn't
188                 // find the project file yet, and continue searching.
189                 Err(e) if e.kind() != io::ErrorKind::NotFound => return Err(e),
190                 _ => {},
191             }
192         }
193
194         // If the current directory has no parent, we're done searching.
195         if !current.pop() {
196             return Ok(None);
197         }
198     }
199 }
200
201 /// Produces a `Conf` filled with the default values and forwards the errors
202 ///
203 /// Used internally for convenience
204 fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
205     (Conf::default(), errors)
206 }
207
208 /// Read the `toml` configuration file.
209 ///
210 /// In case of error, the function tries to continue as much as possible.
211 pub fn read(path: &Path) -> (Conf, Vec<Error>) {
212     let content = match fs::read_to_string(path) {
213         Ok(content) => content,
214         Err(err) => return default(vec![err.into()]),
215     };
216
217     assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
218     match toml::from_str(&content) {
219         Ok(toml) => {
220             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
221
222             let toml_ref: &Conf = &toml;
223
224             let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;
225
226             if cyc_field.is_some() {
227                 let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
228                 errors.push(Error::Toml(cyc_err));
229             }
230
231             (toml, errors)
232         },
233         Err(e) => {
234             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
235             errors.push(Error::Toml(e.to_string()));
236
237             default(errors)
238         },
239     }
240 }