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