]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
Auto merge of #4992 - phansch:rustup_foobar, r=matthiaskrgr
[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_span::source_map;
7 use std::default::Default;
8 use std::io::Read;
9 use std::sync::Mutex;
10 use std::{env, fmt, fs, io, path};
11 use syntax::ast;
12
13 /// Gets the configuration file from arguments.
14 pub fn file_from_args(args: &[ast::NestedMetaItem]) -> Result<Option<path::PathBuf>, (&'static str, source_map::Span)> {
15     for arg in args.iter().filter_map(syntax::ast::NestedMetaItem::meta_item) {
16         if arg.check_name(sym!(conf_file)) {
17             return match arg.kind {
18                 ast::MetaItemKind::Word | ast::MetaItemKind::List(_) => {
19                     Err(("`conf_file` must be a named value", arg.span))
20                 },
21                 ast::MetaItemKind::NameValue(ref value) => {
22                     if let ast::LitKind::Str(ref file, _) = value.kind {
23                         Ok(Some(file.to_string().into()))
24                     } else {
25                         Err(("`conf_file` value must be a string", value.span))
26                     }
27                 },
28             };
29         }
30     }
31
32     Ok(None)
33 }
34
35 /// Error from reading a configuration file.
36 #[derive(Debug)]
37 pub enum Error {
38     /// An I/O error.
39     Io(io::Error),
40     /// Not valid toml or doesn't fit the expected conf format
41     Toml(String),
42 }
43
44 impl fmt::Display for Error {
45     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
46         match *self {
47             Self::Io(ref err) => err.fmt(f),
48             Self::Toml(ref err) => err.fmt(f),
49         }
50     }
51 }
52
53 impl From<io::Error> for Error {
54     fn from(e: io::Error) -> Self {
55         Self::Io(e)
56     }
57 }
58
59 lazy_static! {
60     static ref ERRORS: Mutex<Vec<Error>> = Mutex::new(Vec::new());
61 }
62
63 macro_rules! define_Conf {
64     ($(#[$doc: meta] ($rust_name: ident, $rust_name_str: expr, $default: expr => $($ty: tt)+),)+) => {
65         pub use self::helpers::Conf;
66         mod helpers {
67             use serde::Deserialize;
68             /// Type used to store lint configuration.
69             #[derive(Deserialize)]
70             #[serde(rename_all="kebab-case", deny_unknown_fields)]
71             pub struct Conf {
72                 $(#[$doc] #[serde(default=$rust_name_str)] #[serde(with=$rust_name_str)]
73                           pub $rust_name: define_Conf!(TY $($ty)+),)+
74                 #[allow(dead_code)]
75                 #[serde(default)]
76                 third_party: Option<::toml::Value>,
77             }
78             $(
79                 mod $rust_name {
80                     use serde::Deserialize;
81                     crate fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D)
82                     -> Result<define_Conf!(TY $($ty)+), D::Error> {
83                         type T = define_Conf!(TY $($ty)+);
84                         Ok(T::deserialize(deserializer).unwrap_or_else(|e| {
85                             crate::utils::conf::ERRORS.lock().expect("no threading here")
86                                                         .push(crate::utils::conf::Error::Toml(e.to_string()));
87                             super::$rust_name()
88                         }))
89                     }
90                 }
91
92                 #[must_use]
93                 fn $rust_name() -> define_Conf!(TY $($ty)+) {
94                     define_Conf!(DEFAULT $($ty)+, $default)
95                 }
96             )+
97         }
98     };
99
100     // hack to convert tts
101     (TY $ty: ty) => { $ty };
102
103     // provide a nicer syntax to declare the default value of `Vec<String>` variables
104     (DEFAULT Vec<String>, $e: expr) => { $e.iter().map(|&e| e.to_owned()).collect() };
105     (DEFAULT $ty: ty, $e: expr) => { $e };
106 }
107
108 define_Conf! {
109     /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about
110     (blacklisted_names, "blacklisted_names", ["foo", "bar", "baz", "quux"] => Vec<String>),
111     /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
112     (cognitive_complexity_threshold, "cognitive_complexity_threshold", 25 => u64),
113     /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
114     (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold", None => Option<u64>),
115     /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
116     (doc_valid_idents, "doc_valid_idents", [
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     ] => Vec<String>),
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", 7 => u64),
135     /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
136     (type_complexity_threshold, "type_complexity_threshold", 250 => u64),
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", 5 => u64),
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", 200 => u64),
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", 3 => u64),
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", 200 => u64),
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", 1 => u64),
147     /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
148     (literal_representation_threshold, "literal_representation_threshold", 16384 => u64),
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", None => Option<u64>),
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", 100 => u64),
153     /// Lint: LARGE_STACK_ARRAYS. The maximum allowed size for arrays on the stack
154     (array_size_threshold, "array_size_threshold", 512_000 => u64),
155 }
156
157 impl Default for Conf {
158     #[must_use]
159     fn default() -> Self {
160         toml::from_str("").expect("we never error on empty config files")
161     }
162 }
163
164 /// Search for the configuration file.
165 pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
166     /// Possible filename to search for.
167     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
168
169     // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
170     // If neither of those exist, use ".".
171     let mut current = path::PathBuf::from(
172         env::var("CLIPPY_CONF_DIR")
173             .or_else(|_| env::var("CARGO_MANIFEST_DIR"))
174             .unwrap_or_else(|_| ".".to_string()),
175     );
176     loop {
177         for config_file_name in &CONFIG_FILE_NAMES {
178             let config_file = current.join(config_file_name);
179             match fs::metadata(&config_file) {
180                 // Only return if it's a file to handle the unlikely situation of a directory named
181                 // `clippy.toml`.
182                 Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
183                 // Return the error if it's something other than `NotFound`; otherwise we didn't
184                 // find the project file yet, and continue searching.
185                 Err(e) => {
186                     if e.kind() != io::ErrorKind::NotFound {
187                         return Err(e);
188                     }
189                 },
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: Option<&path::Path>) -> (Conf, Vec<Error>) {
212     let path = if let Some(path) = path {
213         path
214     } else {
215         return default(Vec::new());
216     };
217
218     let file = match fs::File::open(path) {
219         Ok(mut file) => {
220             let mut buf = String::new();
221
222             if let Err(err) = file.read_to_string(&mut buf) {
223                 return default(vec![err.into()]);
224             }
225
226             buf
227         },
228         Err(err) => return default(vec![err.into()]),
229     };
230
231     assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
232     match toml::from_str(&file) {
233         Ok(toml) => {
234             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
235
236             let toml_ref: &Conf = &toml;
237
238             let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;
239
240             if cyc_field.is_some() {
241                 let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
242                 errors.push(Error::Toml(cyc_err));
243             }
244
245             (toml, errors)
246         },
247         Err(e) => {
248             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
249             errors.push(Error::Toml(e.to_string()));
250
251             default(errors)
252         },
253     }
254 }