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