]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
Configuration option for VERBOSE_BIT_MASK threshold
[rust.git] / clippy_lints / src / utils / conf.rs
1 //! Read configurations files.
2
3 #![deny(missing_docs_in_private_items)]
4
5 use std::{env, fmt, fs, io, path};
6 use std::io::Read;
7 use syntax::{ast, codemap};
8 use toml;
9 use std::sync::Mutex;
10
11 /// Get the configuration file from arguments.
12 pub fn file_from_args(
13     args: &[codemap::Spanned<ast::NestedMetaItemKind>],
14 ) -> Result<Option<path::PathBuf>, (&'static str, codemap::Span)> {
15     for arg in args.iter().filter_map(|a| a.meta_item()) {
16         if arg.name() == "conf_file" {
17             return match arg.node {
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) => if let ast::LitKind::Str(ref file, _) = value.node {
22                     Ok(Some(file.to_string().into()))
23                 } else {
24                     Err(("`conf_file` value must be a string", value.span))
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 conf format
39     Toml(String),
40     /// Type error.
41     Type(
42         /// The name of the key.
43         &'static str,
44         /// The expected type.
45         &'static str,
46         /// The type we got instead.
47         &'static str,
48     ),
49     /// There is an unknown key is the file.
50     UnknownKey(String),
51 }
52
53 impl fmt::Display for Error {
54     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
55         match *self {
56             Error::Io(ref err) => err.fmt(f),
57             Error::Toml(ref err) => err.fmt(f),
58             Error::Type(key, expected, got) => {
59                 write!(f, "`{}` is expected to be a `{}` but is a `{}`", key, expected, got)
60             },
61             Error::UnknownKey(ref key) => write!(f, "unknown key `{}`", key),
62         }
63     }
64 }
65
66 impl From<io::Error> for Error {
67     fn from(e: io::Error) -> Self {
68         Error::Io(e)
69     }
70 }
71
72 lazy_static! {
73     static ref ERRORS: Mutex<Vec<Error>> = Mutex::new(Vec::new());
74 }
75
76 macro_rules! define_Conf {
77     ($(#[$doc: meta] ($rust_name: ident, $rust_name_str: expr, $default: expr => $($ty: tt)+),)+) => {
78         pub use self::helpers::Conf;
79         mod helpers {
80             /// Type used to store lint configuration.
81             #[derive(Deserialize)]
82             #[serde(rename_all="kebab-case")]
83             #[serde(deny_unknown_fields)]
84             pub struct Conf {
85                 $(#[$doc] #[serde(default=$rust_name_str)] #[serde(with=$rust_name_str)] pub $rust_name: define_Conf!(TY $($ty)+),)+
86                 #[allow(dead_code)]
87                 #[serde(default)]
88                 third_party: Option<::toml::Value>,
89             }
90             $(
91                 mod $rust_name {
92                     use serde;
93                     use serde::Deserialize;
94                     pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<define_Conf!(TY $($ty)+), D::Error> {
95                         type T = define_Conf!(TY $($ty)+);
96                         Ok(T::deserialize(deserializer).unwrap_or_else(|e| {
97                             ::utils::conf::ERRORS.lock().expect("no threading here").push(::utils::conf::Error::Toml(e.to_string()));
98                             super::$rust_name()
99                         }))
100                     }
101                 }
102
103                 fn $rust_name() -> define_Conf!(TY $($ty)+) {
104                     define_Conf!(DEFAULT $($ty)+, $default)
105                 }
106             )+
107         }
108     };
109
110     // hack to convert tts
111     (TY $ty: ty) => { $ty };
112
113     // how to read the value?
114     (CONV i64, $value: expr) => { $value.as_integer() };
115     (CONV u64, $value: expr) => {
116         $value.as_integer()
117         .iter()
118         .filter_map(|&i| if i >= 0 { Some(i as u64) } else { None })
119         .next()
120     };
121     (CONV String, $value: expr) => { $value.as_str().map(Into::into) };
122     (CONV Vec<String>, $value: expr) => {{
123         let slice = $value.as_array();
124
125         if let Some(slice) = slice {
126             if slice.iter().any(|v| v.as_str().is_none()) {
127                 None
128             } else {
129                 Some(slice.iter().map(|v| v.as_str().expect("already checked").to_owned()).collect())
130             }
131         } else {
132             None
133         }
134     }};
135
136     // provide a nicer syntax to declare the default value of `Vec<String>` variables
137     (DEFAULT Vec<String>, $e: expr) => { $e.iter().map(|&e| e.to_owned()).collect() };
138     (DEFAULT $ty: ty, $e: expr) => { $e };
139 }
140
141 define_Conf! {
142     /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about
143     (blacklisted_names, "blacklisted_names", ["foo", "bar", "baz", "quux"] => Vec<String>),
144     /// Lint: CYCLOMATIC_COMPLEXITY. The maximum cyclomatic complexity a function can have
145     (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold", 25 => u64),
146     /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
147     (doc_valid_idents, "doc_valid_idents", [
148         "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
149         "DirectX",
150         "ECMAScript",
151         "GPLv2", "GPLv3",
152         "GitHub",
153         "IPv4", "IPv6",
154         "JavaScript",
155         "NaN",
156         "OAuth",
157         "OpenGL", "OpenSSH", "OpenSSL",
158         "TrueType",
159         "iOS", "macOS",
160         "TeX", "LaTeX", "BibTeX", "BibLaTeX",
161         "MinGW",
162     ] => Vec<String>),
163     /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
164     (too_many_arguments_threshold, "too_many_arguments_threshold", 7 => u64),
165     /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
166     (type_complexity_threshold, "type_complexity_threshold", 250 => u64),
167     /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
168     (single_char_binding_names_threshold, "single_char_binding_names_threshold", 5 => u64),
169     /// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
170     (too_large_for_stack, "too_large_for_stack", 200 => u64),
171     /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
172     (enum_variant_name_threshold, "enum_variant_name_threshold", 3 => u64),
173     /// Lint: LARGE_ENUM_VARIANT. The maximum size of a emum's variant to avoid box suggestion
174     (enum_variant_size_threshold, "enum_variant_size_threshold", 200 => u64),
175     /// Lint: VERBOSE_BIT_MASK. The maximum size of a bit mask, that won't be checked on verbosity
176     (verbose_bit_mask_threshold, "verbose_bit_mask_threshold", 1 => u64),
177 }
178
179 /// Search for the configuration file.
180 pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
181     /// Possible filename to search for.
182     const CONFIG_FILE_NAMES: [&'static str; 2] = [".clippy.toml", "clippy.toml"];
183
184     let mut current = try!(env::current_dir());
185
186     loop {
187         for config_file_name in &CONFIG_FILE_NAMES {
188             let config_file = current.join(config_file_name);
189             match fs::metadata(&config_file) {
190                 // Only return if it's a file to handle the unlikely situation of a directory named
191                 // `clippy.toml`.
192                 Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
193                 // Return the error if it's something other than `NotFound`; otherwise we didn't
194                 // find the project file yet, and continue searching.
195                 Err(e) => if e.kind() != io::ErrorKind::NotFound {
196                     return Err(e);
197                 },
198                 _ => (),
199             }
200         }
201
202         // If the current directory has no parent, we're done searching.
203         if !current.pop() {
204             return Ok(None);
205         }
206     }
207 }
208
209 /// Produces a `Conf` filled with the default values and forwards the errors
210 ///
211 /// Used internally for convenience
212 fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
213     (toml::from_str("").expect("we never error on empty config files"), errors)
214 }
215
216 /// Read the `toml` configuration file.
217 ///
218 /// In case of error, the function tries to continue as much as possible.
219 pub fn read(path: Option<&path::Path>) -> (Conf, Vec<Error>) {
220     let path = if let Some(path) = path {
221         path
222     } else {
223         return default(Vec::new());
224     };
225
226     let file = match fs::File::open(path) {
227         Ok(mut file) => {
228             let mut buf = String::new();
229
230             if let Err(err) = file.read_to_string(&mut buf) {
231                 return default(vec![err.into()]);
232             }
233
234             buf
235         },
236         Err(err) => return default(vec![err.into()]),
237     };
238
239     assert!(
240         ERRORS
241             .lock()
242             .expect("no threading -> mutex always safe")
243             .is_empty()
244     );
245     match toml::from_str(&file) {
246         Ok(toml) => (
247             toml,
248             ERRORS
249                 .lock()
250                 .expect("no threading -> mutex always safe")
251                 .split_off(0),
252         ),
253         Err(e) => {
254             let mut errors = ERRORS
255                 .lock()
256                 .expect("no threading -> mutex always safe")
257                 .split_off(0);
258             errors.push(Error::Toml(e.to_string()));
259             default(errors)
260         },
261     }
262 }