]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
lint all guard types, not just lock functions
[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     /// 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", 4096 => u64),
157 }
158
159 impl Default for Conf {
160     #[must_use]
161     fn default() -> Self {
162         toml::from_str("").expect("we never error on empty config files")
163     }
164 }
165
166 /// Search for the configuration file.
167 pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
168     /// Possible filename to search for.
169     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
170
171     // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
172     // If neither of those exist, use ".".
173     let mut current = path::PathBuf::from(
174         env::var("CLIPPY_CONF_DIR")
175             .or_else(|_| env::var("CARGO_MANIFEST_DIR"))
176             .unwrap_or_else(|_| ".".to_string()),
177     );
178     loop {
179         for config_file_name in &CONFIG_FILE_NAMES {
180             let config_file = current.join(config_file_name);
181             match fs::metadata(&config_file) {
182                 // Only return if it's a file to handle the unlikely situation of a directory named
183                 // `clippy.toml`.
184                 Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
185                 // Return the error if it's something other than `NotFound`; otherwise we didn't
186                 // find the project file yet, and continue searching.
187                 Err(e) => {
188                     if e.kind() != io::ErrorKind::NotFound {
189                         return Err(e);
190                     }
191                 },
192                 _ => (),
193             }
194         }
195
196         // If the current directory has no parent, we're done searching.
197         if !current.pop() {
198             return Ok(None);
199         }
200     }
201 }
202
203 /// Produces a `Conf` filled with the default values and forwards the errors
204 ///
205 /// Used internally for convenience
206 fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
207     (Conf::default(), errors)
208 }
209
210 /// Read the `toml` configuration file.
211 ///
212 /// In case of error, the function tries to continue as much as possible.
213 pub fn read(path: Option<&path::Path>) -> (Conf, Vec<Error>) {
214     let path = if let Some(path) = path {
215         path
216     } else {
217         return default(Vec::new());
218     };
219
220     let file = match fs::File::open(path) {
221         Ok(mut file) => {
222             let mut buf = String::new();
223
224             if let Err(err) = file.read_to_string(&mut buf) {
225                 return default(vec![err.into()]);
226             }
227
228             buf
229         },
230         Err(err) => return default(vec![err.into()]),
231     };
232
233     assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
234     match toml::from_str(&file) {
235         Ok(toml) => {
236             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
237
238             let toml_ref: &Conf = &toml;
239
240             let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;
241
242             if cyc_field.is_some() {
243                 let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
244                 errors.push(Error::Toml(cyc_err));
245             }
246
247             (toml, errors)
248         },
249         Err(e) => {
250             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
251             errors.push(Error::Toml(e.to_string()));
252
253             default(errors)
254         },
255     }
256 }