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