]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
Merge branch 'master' into fix-4727
[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 std::default::Default;
7 use std::io::Read;
8 use std::sync::Mutex;
9 use std::{env, fmt, fs, io, path};
10 use syntax::{ast, source_map};
11 use toml;
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;
81                     use serde::Deserialize;
82                     crate fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D)
83                     -> Result<define_Conf!(TY $($ty)+), D::Error> {
84                         type T = define_Conf!(TY $($ty)+);
85                         Ok(T::deserialize(deserializer).unwrap_or_else(|e| {
86                             crate::utils::conf::ERRORS.lock().expect("no threading here")
87                                                         .push(crate::utils::conf::Error::Toml(e.to_string()));
88                             super::$rust_name()
89                         }))
90                     }
91                 }
92
93                 #[must_use]
94                 fn $rust_name() -> define_Conf!(TY $($ty)+) {
95                     define_Conf!(DEFAULT $($ty)+, $default)
96                 }
97             )+
98         }
99     };
100
101     // hack to convert tts
102     (TY $ty: ty) => { $ty };
103
104     // provide a nicer syntax to declare the default value of `Vec<String>` variables
105     (DEFAULT Vec<String>, $e: expr) => { $e.iter().map(|&e| e.to_owned()).collect() };
106     (DEFAULT $ty: ty, $e: expr) => { $e };
107 }
108
109 define_Conf! {
110     /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about
111     (blacklisted_names, "blacklisted_names", ["foo", "bar", "baz", "quux"] => Vec<String>),
112     /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
113     (cognitive_complexity_threshold, "cognitive_complexity_threshold", 25 => u64),
114     /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
115     (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold", None => Option<u64>),
116     /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
117     (doc_valid_idents, "doc_valid_idents", [
118         "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
119         "DirectX",
120         "ECMAScript",
121         "GPLv2", "GPLv3",
122         "GitHub", "GitLab",
123         "IPv4", "IPv6",
124         "JavaScript",
125         "NaN", "NaNs",
126         "OAuth",
127         "OpenGL", "OpenSSH", "OpenSSL", "OpenStreetMap",
128         "TrueType",
129         "iOS", "macOS",
130         "TeX", "LaTeX", "BibTeX", "BibLaTeX",
131         "MinGW",
132         "CamelCase",
133     ] => Vec<String>),
134     /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
135     (too_many_arguments_threshold, "too_many_arguments_threshold", 7 => u64),
136     /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
137     (type_complexity_threshold, "type_complexity_threshold", 250 => u64),
138     /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
139     (single_char_binding_names_threshold, "single_char_binding_names_threshold", 5 => u64),
140     /// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
141     (too_large_for_stack, "too_large_for_stack", 200 => u64),
142     /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
143     (enum_variant_name_threshold, "enum_variant_name_threshold", 3 => u64),
144     /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
145     (enum_variant_size_threshold, "enum_variant_size_threshold", 200 => u64),
146     /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
147     (verbose_bit_mask_threshold, "verbose_bit_mask_threshold", 1 => u64),
148     /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
149     (literal_representation_threshold, "literal_representation_threshold", 16384 => u64),
150     /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
151     (trivial_copy_size_limit, "trivial_copy_size_limit", None => Option<u64>),
152     /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
153     (too_many_lines_threshold, "too_many_lines_threshold", 100 => u64),
154 }
155
156 impl Default for Conf {
157     #[must_use]
158     fn default() -> Self {
159         toml::from_str("").expect("we never error on empty config files")
160     }
161 }
162
163 /// Search for the configuration file.
164 pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
165     /// Possible filename to search for.
166     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
167
168     // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
169     // If neither of those exist, use ".".
170     let mut current = path::PathBuf::from(
171         env::var("CLIPPY_CONF_DIR")
172             .or_else(|_| env::var("CARGO_MANIFEST_DIR"))
173             .unwrap_or_else(|_| ".".to_string()),
174     );
175     loop {
176         for config_file_name in &CONFIG_FILE_NAMES {
177             let config_file = current.join(config_file_name);
178             match fs::metadata(&config_file) {
179                 // Only return if it's a file to handle the unlikely situation of a directory named
180                 // `clippy.toml`.
181                 Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
182                 // Return the error if it's something other than `NotFound`; otherwise we didn't
183                 // find the project file yet, and continue searching.
184                 Err(e) => {
185                     if e.kind() != io::ErrorKind::NotFound {
186                         return Err(e);
187                     }
188                 },
189                 _ => (),
190             }
191         }
192
193         // If the current directory has no parent, we're done searching.
194         if !current.pop() {
195             return Ok(None);
196         }
197     }
198 }
199
200 /// Produces a `Conf` filled with the default values and forwards the errors
201 ///
202 /// Used internally for convenience
203 fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
204     (Conf::default(), errors)
205 }
206
207 /// Read the `toml` configuration file.
208 ///
209 /// In case of error, the function tries to continue as much as possible.
210 pub fn read(path: Option<&path::Path>) -> (Conf, Vec<Error>) {
211     let path = if let Some(path) = path {
212         path
213     } else {
214         return default(Vec::new());
215     };
216
217     let file = match fs::File::open(path) {
218         Ok(mut file) => {
219             let mut buf = String::new();
220
221             if let Err(err) = file.read_to_string(&mut buf) {
222                 return default(vec![err.into()]);
223             }
224
225             buf
226         },
227         Err(err) => return default(vec![err.into()]),
228     };
229
230     assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
231     match toml::from_str(&file) {
232         Ok(toml) => {
233             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
234
235             let toml_ref: &Conf = &toml;
236
237             let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;
238
239             if cyc_field.is_some() {
240                 let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
241                 errors.push(Error::Toml(cyc_err));
242             }
243
244             (toml, errors)
245         },
246         Err(e) => {
247             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
248             errors.push(Error::Toml(e.to_string()));
249
250             default(errors)
251         },
252     }
253 }