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