]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
Merge branch 'macro-use' into HEAD
[rust.git] / clippy_lints / src / utils / conf.rs
1 //! Read configurations files.
2
3 #![deny(missing_docs_in_private_items)]
4
5 use lazy_static::lazy_static;
6 use std::{env, fmt, fs, io, path};
7 use std::io::Read;
8 use syntax::{ast, codemap};
9 use toml;
10 use std::sync::Mutex;
11
12 /// Get the configuration file from arguments.
13 pub fn file_from_args(
14     args: &[codemap::Spanned<ast::NestedMetaItemKind>],
15 ) -> Result<Option<path::PathBuf>, (&'static str, codemap::Span)> {
16     for arg in args.iter().filter_map(|a| a.meta_item()) {
17         if arg.name() == "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) => if let ast::LitKind::Str(ref file, _) = value.node {
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     Ok(None)
32 }
33
34 /// Error from reading a configuration file.
35 #[derive(Debug)]
36 pub enum Error {
37     /// An I/O error.
38     Io(io::Error),
39     /// Not valid toml or doesn't fit the expected conf format
40     Toml(String),
41     /// Type error.
42     Type(
43         /// The name of the key.
44         &'static str,
45         /// The expected type.
46         &'static str,
47         /// The type we got instead.
48         &'static str,
49     ),
50     /// There is an unknown key is the file.
51     UnknownKey(String),
52 }
53
54 impl fmt::Display for Error {
55     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
56         match *self {
57             Error::Io(ref err) => err.fmt(f),
58             Error::Toml(ref err) => err.fmt(f),
59             Error::Type(key, expected, got) => {
60                 write!(f, "`{}` is expected to be a `{}` but is a `{}`", key, expected, got)
61             },
62             Error::UnknownKey(ref key) => write!(f, "unknown key `{}`", key),
63         }
64     }
65 }
66
67 impl From<io::Error> for Error {
68     fn from(e: io::Error) -> Self {
69         Error::Io(e)
70     }
71 }
72
73 lazy_static! {
74     static ref ERRORS: Mutex<Vec<Error>> = Mutex::new(Vec::new());
75 }
76
77 macro_rules! define_Conf {
78     ($(#[$doc: meta] ($rust_name: ident, $rust_name_str: expr, $default: expr => $($ty: tt)+),)+) => {
79         pub use self::helpers::Conf;
80         // FIXME(mati865): remove #[allow(rust_2018_idioms)] when it's fixed:
81         //
82         // warning: `extern crate` is not idiomatic in the new edition
83         //    --> src/utils/conf.rs:82:22
84         //     |
85         // 82  |               #[derive(Deserialize)]
86         //     |                        ^^^^^^^^^^^ help: convert it to a `use`
87         //
88         #[allow(rust_2018_idioms)]
89         mod helpers {
90             use serde_derive::Deserialize;
91             /// Type used to store lint configuration.
92             #[derive(Deserialize)]
93             #[serde(rename_all="kebab-case", deny_unknown_fields)]
94             pub struct Conf {
95                 $(#[$doc] #[serde(default=$rust_name_str)] #[serde(with=$rust_name_str)]
96                           pub $rust_name: define_Conf!(TY $($ty)+),)+
97                 #[allow(dead_code)]
98                 #[serde(default)]
99                 third_party: Option<::toml::Value>,
100             }
101             $(
102                 mod $rust_name {
103                     use serde;
104                     use serde::Deserialize;
105                     crate fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D)
106                     -> Result<define_Conf!(TY $($ty)+), D::Error> {
107                         type T = define_Conf!(TY $($ty)+);
108                         Ok(T::deserialize(deserializer).unwrap_or_else(|e| {
109                             crate::utils::conf::ERRORS.lock().expect("no threading here")
110                                                         .push(crate::utils::conf::Error::Toml(e.to_string()));
111                             super::$rust_name()
112                         }))
113                     }
114                 }
115
116                 fn $rust_name() -> define_Conf!(TY $($ty)+) {
117                     define_Conf!(DEFAULT $($ty)+, $default)
118                 }
119             )+
120         }
121     };
122
123     // hack to convert tts
124     (TY $ty: ty) => { $ty };
125
126     // provide a nicer syntax to declare the default value of `Vec<String>` variables
127     (DEFAULT Vec<String>, $e: expr) => { $e.iter().map(|&e| e.to_owned()).collect() };
128     (DEFAULT $ty: ty, $e: expr) => { $e };
129 }
130
131 define_Conf! {
132     /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about
133     (blacklisted_names, "blacklisted_names", ["foo", "bar", "baz", "quux"] => Vec<String>),
134     /// Lint: CYCLOMATIC_COMPLEXITY. The maximum cyclomatic complexity a function can have
135     (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold", 25 => u64),
136     /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
137     (doc_valid_idents, "doc_valid_idents", [
138         "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
139         "DirectX",
140         "ECMAScript",
141         "GPLv2", "GPLv3",
142         "GitHub", "GitLab",
143         "IPv4", "IPv6",
144         "JavaScript",
145         "NaN", "NaNs",
146         "OAuth",
147         "OpenGL", "OpenSSH", "OpenSSL", "OpenStreetMap",
148         "TrueType",
149         "iOS", "macOS",
150         "TeX", "LaTeX", "BibTeX", "BibLaTeX",
151         "MinGW",
152     ] => Vec<String>),
153     /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
154     (too_many_arguments_threshold, "too_many_arguments_threshold", 7 => u64),
155     /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
156     (type_complexity_threshold, "type_complexity_threshold", 250 => u64),
157     /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
158     (single_char_binding_names_threshold, "single_char_binding_names_threshold", 5 => u64),
159     /// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
160     (too_large_for_stack, "too_large_for_stack", 200 => u64),
161     /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
162     (enum_variant_name_threshold, "enum_variant_name_threshold", 3 => u64),
163     /// Lint: LARGE_ENUM_VARIANT. The maximum size of a emum's variant to avoid box suggestion
164     (enum_variant_size_threshold, "enum_variant_size_threshold", 200 => u64),
165     /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
166     (verbose_bit_mask_threshold, "verbose_bit_mask_threshold", 1 => u64),
167     /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
168     (literal_representation_threshold, "literal_representation_threshold", 16384 => u64),
169     /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
170     (trivial_copy_size_limit, "trivial_copy_size_limit", None => Option<u64>),
171 }
172
173 /// Search for the configuration file.
174 pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
175     /// Possible filename to search for.
176     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
177
178     let mut current = path::PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
179
180     loop {
181         for config_file_name in &CONFIG_FILE_NAMES {
182             let config_file = current.join(config_file_name);
183             match fs::metadata(&config_file) {
184                 // Only return if it's a file to handle the unlikely situation of a directory named
185                 // `clippy.toml`.
186                 Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
187                 // Return the error if it's something other than `NotFound`; otherwise we didn't
188                 // find the project file yet, and continue searching.
189                 Err(e) => if e.kind() != io::ErrorKind::NotFound {
190                     return Err(e);
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     (toml::from_str("").expect("we never error on empty config files"), 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!(
234         ERRORS
235             .lock()
236             .expect("no threading -> mutex always safe")
237             .is_empty()
238     );
239     match toml::from_str(&file) {
240         Ok(toml) => (
241             toml,
242             ERRORS
243                 .lock()
244                 .expect("no threading -> mutex always safe")
245                 .split_off(0),
246         ),
247         Err(e) => {
248             let mut errors = ERRORS
249                 .lock()
250                 .expect("no threading -> mutex always safe")
251                 .split_off(0);
252             errors.push(Error::Toml(e.to_string()));
253             default(errors)
254         },
255     }
256 }