]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //! Read configurations files. |
2 | ||
17df50a5 | 3 | #![allow(clippy::module_name_repetitions)] |
f20569fa | 4 | |
17df50a5 XL |
5 | use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor}; |
6 | use serde::Deserialize; | |
7 | use std::error::Error; | |
f20569fa | 8 | use std::path::{Path, PathBuf}; |
f20569fa XL |
9 | use std::{env, fmt, fs, io}; |
10 | ||
136023e0 XL |
11 | /// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint. |
12 | #[derive(Clone, Debug, Deserialize)] | |
13 | pub struct Rename { | |
14 | pub path: String, | |
15 | pub rename: String, | |
16 | } | |
17 | ||
a2a8927a | 18 | /// A single disallowed method, used by the `DISALLOWED_METHODS` lint. |
c295e0f8 XL |
19 | #[derive(Clone, Debug, Deserialize)] |
20 | #[serde(untagged)] | |
21 | pub enum DisallowedMethod { | |
22 | Simple(String), | |
23 | WithReason { path: String, reason: Option<String> }, | |
24 | } | |
25 | ||
5099ac24 FG |
26 | impl DisallowedMethod { |
27 | pub fn path(&self) -> &str { | |
28 | let (Self::Simple(path) | Self::WithReason { path, .. }) = self; | |
29 | ||
30 | path | |
31 | } | |
32 | } | |
33 | ||
a2a8927a | 34 | /// A single disallowed type, used by the `DISALLOWED_TYPES` lint. |
3c0e092e XL |
35 | #[derive(Clone, Debug, Deserialize)] |
36 | #[serde(untagged)] | |
37 | pub enum DisallowedType { | |
38 | Simple(String), | |
39 | WithReason { path: String, reason: Option<String> }, | |
40 | } | |
41 | ||
17df50a5 XL |
42 | /// Conf with parse errors |
43 | #[derive(Default)] | |
44 | pub struct TryConf { | |
45 | pub conf: Conf, | |
46 | pub errors: Vec<String>, | |
f20569fa XL |
47 | } |
48 | ||
17df50a5 XL |
49 | impl TryConf { |
50 | fn from_error(error: impl Error) -> Self { | |
51 | Self { | |
52 | conf: Conf::default(), | |
53 | errors: vec![error.to_string()], | |
f20569fa XL |
54 | } |
55 | } | |
56 | } | |
57 | ||
17df50a5 XL |
58 | macro_rules! define_Conf { |
59 | ($( | |
94222f64 | 60 | $(#[doc = $doc:literal])+ |
17df50a5 XL |
61 | $(#[conf_deprecated($dep:literal)])? |
62 | ($name:ident: $ty:ty = $default:expr), | |
63 | )*) => { | |
64 | /// Clippy lint configuration | |
65 | pub struct Conf { | |
94222f64 | 66 | $($(#[doc = $doc])+ pub $name: $ty,)* |
17df50a5 | 67 | } |
f20569fa | 68 | |
17df50a5 XL |
69 | mod defaults { |
70 | $(pub fn $name() -> $ty { $default })* | |
71 | } | |
f20569fa | 72 | |
17df50a5 XL |
73 | impl Default for Conf { |
74 | fn default() -> Self { | |
75 | Self { $($name: defaults::$name(),)* } | |
f20569fa | 76 | } |
17df50a5 XL |
77 | } |
78 | ||
79 | impl<'de> Deserialize<'de> for TryConf { | |
80 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> { | |
81 | deserializer.deserialize_map(ConfVisitor) | |
82 | } | |
83 | } | |
f20569fa | 84 | |
17df50a5 XL |
85 | #[derive(Deserialize)] |
86 | #[serde(field_identifier, rename_all = "kebab-case")] | |
87 | #[allow(non_camel_case_types)] | |
88 | enum Field { $($name,)* third_party, } | |
f20569fa | 89 | |
17df50a5 XL |
90 | struct ConfVisitor; |
91 | ||
92 | impl<'de> Visitor<'de> for ConfVisitor { | |
93 | type Value = TryConf; | |
94 | ||
95 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { | |
96 | formatter.write_str("Conf") | |
97 | } | |
98 | ||
99 | fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> { | |
100 | let mut errors = Vec::new(); | |
101 | $(let mut $name = None;)* | |
102 | // could get `Field` here directly, but get `str` first for diagnostics | |
103 | while let Some(name) = map.next_key::<&str>()? { | |
104 | match Field::deserialize(name.into_deserializer())? { | |
105 | $(Field::$name => { | |
106 | $(errors.push(format!("deprecated field `{}`. {}", name, $dep));)? | |
107 | match map.next_value() { | |
108 | Err(e) => errors.push(e.to_string()), | |
109 | Ok(value) => match $name { | |
110 | Some(_) => errors.push(format!("duplicate field `{}`", name)), | |
111 | None => $name = Some(value), | |
112 | } | |
113 | } | |
114 | })* | |
115 | // white-listed; ignore | |
116 | Field::third_party => drop(map.next_value::<IgnoredAny>()) | |
f20569fa XL |
117 | } |
118 | } | |
17df50a5 XL |
119 | let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* }; |
120 | Ok(TryConf { conf, errors }) | |
121 | } | |
122 | } | |
f20569fa | 123 | |
5099ac24 | 124 | #[cfg(feature = "internal")] |
17df50a5 XL |
125 | pub mod metadata { |
126 | use crate::utils::internal_lints::metadata_collector::ClippyConfiguration; | |
127 | ||
128 | macro_rules! wrap_option { | |
129 | () => (None); | |
130 | ($x:literal) => (Some($x)); | |
131 | } | |
132 | ||
133 | pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> { | |
134 | vec![ | |
135 | $( | |
136 | { | |
137 | let deprecation_reason = wrap_option!($($dep)?); | |
138 | ||
139 | ClippyConfiguration::new( | |
140 | stringify!($name), | |
141 | stringify!($ty), | |
142 | format!("{:?}", super::defaults::$name()), | |
94222f64 | 143 | concat!($($doc, '\n',)*), |
17df50a5 XL |
144 | deprecation_reason, |
145 | ) | |
146 | }, | |
147 | )+ | |
148 | ] | |
149 | } | |
f20569fa XL |
150 | } |
151 | }; | |
152 | } | |
153 | ||
f20569fa | 154 | define_Conf! { |
c295e0f8 | 155 | /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX. |
94222f64 XL |
156 | /// |
157 | /// Suppress lints whenever the suggested change would cause breakage for other crates. | |
17df50a5 | 158 | (avoid_breaking_exported_api: bool = true), |
04454e1e | 159 | /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED. |
94222f64 XL |
160 | /// |
161 | /// The minimum rust version that the project supports | |
17df50a5 | 162 | (msrv: Option<String> = None), |
94222f64 XL |
163 | /// Lint: BLACKLISTED_NAME. |
164 | /// | |
165 | /// The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses | |
17df50a5 | 166 | (blacklisted_names: Vec<String> = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), |
94222f64 XL |
167 | /// Lint: COGNITIVE_COMPLEXITY. |
168 | /// | |
169 | /// The maximum cognitive complexity a function can have | |
17df50a5 | 170 | (cognitive_complexity_threshold: u64 = 25), |
94222f64 XL |
171 | /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. |
172 | /// | |
173 | /// Use the Cognitive Complexity lint instead. | |
17df50a5 XL |
174 | #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")] |
175 | (cyclomatic_complexity_threshold: Option<u64> = None), | |
94222f64 XL |
176 | /// Lint: DOC_MARKDOWN. |
177 | /// | |
178 | /// The list of words this lint should not consider as identifiers needing ticks | |
17df50a5 | 179 | (doc_valid_idents: Vec<String> = [ |
f20569fa XL |
180 | "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", |
181 | "DirectX", | |
182 | "ECMAScript", | |
183 | "GPLv2", "GPLv3", | |
184 | "GitHub", "GitLab", | |
185 | "IPv4", "IPv6", | |
186 | "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", | |
187 | "NaN", "NaNs", | |
188 | "OAuth", "GraphQL", | |
189 | "OCaml", | |
190 | "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", | |
191 | "WebGL", | |
192 | "TensorFlow", | |
193 | "TrueType", | |
136023e0 | 194 | "iOS", "macOS", "FreeBSD", |
f20569fa XL |
195 | "TeX", "LaTeX", "BibTeX", "BibLaTeX", |
196 | "MinGW", | |
197 | "CamelCase", | |
198 | ].iter().map(ToString::to_string).collect()), | |
94222f64 XL |
199 | /// Lint: TOO_MANY_ARGUMENTS. |
200 | /// | |
201 | /// The maximum number of argument a function or method can have | |
17df50a5 | 202 | (too_many_arguments_threshold: u64 = 7), |
94222f64 XL |
203 | /// Lint: TYPE_COMPLEXITY. |
204 | /// | |
205 | /// The maximum complexity a type can have | |
17df50a5 | 206 | (type_complexity_threshold: u64 = 250), |
94222f64 XL |
207 | /// Lint: MANY_SINGLE_CHAR_NAMES. |
208 | /// | |
209 | /// The maximum number of single char bindings a scope may have | |
17df50a5 | 210 | (single_char_binding_names_threshold: u64 = 4), |
94222f64 XL |
211 | /// Lint: BOXED_LOCAL, USELESS_VEC. |
212 | /// | |
213 | /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap | |
17df50a5 | 214 | (too_large_for_stack: u64 = 200), |
94222f64 XL |
215 | /// Lint: ENUM_VARIANT_NAMES. |
216 | /// | |
217 | /// The minimum number of enum variants for the lints about variant names to trigger | |
17df50a5 | 218 | (enum_variant_name_threshold: u64 = 3), |
94222f64 XL |
219 | /// Lint: LARGE_ENUM_VARIANT. |
220 | /// | |
221 | /// The maximum size of an enum's variant to avoid box suggestion | |
17df50a5 | 222 | (enum_variant_size_threshold: u64 = 200), |
94222f64 XL |
223 | /// Lint: VERBOSE_BIT_MASK. |
224 | /// | |
225 | /// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' | |
17df50a5 | 226 | (verbose_bit_mask_threshold: u64 = 1), |
94222f64 XL |
227 | /// Lint: DECIMAL_LITERAL_REPRESENTATION. |
228 | /// | |
229 | /// The lower bound for linting decimal literals | |
17df50a5 | 230 | (literal_representation_threshold: u64 = 16384), |
94222f64 XL |
231 | /// Lint: TRIVIALLY_COPY_PASS_BY_REF. |
232 | /// | |
233 | /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. | |
17df50a5 | 234 | (trivial_copy_size_limit: Option<u64> = None), |
94222f64 XL |
235 | /// Lint: LARGE_TYPE_PASS_BY_MOVE. |
236 | /// | |
237 | /// The minimum size (in bytes) to consider a type for passing by reference instead of by value. | |
17df50a5 | 238 | (pass_by_value_size_limit: u64 = 256), |
94222f64 XL |
239 | /// Lint: TOO_MANY_LINES. |
240 | /// | |
241 | /// The maximum number of lines a function or method can have | |
17df50a5 | 242 | (too_many_lines_threshold: u64 = 100), |
94222f64 XL |
243 | /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. |
244 | /// | |
245 | /// The maximum allowed size for arrays on the stack | |
17df50a5 | 246 | (array_size_threshold: u64 = 512_000), |
94222f64 XL |
247 | /// Lint: VEC_BOX. |
248 | /// | |
249 | /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed | |
17df50a5 | 250 | (vec_box_size_threshold: u64 = 4096), |
94222f64 XL |
251 | /// Lint: TYPE_REPETITION_IN_BOUNDS. |
252 | /// | |
253 | /// The maximum number of bounds a trait can have to be linted | |
17df50a5 | 254 | (max_trait_bounds: u64 = 3), |
94222f64 XL |
255 | /// Lint: STRUCT_EXCESSIVE_BOOLS. |
256 | /// | |
257 | /// The maximum number of bool fields a struct can have | |
17df50a5 | 258 | (max_struct_bools: u64 = 3), |
94222f64 XL |
259 | /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. |
260 | /// | |
261 | /// The maximum number of bool parameters a function can have | |
17df50a5 | 262 | (max_fn_params_bools: u64 = 3), |
94222f64 XL |
263 | /// Lint: WILDCARD_IMPORTS. |
264 | /// | |
265 | /// Whether to allow certain wildcard imports (prelude, super in tests). | |
17df50a5 | 266 | (warn_on_all_wildcard_imports: bool = false), |
a2a8927a | 267 | /// Lint: DISALLOWED_METHODS. |
94222f64 XL |
268 | /// |
269 | /// The list of disallowed methods, written as fully qualified paths. | |
c295e0f8 | 270 | (disallowed_methods: Vec<crate::utils::conf::DisallowedMethod> = Vec::new()), |
a2a8927a | 271 | /// Lint: DISALLOWED_TYPES. |
94222f64 XL |
272 | /// |
273 | /// The list of disallowed types, written as fully qualified paths. | |
3c0e092e | 274 | (disallowed_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()), |
94222f64 XL |
275 | /// Lint: UNREADABLE_LITERAL. |
276 | /// | |
277 | /// Should the fraction of a decimal be linted to include separators. | |
17df50a5 | 278 | (unreadable_literal_lint_fractions: bool = true), |
94222f64 XL |
279 | /// Lint: UPPER_CASE_ACRONYMS. |
280 | /// | |
281 | /// Enables verbose mode. Triggers if there is more than one uppercase char next to each other | |
17df50a5 | 282 | (upper_case_acronyms_aggressive: bool = false), |
94222f64 XL |
283 | /// Lint: _CARGO_COMMON_METADATA. |
284 | /// | |
285 | /// For internal testing only, ignores the current `publish` settings in the Cargo manifest. | |
17df50a5 | 286 | (cargo_ignore_publish: bool = false), |
94222f64 XL |
287 | /// Lint: NONSTANDARD_MACRO_BRACES. |
288 | /// | |
289 | /// Enforce the named macros always use the braces specified. | |
290 | /// | |
291 | /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro | |
292 | /// is could be used with a full path two `MacroMatcher`s have to be added one with the full path | |
293 | /// `crate_name::macro_name` and one with just the macro name. | |
136023e0 | 294 | (standard_macro_braces: Vec<crate::nonstandard_macro_braces::MacroMatcher> = Vec::new()), |
94222f64 XL |
295 | /// Lint: MISSING_ENFORCED_IMPORT_RENAMES. |
296 | /// | |
297 | /// The list of imports to always rename, a fully qualified path followed by the rename. | |
136023e0 | 298 | (enforced_import_renames: Vec<crate::utils::conf::Rename> = Vec::new()), |
3c0e092e | 299 | /// Lint: DISALLOWED_SCRIPT_IDENTS. |
94222f64 XL |
300 | /// |
301 | /// The list of unicode scripts allowed to be used in the scope. | |
3c0e092e | 302 | (allowed_scripts: Vec<String> = ["Latin"].iter().map(ToString::to_string).collect()), |
c295e0f8 XL |
303 | /// Lint: NON_SEND_FIELDS_IN_SEND_TY. |
304 | /// | |
305 | /// Whether to apply the raw pointer heuristic to determine if a type is `Send`. | |
306 | (enable_raw_pointer_heuristic_for_send: bool = true), | |
a2a8927a XL |
307 | /// Lint: INDEX_REFUTABLE_SLICE. |
308 | /// | |
309 | /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in | |
310 | /// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed. | |
311 | /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. | |
312 | (max_suggested_slice_pattern_length: u64 = 3), | |
04454e1e FG |
313 | /// Lint: AWAIT_HOLDING_INVALID_TYPE |
314 | (await_holding_invalid_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()), | |
315 | /// Lint: LARGE_INCLUDE_FILE. | |
316 | /// | |
317 | /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes | |
318 | (max_include_file_size: u64 = 1_000_000), | |
f20569fa XL |
319 | } |
320 | ||
321 | /// Search for the configuration file. | |
322 | pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> { | |
323 | /// Possible filename to search for. | |
324 | const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"]; | |
325 | ||
326 | // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR. | |
327 | // If neither of those exist, use ".". | |
328 | let mut current = env::var_os("CLIPPY_CONF_DIR") | |
329 | .or_else(|| env::var_os("CARGO_MANIFEST_DIR")) | |
330 | .map_or_else(|| PathBuf::from("."), PathBuf::from); | |
5099ac24 FG |
331 | |
332 | let mut found_config: Option<PathBuf> = None; | |
333 | ||
f20569fa XL |
334 | loop { |
335 | for config_file_name in &CONFIG_FILE_NAMES { | |
17df50a5 XL |
336 | if let Ok(config_file) = current.join(config_file_name).canonicalize() { |
337 | match fs::metadata(&config_file) { | |
338 | Err(e) if e.kind() == io::ErrorKind::NotFound => {}, | |
339 | Err(e) => return Err(e), | |
340 | Ok(md) if md.is_dir() => {}, | |
5099ac24 FG |
341 | Ok(_) => { |
342 | // warn if we happen to find two config files #8323 | |
343 | if let Some(ref found_config_) = found_config { | |
344 | eprintln!( | |
345 | "Using config file `{}`\nWarning: `{}` will be ignored.", | |
346 | found_config_.display(), | |
347 | config_file.display(), | |
348 | ); | |
349 | } else { | |
350 | found_config = Some(config_file); | |
351 | } | |
352 | }, | |
17df50a5 | 353 | } |
f20569fa XL |
354 | } |
355 | } | |
356 | ||
5099ac24 FG |
357 | if found_config.is_some() { |
358 | return Ok(found_config); | |
359 | } | |
360 | ||
f20569fa XL |
361 | // If the current directory has no parent, we're done searching. |
362 | if !current.pop() { | |
363 | return Ok(None); | |
364 | } | |
365 | } | |
366 | } | |
367 | ||
f20569fa XL |
368 | /// Read the `toml` configuration file. |
369 | /// | |
370 | /// In case of error, the function tries to continue as much as possible. | |
17df50a5 | 371 | pub fn read(path: &Path) -> TryConf { |
f20569fa | 372 | let content = match fs::read_to_string(path) { |
17df50a5 | 373 | Err(e) => return TryConf::from_error(e), |
f20569fa | 374 | Ok(content) => content, |
f20569fa | 375 | }; |
17df50a5 | 376 | toml::from_str(&content).unwrap_or_else(TryConf::from_error) |
f20569fa | 377 | } |