]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
1c71a76f159054ac7145aef1385be95ff0080d63
[rust.git] / clippy_lints / src / utils / conf.rs
1 //! Read configurations files.
2
3 #![deny(clippy::missing_docs_in_private_items)]
4
5 use crate::utils::sym;
6 use lazy_static::lazy_static;
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, source_map};
12 use toml;
13
14 /// Gets the configuration file from arguments.
15 pub fn file_from_args(args: &[ast::NestedMetaItem]) -> Result<Option<path::PathBuf>, (&'static str, source_map::Span)> {
16     for arg in args.iter().filter_map(syntax::ast::NestedMetaItem::meta_item) {
17         if arg.check_name(*sym::conf_file) {
18             return match arg.node {
19                 ast::MetaItemKind::Word | ast::MetaItemKind::List(_) => {
20                     Err(("`conf_file` must be a named value", arg.span))
21                 },
22                 ast::MetaItemKind::NameValue(ref value) => {
23                     if let ast::LitKind::Str(ref file, _) = value.node {
24                         Ok(Some(file.to_string().into()))
25                     } else {
26                         Err(("`conf_file` value must be a string", value.span))
27                     }
28                 },
29             };
30         }
31     }
32
33     Ok(None)
34 }
35
36 /// Error from reading a configuration file.
37 #[derive(Debug)]
38 pub enum Error {
39     /// An I/O error.
40     Io(io::Error),
41     /// Not valid toml or doesn't fit the expected conf format
42     Toml(String),
43 }
44
45 impl fmt::Display for Error {
46     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
47         match *self {
48             Error::Io(ref err) => err.fmt(f),
49             Error::Toml(ref err) => err.fmt(f),
50         }
51     }
52 }
53
54 impl From<io::Error> for Error {
55     fn from(e: io::Error) -> Self {
56         Error::Io(e)
57     }
58 }
59
60 lazy_static! {
61     static ref ERRORS: Mutex<Vec<Error>> = Mutex::new(Vec::new());
62 }
63
64 macro_rules! define_Conf {
65     ($(#[$doc: meta] ($rust_name: ident, $rust_name_str: expr, $default: expr => $($ty: tt)+),)+) => {
66         pub use self::helpers::Conf;
67         mod helpers {
68             use serde::Deserialize;
69             /// Type used to store lint configuration.
70             #[derive(Deserialize)]
71             #[serde(rename_all="kebab-case", deny_unknown_fields)]
72             pub struct Conf {
73                 $(#[$doc] #[serde(default=$rust_name_str)] #[serde(with=$rust_name_str)]
74                           pub $rust_name: define_Conf!(TY $($ty)+),)+
75                 #[allow(dead_code)]
76                 #[serde(default)]
77                 third_party: Option<::toml::Value>,
78             }
79             $(
80                 mod $rust_name {
81                     use serde;
82                     use serde::Deserialize;
83                     crate fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D)
84                     -> Result<define_Conf!(TY $($ty)+), D::Error> {
85                         type T = define_Conf!(TY $($ty)+);
86                         Ok(T::deserialize(deserializer).unwrap_or_else(|e| {
87                             crate::utils::conf::ERRORS.lock().expect("no threading here")
88                                                         .push(crate::utils::conf::Error::Toml(e.to_string()));
89                             super::$rust_name()
90                         }))
91                     }
92                 }
93
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     fn default() -> Self {
158         toml::from_str("").expect("we never error on empty config files")
159     }
160 }
161
162 /// Search for the configuration file.
163 pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
164     /// Possible filename to search for.
165     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
166
167     // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
168     // If neither of those exist, use ".".
169     let mut current = path::PathBuf::from(
170         env::var("CLIPPY_CONF_DIR")
171             .or_else(|_| env::var("CARGO_MANIFEST_DIR"))
172             .unwrap_or_else(|_| ".".to_string()),
173     );
174     loop {
175         for config_file_name in &CONFIG_FILE_NAMES {
176             let config_file = current.join(config_file_name);
177             match fs::metadata(&config_file) {
178                 // Only return if it's a file to handle the unlikely situation of a directory named
179                 // `clippy.toml`.
180                 Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
181                 // Return the error if it's something other than `NotFound`; otherwise we didn't
182                 // find the project file yet, and continue searching.
183                 Err(e) => {
184                     if e.kind() != io::ErrorKind::NotFound {
185                         return Err(e);
186                     }
187                 },
188                 _ => (),
189             }
190         }
191
192         // If the current directory has no parent, we're done searching.
193         if !current.pop() {
194             return Ok(None);
195         }
196     }
197 }
198
199 /// Produces a `Conf` filled with the default values and forwards the errors
200 ///
201 /// Used internally for convenience
202 fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
203     (Conf::default(), errors)
204 }
205
206 /// Read the `toml` configuration file.
207 ///
208 /// In case of error, the function tries to continue as much as possible.
209 pub fn read(path: Option<&path::Path>) -> (Conf, Vec<Error>) {
210     let path = if let Some(path) = path {
211         path
212     } else {
213         return default(Vec::new());
214     };
215
216     let file = match fs::File::open(path) {
217         Ok(mut file) => {
218             let mut buf = String::new();
219
220             if let Err(err) = file.read_to_string(&mut buf) {
221                 return default(vec![err.into()]);
222             }
223
224             buf
225         },
226         Err(err) => return default(vec![err.into()]),
227     };
228
229     assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
230     match toml::from_str(&file) {
231         Ok(toml) => {
232             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
233
234             let toml_ref: &Conf = &toml;
235
236             let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;
237
238             if cyc_field.is_some() {
239                 let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
240                 errors.push(Error::Toml(cyc_err));
241             }
242
243             (toml, errors)
244         },
245         Err(e) => {
246             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
247             errors.push(Error::Toml(e.to_string()));
248
249             default(errors)
250         },
251     }
252 }