]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
983a0ae44bf953aae657d018d1b82c30a831cd68
[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
10 /// Get the configuration file from arguments.
11 pub fn file_from_args(args: &[codemap::Spanned<ast::NestedMetaItemKind>])
12     -> Result<Option<path::PathBuf>, (&'static str, codemap::Span)> {
13     for arg in args.iter().filter_map(|a| a.meta_item()) {
14         if arg.name() == "conf_file" {
15             return match arg.node {
16                 ast::MetaItemKind::Word |
17                 ast::MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)),
18                 ast::MetaItemKind::NameValue(ref value) => {
19                     if let ast::LitKind::Str(ref file, _) = value.node {
20                         Ok(Some(file.to_string().into()))
21                     } else {
22                         Err(("`conf_file` value must be a string", value.span))
23                     }
24                 },
25             };
26         }
27     }
28
29     Ok(None)
30 }
31
32 /// Error from reading a configuration file.
33 #[derive(Debug)]
34 pub enum Error {
35     /// An I/O error.
36     Io(io::Error),
37     /// The file is not valid TOML.
38     Toml(Vec<toml::ParserError>),
39     /// Type error.
40     Type(/// The name of the key.
41          &'static str,
42          /// The expected type.
43          &'static str,
44          /// The type we got instead.
45          &'static str),
46     /// There is an unknown key is the file.
47     UnknownKey(String),
48 }
49
50 impl fmt::Display for Error {
51     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
52         match *self {
53             Error::Io(ref err) => err.fmt(f),
54             Error::Toml(ref errs) => {
55                 let mut first = true;
56                 for err in errs {
57                     if !first {
58                         try!(", ".fmt(f));
59                         first = false;
60                     }
61
62                     try!(err.fmt(f));
63                 }
64
65                 Ok(())
66             },
67             Error::Type(key, expected, got) => {
68                 write!(f, "`{}` is expected to be a `{}` but is a `{}`", key, expected, got)
69             },
70             Error::UnknownKey(ref key) => write!(f, "unknown key `{}`", key),
71         }
72     }
73 }
74
75 impl From<io::Error> for Error {
76     fn from(e: io::Error) -> Self {
77         Error::Io(e)
78     }
79 }
80
81 macro_rules! define_Conf {
82     ($(#[$doc: meta] ($toml_name: tt, $rust_name: ident, $default: expr => $($ty: tt)+),)+) => {
83         /// Type used to store lint configuration.
84         pub struct Conf {
85             $(#[$doc] pub $rust_name: define_Conf!(TY $($ty)+),)+
86         }
87
88         impl Default for Conf {
89             fn default() -> Conf {
90                 Conf {
91                     $($rust_name: define_Conf!(DEFAULT $($ty)+, $default),)+
92                 }
93             }
94         }
95
96         impl Conf {
97             /// Set the property `name` (which must be the `toml` name) to the given value
98             #[allow(cast_sign_loss)]
99             fn set(&mut self, name: String, value: toml::Value) -> Result<(), Error> {
100                 match name.as_str() {
101                     $(
102                         define_Conf!(PAT $toml_name) => {
103                             if let Some(value) = define_Conf!(CONV $($ty)+, value) {
104                                 self.$rust_name = value;
105                             }
106                             else {
107                                 return Err(Error::Type(define_Conf!(EXPR $toml_name),
108                                                        stringify!($($ty)+),
109                                                        value.type_str()));
110                             }
111                         },
112                     )+
113                     "third-party" => {
114                         // for external tools such as clippy-service
115                         return Ok(());
116                     }
117                     _ => {
118                         return Err(Error::UnknownKey(name));
119                     }
120                 }
121
122                 Ok(())
123             }
124         }
125     };
126
127     // hack to convert tts
128     (PAT $pat: pat) => { $pat };
129     (EXPR $e: expr) => { $e };
130     (TY $ty: ty) => { $ty };
131
132     // how to read the value?
133     (CONV i64, $value: expr) => { $value.as_integer() };
134     (CONV u64, $value: expr) => {
135         $value.as_integer()
136         .iter()
137         .filter_map(|&i| if i >= 0 { Some(i as u64) } else { None })
138         .next()
139     };
140     (CONV String, $value: expr) => { $value.as_str().map(Into::into) };
141     (CONV Vec<String>, $value: expr) => {{
142         let slice = $value.as_slice();
143
144         if let Some(slice) = slice {
145             if slice.iter().any(|v| v.as_str().is_none()) {
146                 None
147             } else {
148                 Some(slice.iter().map(|v| v.as_str().expect("already checked").to_owned()).collect())
149             }
150         } else {
151             None
152         }
153     }};
154
155     // provide a nicer syntax to declare the default value of `Vec<String>` variables
156     (DEFAULT Vec<String>, $e: expr) => { $e.iter().map(|&e| e.to_owned()).collect() };
157     (DEFAULT $ty: ty, $e: expr) => { $e };
158 }
159
160 define_Conf! {
161     /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about
162     ("blacklisted-names", blacklisted_names, ["foo", "bar", "baz", "quux"] => Vec<String>),
163     /// Lint: CYCLOMATIC_COMPLEXITY. The maximum cyclomatic complexity a function can have
164     ("cyclomatic-complexity-threshold", cyclomatic_complexity_threshold, 25 => u64),
165     /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
166     ("doc-valid-idents", doc_valid_idents, [
167         "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
168         "DirectX",
169         "ECMAScript",
170         "GPLv2", "GPLv3",
171         "GitHub",
172         "IPv4", "IPv6",
173         "JavaScript",
174         "NaN",
175         "OAuth",
176         "OpenGL",
177         "TrueType",
178         "iOS", "macOS",
179         "TeX", "LaTeX", "BibTex", "BibLaTex",
180         "MinGW",
181     ] => Vec<String>),
182     /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
183     ("too-many-arguments-threshold", too_many_arguments_threshold, 7 => u64),
184     /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
185     ("type-complexity-threshold", type_complexity_threshold, 250 => u64),
186     /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
187     ("single-char-binding-names-threshold", max_single_char_names, 5 => u64),
188     /// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
189     ("too-large-for-stack", too_large_for_stack, 200 => u64),
190     /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
191     ("enum-variant-name-threshold", enum_variant_name_threshold, 3 => u64),
192     /// Lint: LARGE_ENUM_VARIANT. The maximum size of a emum's variant to avoid box suggestion
193     ("enum-variant-size-threshold", enum_variant_size_threshold, 200 => u64),
194 }
195
196 /// Search for the configuration file.
197 pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
198     /// Possible filename to search for.
199     const CONFIG_FILE_NAMES: [&'static str; 2] = [".clippy.toml", "clippy.toml"];
200
201     let mut current = try!(env::current_dir());
202
203     loop {
204         for config_file_name in &CONFIG_FILE_NAMES {
205             let config_file = current.join(config_file_name);
206             match fs::metadata(&config_file) {
207                 // Only return if it's a file to handle the unlikely situation of a directory named
208                 // `clippy.toml`.
209                 Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
210                 // Return the error if it's something other than `NotFound`; otherwise we didn't
211                 // find the project file yet, and continue searching.
212                 Err(e) => {
213                     if e.kind() != io::ErrorKind::NotFound {
214                         return Err(e);
215                     }
216                 },
217                 _ => (),
218             }
219         }
220
221         // If the current directory has no parent, we're done searching.
222         if !current.pop() {
223             return Ok(None);
224         }
225     }
226 }
227
228 /// Read the `toml` configuration file.
229 ///
230 /// In case of error, the function tries to continue as much as possible.
231 pub fn read(path: Option<&path::Path>) -> (Conf, Vec<Error>) {
232     let mut conf = Conf::default();
233     let mut errors = Vec::new();
234
235     let path = if let Some(path) = path {
236         path
237     } else {
238         return (conf, errors);
239     };
240
241     let file = match fs::File::open(path) {
242         Ok(mut file) => {
243             let mut buf = String::new();
244
245             if let Err(err) = file.read_to_string(&mut buf) {
246                 errors.push(err.into());
247                 return (conf, errors);
248             }
249
250             buf
251         },
252         Err(err) => {
253             errors.push(err.into());
254             return (conf, errors);
255         },
256     };
257
258     let mut parser = toml::Parser::new(&file);
259     let toml = if let Some(toml) = parser.parse() {
260         toml
261     } else {
262         errors.push(Error::Toml(parser.errors));
263         return (conf, errors);
264     };
265
266     for (key, value) in toml {
267         if let Err(err) = conf.set(key, value) {
268             errors.push(err);
269         }
270     }
271
272     (conf, errors)
273 }