]>
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 | ||
17df50a5 XL |
18 | /// Conf with parse errors |
19 | #[derive(Default)] | |
20 | pub struct TryConf { | |
21 | pub conf: Conf, | |
22 | pub errors: Vec<String>, | |
f20569fa XL |
23 | } |
24 | ||
17df50a5 XL |
25 | impl TryConf { |
26 | fn from_error(error: impl Error) -> Self { | |
27 | Self { | |
28 | conf: Conf::default(), | |
29 | errors: vec![error.to_string()], | |
f20569fa XL |
30 | } |
31 | } | |
32 | } | |
33 | ||
136023e0 XL |
34 | /// Note that the configuration parsing currently doesn't support documentation that will |
35 | /// that spans over several lines. This will be possible with the new implementation | |
36 | /// See (rust-clippy#7172) | |
17df50a5 XL |
37 | macro_rules! define_Conf { |
38 | ($( | |
39 | #[doc = $doc:literal] | |
40 | $(#[conf_deprecated($dep:literal)])? | |
41 | ($name:ident: $ty:ty = $default:expr), | |
42 | )*) => { | |
43 | /// Clippy lint configuration | |
44 | pub struct Conf { | |
45 | $(#[doc = $doc] pub $name: $ty,)* | |
46 | } | |
f20569fa | 47 | |
17df50a5 XL |
48 | mod defaults { |
49 | $(pub fn $name() -> $ty { $default })* | |
50 | } | |
f20569fa | 51 | |
17df50a5 XL |
52 | impl Default for Conf { |
53 | fn default() -> Self { | |
54 | Self { $($name: defaults::$name(),)* } | |
f20569fa | 55 | } |
17df50a5 XL |
56 | } |
57 | ||
58 | impl<'de> Deserialize<'de> for TryConf { | |
59 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> { | |
60 | deserializer.deserialize_map(ConfVisitor) | |
61 | } | |
62 | } | |
f20569fa | 63 | |
17df50a5 XL |
64 | #[derive(Deserialize)] |
65 | #[serde(field_identifier, rename_all = "kebab-case")] | |
66 | #[allow(non_camel_case_types)] | |
67 | enum Field { $($name,)* third_party, } | |
f20569fa | 68 | |
17df50a5 XL |
69 | struct ConfVisitor; |
70 | ||
71 | impl<'de> Visitor<'de> for ConfVisitor { | |
72 | type Value = TryConf; | |
73 | ||
74 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { | |
75 | formatter.write_str("Conf") | |
76 | } | |
77 | ||
78 | fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> { | |
79 | let mut errors = Vec::new(); | |
80 | $(let mut $name = None;)* | |
81 | // could get `Field` here directly, but get `str` first for diagnostics | |
82 | while let Some(name) = map.next_key::<&str>()? { | |
83 | match Field::deserialize(name.into_deserializer())? { | |
84 | $(Field::$name => { | |
85 | $(errors.push(format!("deprecated field `{}`. {}", name, $dep));)? | |
86 | match map.next_value() { | |
87 | Err(e) => errors.push(e.to_string()), | |
88 | Ok(value) => match $name { | |
89 | Some(_) => errors.push(format!("duplicate field `{}`", name)), | |
90 | None => $name = Some(value), | |
91 | } | |
92 | } | |
93 | })* | |
94 | // white-listed; ignore | |
95 | Field::third_party => drop(map.next_value::<IgnoredAny>()) | |
f20569fa XL |
96 | } |
97 | } | |
17df50a5 XL |
98 | let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* }; |
99 | Ok(TryConf { conf, errors }) | |
100 | } | |
101 | } | |
f20569fa | 102 | |
17df50a5 XL |
103 | #[cfg(feature = "metadata-collector-lint")] |
104 | pub mod metadata { | |
105 | use crate::utils::internal_lints::metadata_collector::ClippyConfiguration; | |
106 | ||
107 | macro_rules! wrap_option { | |
108 | () => (None); | |
109 | ($x:literal) => (Some($x)); | |
110 | } | |
111 | ||
112 | pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> { | |
113 | vec![ | |
114 | $( | |
115 | { | |
116 | let deprecation_reason = wrap_option!($($dep)?); | |
117 | ||
118 | ClippyConfiguration::new( | |
119 | stringify!($name), | |
120 | stringify!($ty), | |
121 | format!("{:?}", super::defaults::$name()), | |
122 | $doc, | |
123 | deprecation_reason, | |
124 | ) | |
125 | }, | |
126 | )+ | |
127 | ] | |
128 | } | |
f20569fa XL |
129 | } |
130 | }; | |
131 | } | |
132 | ||
17df50a5 | 133 | // N.B., this macro is parsed by util/lintlib.py |
f20569fa | 134 | define_Conf! { |
17df50a5 XL |
135 | /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION. Suppress lints whenever the suggested change would cause breakage for other crates. |
136 | (avoid_breaking_exported_api: bool = true), | |
137 | /// Lint: 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. The minimum rust version that the project supports | |
138 | (msrv: Option<String> = None), | |
f20569fa | 139 | /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses |
17df50a5 | 140 | (blacklisted_names: Vec<String> = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), |
f20569fa | 141 | /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have |
17df50a5 | 142 | (cognitive_complexity_threshold: u64 = 25), |
f20569fa | 143 | /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. |
17df50a5 XL |
144 | #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")] |
145 | (cyclomatic_complexity_threshold: Option<u64> = None), | |
f20569fa | 146 | /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks |
17df50a5 | 147 | (doc_valid_idents: Vec<String> = [ |
f20569fa XL |
148 | "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", |
149 | "DirectX", | |
150 | "ECMAScript", | |
151 | "GPLv2", "GPLv3", | |
152 | "GitHub", "GitLab", | |
153 | "IPv4", "IPv6", | |
154 | "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", | |
155 | "NaN", "NaNs", | |
156 | "OAuth", "GraphQL", | |
157 | "OCaml", | |
158 | "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", | |
159 | "WebGL", | |
160 | "TensorFlow", | |
161 | "TrueType", | |
136023e0 | 162 | "iOS", "macOS", "FreeBSD", |
f20569fa XL |
163 | "TeX", "LaTeX", "BibTeX", "BibLaTeX", |
164 | "MinGW", | |
165 | "CamelCase", | |
166 | ].iter().map(ToString::to_string).collect()), | |
167 | /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have | |
17df50a5 | 168 | (too_many_arguments_threshold: u64 = 7), |
f20569fa | 169 | /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have |
17df50a5 | 170 | (type_complexity_threshold: u64 = 250), |
f20569fa | 171 | /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have |
17df50a5 | 172 | (single_char_binding_names_threshold: u64 = 4), |
f20569fa | 173 | /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap |
17df50a5 | 174 | (too_large_for_stack: u64 = 200), |
f20569fa | 175 | /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger |
17df50a5 | 176 | (enum_variant_name_threshold: u64 = 3), |
f20569fa | 177 | /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion |
17df50a5 | 178 | (enum_variant_size_threshold: u64 = 200), |
f20569fa | 179 | /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' |
17df50a5 | 180 | (verbose_bit_mask_threshold: u64 = 1), |
f20569fa | 181 | /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals |
17df50a5 | 182 | (literal_representation_threshold: u64 = 16384), |
f20569fa | 183 | /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. |
17df50a5 | 184 | (trivial_copy_size_limit: Option<u64> = None), |
f20569fa | 185 | /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value. |
17df50a5 | 186 | (pass_by_value_size_limit: u64 = 256), |
f20569fa | 187 | /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have |
17df50a5 | 188 | (too_many_lines_threshold: u64 = 100), |
f20569fa | 189 | /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack |
17df50a5 | 190 | (array_size_threshold: u64 = 512_000), |
f20569fa | 191 | /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed |
17df50a5 | 192 | (vec_box_size_threshold: u64 = 4096), |
f20569fa | 193 | /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted |
17df50a5 | 194 | (max_trait_bounds: u64 = 3), |
136023e0 | 195 | /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bool fields a struct can have |
17df50a5 | 196 | (max_struct_bools: u64 = 3), |
136023e0 | 197 | /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bool parameters a function can have |
17df50a5 | 198 | (max_fn_params_bools: u64 = 3), |
f20569fa | 199 | /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests). |
17df50a5 | 200 | (warn_on_all_wildcard_imports: bool = false), |
f20569fa | 201 | /// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths. |
17df50a5 | 202 | (disallowed_methods: Vec<String> = Vec::new()), |
136023e0 XL |
203 | /// Lint: DISALLOWED_TYPE. The list of disallowed types, written as fully qualified paths. |
204 | (disallowed_types: Vec<String> = Vec::new()), | |
f20569fa | 205 | /// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators. |
17df50a5 | 206 | (unreadable_literal_lint_fractions: bool = true), |
f20569fa | 207 | /// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other |
17df50a5 | 208 | (upper_case_acronyms_aggressive: bool = false), |
f20569fa | 209 | /// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest. |
17df50a5 | 210 | (cargo_ignore_publish: bool = false), |
136023e0 XL |
211 | /// Lint: NONSTANDARD_MACRO_BRACES. Enforce the named macros always use the braces specified. <br> A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro is could be used with a full path two `MacroMatcher`s have to be added one with the full path `crate_name::macro_name` and one with just the macro name. |
212 | (standard_macro_braces: Vec<crate::nonstandard_macro_braces::MacroMatcher> = Vec::new()), | |
213 | /// Lint: MISSING_ENFORCED_IMPORT_RENAMES. The list of imports to always rename, a fully qualified path followed by the rename. | |
214 | (enforced_import_renames: Vec<crate::utils::conf::Rename> = Vec::new()), | |
215 | /// Lint: RESTRICTED_SCRIPTS. The list of unicode scripts allowed to be used in the scope. | |
216 | (allowed_scripts: Vec<String> = vec!["Latin".to_string()]), | |
f20569fa XL |
217 | } |
218 | ||
219 | /// Search for the configuration file. | |
220 | pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> { | |
221 | /// Possible filename to search for. | |
222 | const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"]; | |
223 | ||
224 | // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR. | |
225 | // If neither of those exist, use ".". | |
226 | let mut current = env::var_os("CLIPPY_CONF_DIR") | |
227 | .or_else(|| env::var_os("CARGO_MANIFEST_DIR")) | |
228 | .map_or_else(|| PathBuf::from("."), PathBuf::from); | |
229 | loop { | |
230 | for config_file_name in &CONFIG_FILE_NAMES { | |
17df50a5 XL |
231 | if let Ok(config_file) = current.join(config_file_name).canonicalize() { |
232 | match fs::metadata(&config_file) { | |
233 | Err(e) if e.kind() == io::ErrorKind::NotFound => {}, | |
234 | Err(e) => return Err(e), | |
235 | Ok(md) if md.is_dir() => {}, | |
236 | Ok(_) => return Ok(Some(config_file)), | |
237 | } | |
f20569fa XL |
238 | } |
239 | } | |
240 | ||
241 | // If the current directory has no parent, we're done searching. | |
242 | if !current.pop() { | |
243 | return Ok(None); | |
244 | } | |
245 | } | |
246 | } | |
247 | ||
f20569fa XL |
248 | /// Read the `toml` configuration file. |
249 | /// | |
250 | /// In case of error, the function tries to continue as much as possible. | |
17df50a5 | 251 | pub fn read(path: &Path) -> TryConf { |
f20569fa | 252 | let content = match fs::read_to_string(path) { |
17df50a5 | 253 | Err(e) => return TryConf::from_error(e), |
f20569fa | 254 | Ok(content) => content, |
f20569fa | 255 | }; |
17df50a5 | 256 | toml::from_str(&content).unwrap_or_else(TryConf::from_error) |
f20569fa | 257 | } |