]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/conf.rs
Auto merge of #3684 - g-bartoszek:sugg-snippet-modifications, r=phansch
[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::io::Read;
8 use std::sync::Mutex;
9 use std::{env, fmt, fs, io, path};
10 use syntax::{ast, source_map};
11 use toml;
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) => {
24                     if let ast::LitKind::Str(ref file, _) = value.node {
25                         Ok(Some(file.to_string().into()))
26                     } else {
27                         Err(("`conf_file` value must be a string", value.span))
28                     }
29                 },
30             };
31         }
32     }
33
34     Ok(None)
35 }
36
37 /// Error from reading a configuration file.
38 #[derive(Debug)]
39 pub enum Error {
40     /// An I/O error.
41     Io(io::Error),
42     /// Not valid toml or doesn't fit the expected conf format
43     Toml(String),
44 }
45
46 impl fmt::Display for Error {
47     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
48         match *self {
49             Error::Io(ref err) => err.fmt(f),
50             Error::Toml(ref err) => err.fmt(f),
51         }
52     }
53 }
54
55 impl From<io::Error> for Error {
56     fn from(e: io::Error) -> Self {
57         Error::Io(e)
58     }
59 }
60
61 lazy_static! {
62     static ref ERRORS: Mutex<Vec<Error>> = Mutex::new(Vec::new());
63 }
64
65 macro_rules! define_Conf {
66     ($(#[$doc: meta] ($rust_name: ident, $rust_name_str: expr, $default: expr => $($ty: tt)+),)+) => {
67         pub use self::helpers::Conf;
68         mod helpers {
69             use serde_derive::Deserialize;
70             /// Type used to store lint configuration.
71             #[derive(Deserialize)]
72             #[serde(rename_all="kebab-case", deny_unknown_fields)]
73             pub struct Conf {
74                 $(#[$doc] #[serde(default=$rust_name_str)] #[serde(with=$rust_name_str)]
75                           pub $rust_name: define_Conf!(TY $($ty)+),)+
76                 #[allow(dead_code)]
77                 #[serde(default)]
78                 third_party: Option<::toml::Value>,
79             }
80             $(
81                 mod $rust_name {
82                     use serde;
83                     use serde::Deserialize;
84                     crate fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D)
85                     -> Result<define_Conf!(TY $($ty)+), D::Error> {
86                         type T = define_Conf!(TY $($ty)+);
87                         Ok(T::deserialize(deserializer).unwrap_or_else(|e| {
88                             crate::utils::conf::ERRORS.lock().expect("no threading here")
89                                                         .push(crate::utils::conf::Error::Toml(e.to_string()));
90                             super::$rust_name()
91                         }))
92                     }
93                 }
94
95                 fn $rust_name() -> define_Conf!(TY $($ty)+) {
96                     define_Conf!(DEFAULT $($ty)+, $default)
97                 }
98             )+
99         }
100     };
101
102     // hack to convert tts
103     (TY $ty: ty) => { $ty };
104
105     // provide a nicer syntax to declare the default value of `Vec<String>` variables
106     (DEFAULT Vec<String>, $e: expr) => { $e.iter().map(|&e| e.to_owned()).collect() };
107     (DEFAULT $ty: ty, $e: expr) => { $e };
108 }
109
110 define_Conf! {
111     /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about
112     (blacklisted_names, "blacklisted_names", ["foo", "bar", "baz", "quux"] => Vec<String>),
113     /// Lint: CYCLOMATIC_COMPLEXITY. The maximum cyclomatic complexity a function can have
114     (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold", 25 => u64),
115     /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
116     (doc_valid_idents, "doc_valid_idents", [
117         "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
118         "DirectX",
119         "ECMAScript",
120         "GPLv2", "GPLv3",
121         "GitHub", "GitLab",
122         "IPv4", "IPv6",
123         "JavaScript",
124         "NaN", "NaNs",
125         "OAuth",
126         "OpenGL", "OpenSSH", "OpenSSL", "OpenStreetMap",
127         "TrueType",
128         "iOS", "macOS",
129         "TeX", "LaTeX", "BibTeX", "BibLaTeX",
130         "MinGW",
131         "CamelCase",
132     ] => Vec<String>),
133     /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
134     (too_many_arguments_threshold, "too_many_arguments_threshold", 7 => u64),
135     /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
136     (type_complexity_threshold, "type_complexity_threshold", 250 => u64),
137     /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
138     (single_char_binding_names_threshold, "single_char_binding_names_threshold", 5 => u64),
139     /// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
140     (too_large_for_stack, "too_large_for_stack", 200 => u64),
141     /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
142     (enum_variant_name_threshold, "enum_variant_name_threshold", 3 => u64),
143     /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
144     (enum_variant_size_threshold, "enum_variant_size_threshold", 200 => u64),
145     /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
146     (verbose_bit_mask_threshold, "verbose_bit_mask_threshold", 1 => u64),
147     /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
148     (literal_representation_threshold, "literal_representation_threshold", 16384 => u64),
149     /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
150     (trivial_copy_size_limit, "trivial_copy_size_limit", None => Option<u64>),
151 }
152
153 impl Default for Conf {
154     fn default() -> Self {
155         toml::from_str("").expect("we never error on empty config files")
156     }
157 }
158
159 /// Search for the configuration file.
160 pub fn lookup_conf_file() -> io::Result<Option<path::PathBuf>> {
161     /// Possible filename to search for.
162     const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
163
164     let mut current = path::PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
165
166     loop {
167         for config_file_name in &CONFIG_FILE_NAMES {
168             let config_file = current.join(config_file_name);
169             match fs::metadata(&config_file) {
170                 // Only return if it's a file to handle the unlikely situation of a directory named
171                 // `clippy.toml`.
172                 Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
173                 // Return the error if it's something other than `NotFound`; otherwise we didn't
174                 // find the project file yet, and continue searching.
175                 Err(e) => {
176                     if e.kind() != io::ErrorKind::NotFound {
177                         return Err(e);
178                     }
179                 },
180                 _ => (),
181             }
182         }
183
184         // If the current directory has no parent, we're done searching.
185         if !current.pop() {
186             return Ok(None);
187         }
188     }
189 }
190
191 /// Produces a `Conf` filled with the default values and forwards the errors
192 ///
193 /// Used internally for convenience
194 fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
195     (Conf::default(), errors)
196 }
197
198 /// Read the `toml` configuration file.
199 ///
200 /// In case of error, the function tries to continue as much as possible.
201 pub fn read(path: Option<&path::Path>) -> (Conf, Vec<Error>) {
202     let path = if let Some(path) = path {
203         path
204     } else {
205         return default(Vec::new());
206     };
207
208     let file = match fs::File::open(path) {
209         Ok(mut file) => {
210             let mut buf = String::new();
211
212             if let Err(err) = file.read_to_string(&mut buf) {
213                 return default(vec![err.into()]);
214             }
215
216             buf
217         },
218         Err(err) => return default(vec![err.into()]),
219     };
220
221     assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
222     match toml::from_str(&file) {
223         Ok(toml) => (
224             toml,
225             ERRORS.lock().expect("no threading -> mutex always safe").split_off(0),
226         ),
227         Err(e) => {
228             let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
229             errors.push(Error::Toml(e.to_string()));
230             default(errors)
231         },
232     }
233 }