clippy_config/
conf.rs

1use crate::ClippyConfiguration;
2use crate::types::{
3    DisallowedPath, DisallowedPathWithoutReplacement, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour,
4    Rename, SourceItemOrdering, SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings,
5    SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
6    SourceItemOrderingWithinModuleItemGroupings,
7};
8use clippy_utils::msrvs::Msrv;
9use itertools::Itertools;
10use rustc_errors::Applicability;
11use rustc_session::Session;
12use rustc_span::edit_distance::edit_distance;
13use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
14use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
15use serde::{Deserialize, Deserializer, Serialize};
16use std::collections::HashMap;
17use std::fmt::{Debug, Display, Formatter};
18use std::ops::Range;
19use std::path::PathBuf;
20use std::str::FromStr;
21use std::sync::OnceLock;
22use std::{cmp, env, fmt, fs, io};
23
24#[rustfmt::skip]
25const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
26    "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
27    "MHz", "GHz", "THz",
28    "AccessKit",
29    "CoAP", "CoreFoundation", "CoreGraphics", "CoreText",
30    "DevOps",
31    "Direct2D", "Direct3D", "DirectWrite", "DirectX",
32    "ECMAScript",
33    "GPLv2", "GPLv3",
34    "GitHub", "GitLab",
35    "IPv4", "IPv6",
36    "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript",
37    "WebAssembly",
38    "NaN", "NaNs",
39    "OAuth", "GraphQL",
40    "OCaml",
41    "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry",
42    "OpenType",
43    "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport",
44    "WebP", "OpenExr", "YCbCr", "sRGB",
45    "TensorFlow",
46    "TrueType",
47    "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD",
48    "TeX", "LaTeX", "BibTeX", "BibLaTeX",
49    "MinGW",
50    "CamelCase",
51];
52const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
53const DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z", "w", "n"];
54const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"];
55const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] =
56    &["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"];
57const DEFAULT_MODULE_ITEM_ORDERING_GROUPS: &[(&str, &[SourceItemOrderingModuleItemKind])] = {
58    #[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
59    use SourceItemOrderingModuleItemKind::*;
60    &[
61        ("modules", &[ExternCrate, Mod, ForeignMod]),
62        ("use", &[Use]),
63        ("macros", &[Macro]),
64        ("global_asm", &[GlobalAsm]),
65        ("UPPER_SNAKE_CASE", &[Static, Const]),
66        ("PascalCase", &[TyAlias, Enum, Struct, Union, Trait, TraitAlias, Impl]),
67        ("lower_snake_case", &[Fn]),
68    ]
69};
70const DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER: &[SourceItemOrderingTraitAssocItemKind] = {
71    #[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
72    use SourceItemOrderingTraitAssocItemKind::*;
73    &[Const, Type, Fn]
74};
75const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
76    #[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
77    use SourceItemOrderingCategory::*;
78    &[Enum, Impl, Module, Struct, Trait]
79};
80
81/// Conf with parse errors
82#[derive(Default)]
83struct TryConf {
84    conf: Conf,
85    value_spans: HashMap<String, Range<usize>>,
86    errors: Vec<ConfError>,
87    warnings: Vec<ConfError>,
88}
89
90impl TryConf {
91    fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
92        Self {
93            conf: Conf::default(),
94            value_spans: HashMap::default(),
95            errors: vec![ConfError::from_toml(file, error)],
96            warnings: vec![],
97        }
98    }
99}
100
101#[derive(Debug)]
102struct ConfError {
103    message: String,
104    suggestion: Option<Suggestion>,
105    span: Span,
106}
107
108impl ConfError {
109    fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
110        let span = error.span().unwrap_or(0..file.source_len.0 as usize);
111        Self::spanned(file, error.message(), None, span)
112    }
113
114    fn spanned(
115        file: &SourceFile,
116        message: impl Into<String>,
117        suggestion: Option<Suggestion>,
118        span: Range<usize>,
119    ) -> Self {
120        Self {
121            message: message.into(),
122            suggestion,
123            span: Span::new(
124                file.start_pos + BytePos::from_usize(span.start),
125                file.start_pos + BytePos::from_usize(span.end),
126                SyntaxContext::root(),
127                None,
128            ),
129        }
130    }
131}
132
133// Remove code tags and code behind '# 's, as they are not needed for the lint docs and --explain
134pub fn sanitize_explanation(raw_docs: &str) -> String {
135    // Remove tags and hidden code:
136    let mut explanation = String::with_capacity(128);
137    let mut in_code = false;
138    for line in raw_docs.lines() {
139        let line = line.strip_prefix(' ').unwrap_or(line);
140
141        if let Some(lang) = line.strip_prefix("```") {
142            let tag = lang.split_once(',').map_or(lang, |(left, _)| left);
143            if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") {
144                explanation += "```rust\n";
145            } else {
146                explanation += line;
147                explanation.push('\n');
148            }
149            in_code = !in_code;
150        } else if !(in_code && line.starts_with("# ")) {
151            explanation += line;
152            explanation.push('\n');
153        }
154    }
155
156    explanation
157}
158
159macro_rules! wrap_option {
160    () => {
161        None
162    };
163    ($x:literal) => {
164        Some($x)
165    };
166}
167
168macro_rules! default_text {
169    ($value:expr) => {{
170        let mut text = String::new();
171        $value.serialize(toml::ser::ValueSerializer::new(&mut text)).unwrap();
172        text
173    }};
174    ($value:expr, $override:expr) => {
175        $override.to_string()
176    };
177}
178
179macro_rules! define_Conf {
180    ($(
181        $(#[doc = $doc:literal])+
182        $(#[conf_deprecated($dep:literal, $new_conf:ident)])?
183        $(#[default_text = $default_text:expr])?
184        $(#[lints($($for_lints:ident),* $(,)?)])?
185        $name:ident: $ty:ty = $default:expr,
186    )*) => {
187        /// Clippy lint configuration
188        pub struct Conf {
189            $($(#[cfg_attr(doc, doc = $doc)])+ pub $name: $ty,)*
190        }
191
192        mod defaults {
193            use super::*;
194            $(pub fn $name() -> $ty { $default })*
195        }
196
197        impl Default for Conf {
198            fn default() -> Self {
199                Self { $($name: defaults::$name(),)* }
200            }
201        }
202
203        #[derive(Deserialize)]
204        #[serde(field_identifier, rename_all = "kebab-case")]
205        #[allow(non_camel_case_types)]
206        enum Field { $($name,)* third_party, }
207
208        struct ConfVisitor<'a>(&'a SourceFile);
209
210        impl<'de> Visitor<'de> for ConfVisitor<'_> {
211            type Value = TryConf;
212
213            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
214                formatter.write_str("Conf")
215            }
216
217            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
218                let mut value_spans = HashMap::new();
219                let mut errors = Vec::new();
220                let mut warnings = Vec::new();
221                $(let mut $name = None;)*
222                // could get `Field` here directly, but get `String` first for diagnostics
223                while let Some(name) = map.next_key::<toml::Spanned<String>>()? {
224                    match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
225                        Err(e) => {
226                            let e: FieldError = e;
227                            errors.push(ConfError::spanned(self.0, e.error, e.suggestion, name.span()));
228                        }
229                        $(Ok(Field::$name) => {
230                            $(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
231                            let raw_value = map.next_value::<toml::Spanned<toml::Value>>()?;
232                            let value_span = raw_value.span();
233                            match <$ty>::deserialize(raw_value.into_inner()) {
234                                Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), None, value_span)),
235                                Ok(value) => match $name {
236                                    Some(_) => {
237                                        errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
238                                    }
239                                    None => {
240                                        $name = Some(value);
241                                        value_spans.insert(name.get_ref().as_str().to_string(), value_span);
242                                        // $new_conf is the same as one of the defined `$name`s, so
243                                        // this variable is defined in line 2 of this function.
244                                        $(match $new_conf {
245                                            Some(_) => errors.push(ConfError::spanned(self.0, concat!(
246                                                "duplicate field `", stringify!($new_conf),
247                                                "` (provided as `", stringify!($name), "`)"
248                                            ), None, name.span())),
249                                            None => $new_conf = $name.clone(),
250                                        })?
251                                    },
252                                }
253                            }
254                        })*
255                        // ignore contents of the third_party key
256                        Ok(Field::third_party) => drop(map.next_value::<IgnoredAny>())
257                    }
258                }
259                let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
260                Ok(TryConf { conf, value_spans, errors, warnings })
261            }
262        }
263
264        pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
265            vec![$(
266                ClippyConfiguration {
267                    name: stringify!($name).replace('_', "-"),
268                    default: default_text!(defaults::$name() $(, $default_text)?),
269                    lints: &[$($(stringify!($for_lints)),*)?],
270                    doc: concat!($($doc, '\n',)*),
271                    deprecation_reason: wrap_option!($($dep)?)
272                },
273            )*]
274        }
275    };
276}
277
278define_Conf! {
279    /// Which crates to allow absolute paths from
280    #[lints(absolute_paths)]
281    absolute_paths_allowed_crates: Vec<String> = Vec::new(),
282    /// The maximum number of segments a path can have before being linted, anything above this will
283    /// be linted.
284    #[lints(absolute_paths)]
285    absolute_paths_max_segments: u64 = 2,
286    /// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block
287    #[lints(undocumented_unsafe_blocks)]
288    accept_comment_above_attributes: bool = true,
289    /// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block
290    #[lints(undocumented_unsafe_blocks)]
291    accept_comment_above_statement: bool = true,
292    /// Don't lint when comparing the result of a modulo operation to zero.
293    #[lints(modulo_arithmetic)]
294    allow_comparison_to_zero: bool = true,
295    /// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]`
296    #[lints(dbg_macro)]
297    allow_dbg_in_tests: bool = false,
298    /// Whether `expect` should be allowed in code always evaluated at compile time
299    #[lints(expect_used)]
300    allow_expect_in_consts: bool = true,
301    /// Whether `expect` should be allowed in test functions or `#[cfg(test)]`
302    #[lints(expect_used)]
303    allow_expect_in_tests: bool = false,
304    /// Whether `indexing_slicing` should be allowed in test functions or `#[cfg(test)]`
305    #[lints(indexing_slicing)]
306    allow_indexing_slicing_in_tests: bool = false,
307    /// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`
308    #[lints(uninlined_format_args)]
309    allow_mixed_uninlined_format_args: bool = true,
310    /// Whether to allow `r#""#` when `r""` can be used
311    #[lints(needless_raw_string_hashes)]
312    allow_one_hash_in_raw_strings: bool = false,
313    /// Whether `panic` should be allowed in test functions or `#[cfg(test)]`
314    #[lints(panic)]
315    allow_panic_in_tests: bool = false,
316    /// Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]`
317    #[lints(print_stderr, print_stdout)]
318    allow_print_in_tests: bool = false,
319    /// Whether to allow module inception if it's not public.
320    #[lints(module_inception)]
321    allow_private_module_inception: bool = false,
322    /// List of trait paths to ignore when checking renamed function parameters.
323    ///
324    /// #### Example
325    ///
326    /// ```toml
327    /// allow-renamed-params-for = [ "std::convert::From" ]
328    /// ```
329    ///
330    /// #### Noteworthy
331    ///
332    /// - By default, the following traits are ignored: `From`, `TryFrom`, `FromStr`
333    /// - `".."` can be used as part of the list to indicate that the configured values should be appended to the
334    /// default configuration of Clippy. By default, any configuration will replace the default value.
335    #[lints(renamed_function_params)]
336    allow_renamed_params_for: Vec<String> =
337        DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect(),
338    /// Whether `unwrap` should be allowed in code always evaluated at compile time
339    #[lints(unwrap_used)]
340    allow_unwrap_in_consts: bool = true,
341    /// Whether `unwrap` should be allowed in test functions or `#[cfg(test)]`
342    #[lints(unwrap_used)]
343    allow_unwrap_in_tests: bool = false,
344    /// Whether `useless_vec` should ignore test functions or `#[cfg(test)]`
345    #[lints(useless_vec)]
346    allow_useless_vec_in_tests: bool = false,
347    /// Additional dotfiles (files or directories starting with a dot) to allow
348    #[lints(path_ends_with_ext)]
349    allowed_dotfiles: Vec<String> = Vec::default(),
350    /// A list of crate names to allow duplicates of
351    #[lints(multiple_crate_versions)]
352    allowed_duplicate_crates: Vec<String> = Vec::new(),
353    /// Allowed names below the minimum allowed characters. The value `".."` can be used as part of
354    /// the list to indicate, that the configured values should be appended to the default
355    /// configuration of Clippy. By default, any configuration will replace the default value.
356    #[lints(min_ident_chars)]
357    allowed_idents_below_min_chars: Vec<String> =
358        DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect(),
359    /// List of prefixes to allow when determining whether an item's name ends with the module's name.
360    /// If the rest of an item's name is an allowed prefix (e.g. item `ToFoo` or `to_foo` in module `foo`),
361    /// then don't emit a warning.
362    ///
363    /// #### Example
364    ///
365    /// ```toml
366    /// allowed-prefixes = [ "to", "from" ]
367    /// ```
368    ///
369    /// #### Noteworthy
370    ///
371    /// - By default, the following prefixes are allowed: `to`, `as`, `into`, `from`, `try_into` and `try_from`
372    /// - PascalCase variant is included automatically for each snake_case variant (e.g. if `try_into` is included,
373    ///   `TryInto` will also be included)
374    /// - Use `".."` as part of the list to indicate that the configured values should be appended to the
375    /// default configuration of Clippy. By default, any configuration will replace the default value
376    #[lints(module_name_repetitions)]
377    allowed_prefixes: Vec<String> = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect(),
378    /// The list of unicode scripts allowed to be used in the scope.
379    #[lints(disallowed_script_idents)]
380    allowed_scripts: Vec<String> = vec!["Latin".to_string()],
381    /// List of path segments allowed to have wildcard imports.
382    ///
383    /// #### Example
384    ///
385    /// ```toml
386    /// allowed-wildcard-imports = [ "utils", "common" ]
387    /// ```
388    ///
389    /// #### Noteworthy
390    ///
391    /// 1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`.
392    /// 2. Paths with any segment that containing the word 'prelude'
393    /// are already allowed by default.
394    #[lints(wildcard_imports)]
395    allowed_wildcard_imports: Vec<String> = Vec::new(),
396    /// Suppress checking of the passed type names in all types of operations.
397    ///
398    /// If a specific operation is desired, consider using `arithmetic_side_effects_allowed_binary` or `arithmetic_side_effects_allowed_unary` instead.
399    ///
400    /// #### Example
401    ///
402    /// ```toml
403    /// arithmetic-side-effects-allowed = ["SomeType", "AnotherType"]
404    /// ```
405    ///
406    /// #### Noteworthy
407    ///
408    /// A type, say `SomeType`, listed in this configuration has the same behavior of
409    /// `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`.
410    #[lints(arithmetic_side_effects)]
411    arithmetic_side_effects_allowed: Vec<String> = <_>::default(),
412    /// Suppress checking of the passed type pair names in binary operations like addition or
413    /// multiplication.
414    ///
415    /// Supports the "*" wildcard to indicate that a certain type won't trigger the lint regardless
416    /// of the involved counterpart. For example, `["SomeType", "*"]` or `["*", "AnotherType"]`.
417    ///
418    /// Pairs are asymmetric, which means that `["SomeType", "AnotherType"]` is not the same as
419    /// `["AnotherType", "SomeType"]`.
420    ///
421    /// #### Example
422    ///
423    /// ```toml
424    /// arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType", "*"]]
425    /// ```
426    #[lints(arithmetic_side_effects)]
427    arithmetic_side_effects_allowed_binary: Vec<(String, String)> = <_>::default(),
428    /// Suppress checking of the passed type names in unary operations like "negation" (`-`).
429    ///
430    /// #### Example
431    ///
432    /// ```toml
433    /// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
434    /// ```
435    #[lints(arithmetic_side_effects)]
436    arithmetic_side_effects_allowed_unary: Vec<String> = <_>::default(),
437    /// The maximum allowed size for arrays on the stack
438    #[lints(large_const_arrays, large_stack_arrays)]
439    array_size_threshold: u64 = 16 * 1024,
440    /// Suppress lints whenever the suggested change would cause breakage for other crates.
441    #[lints(
442        box_collection,
443        enum_variant_names,
444        large_types_passed_by_value,
445        linkedlist,
446        needless_pass_by_ref_mut,
447        option_option,
448        owned_cow,
449        rc_buffer,
450        rc_mutex,
451        redundant_allocation,
452        ref_option,
453        single_call_fn,
454        trivially_copy_pass_by_ref,
455        unnecessary_box_returns,
456        unnecessary_wraps,
457        unused_self,
458        upper_case_acronyms,
459        vec_box,
460        wrong_self_convention,
461    )]
462    avoid_breaking_exported_api: bool = true,
463    /// The list of types which may not be held across an await point.
464    #[lints(await_holding_invalid_type)]
465    await_holding_invalid_types: Vec<DisallowedPathWithoutReplacement> = Vec::new(),
466    /// DEPRECATED LINT: BLACKLISTED_NAME.
467    ///
468    /// Use the Disallowed Names lint instead
469    #[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)]
470    blacklisted_names: Vec<String> = Vec::new(),
471    /// For internal testing only, ignores the current `publish` settings in the Cargo manifest.
472    #[lints(cargo_common_metadata)]
473    cargo_ignore_publish: bool = false,
474    /// Whether to check MSRV compatibility in `#[test]` and `#[cfg(test)]` code.
475    #[lints(incompatible_msrv)]
476    check_incompatible_msrv_in_tests: bool = false,
477    /// Whether to also run the listed lints on private items.
478    #[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
479    check_private_items: bool = false,
480    /// The maximum cognitive complexity a function can have
481    #[lints(cognitive_complexity)]
482    cognitive_complexity_threshold: u64 = 25,
483    /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY.
484    ///
485    /// Use the Cognitive Complexity lint instead.
486    #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
487    cyclomatic_complexity_threshold: u64 = 25,
488    /// The list of disallowed macros, written as fully qualified paths.
489    #[lints(disallowed_macros)]
490    disallowed_macros: Vec<DisallowedPath> = Vec::new(),
491    /// The list of disallowed methods, written as fully qualified paths.
492    #[lints(disallowed_methods)]
493    disallowed_methods: Vec<DisallowedPath> = Vec::new(),
494    /// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
495    /// `".."` can be used as part of the list to indicate that the configured values should be appended to the
496    /// default configuration of Clippy. By default, any configuration will replace the default value.
497    #[lints(disallowed_names)]
498    disallowed_names: Vec<String> = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(),
499    /// The list of disallowed types, written as fully qualified paths.
500    #[lints(disallowed_types)]
501    disallowed_types: Vec<DisallowedPath> = Vec::new(),
502    /// The list of words this lint should not consider as identifiers needing ticks. The value
503    /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
504    /// default configuration of Clippy. By default, any configuration will replace the default value. For example:
505    /// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
506    /// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
507    #[lints(doc_markdown)]
508    doc_valid_idents: Vec<String> = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect(),
509    /// Whether to apply the raw pointer heuristic to determine if a type is `Send`.
510    #[lints(non_send_fields_in_send_ty)]
511    enable_raw_pointer_heuristic_for_send: bool = true,
512    /// Whether to recommend using implicit into iter for reborrowed values.
513    ///
514    /// #### Example
515    /// ```no_run
516    /// let mut vec = vec![1, 2, 3];
517    /// let rmvec = &mut vec;
518    /// for _ in rmvec.iter() {}
519    /// for _ in rmvec.iter_mut() {}
520    /// ```
521    ///
522    /// Use instead:
523    /// ```no_run
524    /// let mut vec = vec![1, 2, 3];
525    /// let rmvec = &mut vec;
526    /// for _ in &*rmvec {}
527    /// for _ in &mut *rmvec {}
528    /// ```
529    #[lints(explicit_iter_loop)]
530    enforce_iter_loop_reborrow: bool = false,
531    /// The list of imports to always rename, a fully qualified path followed by the rename.
532    #[lints(missing_enforced_import_renames)]
533    enforced_import_renames: Vec<Rename> = Vec::new(),
534    /// The minimum number of enum variants for the lints about variant names to trigger
535    #[lints(enum_variant_names)]
536    enum_variant_name_threshold: u64 = 3,
537    /// The maximum size of an enum's variant to avoid box suggestion
538    #[lints(large_enum_variant)]
539    enum_variant_size_threshold: u64 = 200,
540    /// The maximum amount of nesting a block can reside in
541    #[lints(excessive_nesting)]
542    excessive_nesting_threshold: u64 = 0,
543    /// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
544    #[lints(large_futures)]
545    future_size_threshold: u64 = 16 * 1024,
546    /// A list of paths to types that should be treated as if they do not contain interior mutability
547    #[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)]
548    ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()]),
549    /// The maximum size of the `Err`-variant in a `Result` returned from a function
550    #[lints(result_large_err)]
551    large_error_threshold: u64 = 128,
552    /// Whether to suggest reordering constructor fields when initializers are present.
553    ///
554    /// Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
555    /// suggested code would compile, it can change semantics if the initializer expressions have side effects. The
556    /// following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
557    ///
558    /// ```rust
559    /// struct MyStruct {
560    ///     vector: Vec<u32>,
561    ///     length: usize
562    /// }
563    /// fn main() {
564    ///     let vector = vec![1,2,3];
565    ///     MyStruct { length: vector.len(), vector};
566    /// }
567    /// ```
568    ///
569    /// [from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
570    #[lints(inconsistent_struct_constructor)]
571    lint_inconsistent_struct_field_initializers: bool = false,
572    /// The lower bound for linting decimal literals
573    #[lints(decimal_literal_representation)]
574    literal_representation_threshold: u64 = 16384,
575    /// Whether the matches should be considered by the lint, and whether there should
576    /// be filtering for common types.
577    #[lints(manual_let_else)]
578    matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes,
579    /// The maximum number of bool parameters a function can have
580    #[lints(fn_params_excessive_bools)]
581    max_fn_params_bools: u64 = 3,
582    /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes
583    #[lints(large_include_file)]
584    max_include_file_size: u64 = 1_000_000,
585    /// The maximum number of bool fields a struct can have
586    #[lints(struct_excessive_bools)]
587    max_struct_bools: u64 = 3,
588    /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
589    /// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed.
590    /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
591    #[lints(index_refutable_slice)]
592    max_suggested_slice_pattern_length: u64 = 3,
593    /// The maximum number of bounds a trait can have to be linted
594    #[lints(type_repetition_in_bounds)]
595    max_trait_bounds: u64 = 3,
596    /// Minimum chars an ident can have, anything below or equal to this will be linted.
597    #[lints(min_ident_chars)]
598    min_ident_chars_threshold: u64 = 1,
599    /// Whether to **only** check for missing documentation in items visible within the current
600    /// crate. For example, `pub(crate)` items.
601    #[lints(missing_docs_in_private_items)]
602    missing_docs_in_crate_items: bool = false,
603    /// The named groupings of different source item kinds within modules.
604    #[lints(arbitrary_source_item_ordering)]
605    module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(),
606    /// Whether the items within module groups should be ordered alphabetically or not.
607    ///
608    /// This option can be configured to "all", "none", or a list of specific grouping names that should be checked
609    /// (e.g. only "enums").
610    #[lints(arbitrary_source_item_ordering)]
611    module_items_ordered_within_groupings: SourceItemOrderingWithinModuleItemGroupings =
612        SourceItemOrderingWithinModuleItemGroupings::None,
613    /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
614    #[default_text = "current version"]
615    #[lints(
616        allow_attributes,
617        allow_attributes_without_reason,
618        almost_complete_range,
619        approx_constant,
620        assigning_clones,
621        borrow_as_ptr,
622        cast_abs_to_unsigned,
623        checked_conversions,
624        cloned_instead_of_copied,
625        collapsible_match,
626        collapsible_str_replace,
627        deprecated_cfg_attr,
628        derivable_impls,
629        err_expect,
630        filter_map_next,
631        from_over_into,
632        if_then_some_else_none,
633        index_refutable_slice,
634        io_other_error,
635        iter_kv_map,
636        legacy_numeric_constants,
637        lines_filter_map_ok,
638        manual_bits,
639        manual_c_str_literals,
640        manual_clamp,
641        manual_div_ceil,
642        manual_flatten,
643        manual_hash_one,
644        manual_is_ascii_check,
645        manual_let_else,
646        manual_midpoint,
647        manual_non_exhaustive,
648        manual_option_as_slice,
649        manual_pattern_char_comparison,
650        manual_range_contains,
651        manual_rem_euclid,
652        manual_repeat_n,
653        manual_retain,
654        manual_slice_fill,
655        manual_split_once,
656        manual_str_repeat,
657        manual_strip,
658        manual_try_fold,
659        map_clone,
660        map_unwrap_or,
661        map_with_unused_argument_over_ranges,
662        match_like_matches_macro,
663        mem_replace_option_with_some,
664        mem_replace_with_default,
665        missing_const_for_fn,
666        needless_borrow,
667        non_std_lazy_statics,
668        option_as_ref_deref,
669        option_map_unwrap_or,
670        ptr_as_ptr,
671        question_mark,
672        redundant_field_names,
673        redundant_static_lifetimes,
674        repeat_vec_with_capacity,
675        same_item_push,
676        seek_from_current,
677        seek_rewind,
678        transmute_ptr_to_ref,
679        tuple_array_conversions,
680        type_repetition_in_bounds,
681        unchecked_duration_subtraction,
682        uninlined_format_args,
683        unnecessary_lazy_evaluations,
684        unnested_or_patterns,
685        unused_trait_names,
686        use_self,
687    )]
688    msrv: Msrv = Msrv::default(),
689    /// The minimum size (in bytes) to consider a type for passing by reference instead of by value.
690    #[lints(large_types_passed_by_value)]
691    pass_by_value_size_limit: u64 = 256,
692    /// Lint "public" fields in a struct that are prefixed with an underscore based on their
693    /// exported visibility, or whether they are marked as "pub".
694    #[lints(pub_underscore_fields)]
695    pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported,
696    /// Whether to lint only if it's multiline.
697    #[lints(semicolon_inside_block)]
698    semicolon_inside_block_ignore_singleline: bool = false,
699    /// Whether to lint only if it's singleline.
700    #[lints(semicolon_outside_block)]
701    semicolon_outside_block_ignore_multiline: bool = false,
702    /// The maximum number of single char bindings a scope may have
703    #[lints(many_single_char_names)]
704    single_char_binding_names_threshold: u64 = 4,
705    /// Which kind of elements should be ordered internally, possible values being `enum`, `impl`, `module`, `struct`, `trait`.
706    #[lints(arbitrary_source_item_ordering)]
707    source_item_ordering: SourceItemOrdering = DEFAULT_SOURCE_ITEM_ORDERING.into(),
708    /// The maximum allowed stack size for functions in bytes
709    #[lints(large_stack_frames)]
710    stack_size_threshold: u64 = 512_000,
711    /// Enforce the named macros always use the braces specified.
712    ///
713    /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
714    /// could be used with a full path two `MacroMatcher`s have to be added one with the full path
715    /// `crate_name::macro_name` and one with just the macro name.
716    #[lints(nonstandard_macro_braces)]
717    standard_macro_braces: Vec<MacroMatcher> = Vec::new(),
718    /// The minimum number of struct fields for the lints about field names to trigger
719    #[lints(struct_field_names)]
720    struct_field_name_threshold: u64 = 3,
721    /// Whether to suppress a restriction lint in constant code. In same
722    /// cases the restructured operation might not be unavoidable, as the
723    /// suggested counterparts are unavailable in constant code. This
724    /// configuration will cause restriction lints to trigger even
725    /// if no suggestion can be made.
726    #[lints(indexing_slicing)]
727    suppress_restriction_lint_in_const: bool = false,
728    /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
729    #[lints(boxed_local, useless_vec)]
730    too_large_for_stack: u64 = 200,
731    /// The maximum number of argument a function or method can have
732    #[lints(too_many_arguments)]
733    too_many_arguments_threshold: u64 = 7,
734    /// The maximum number of lines a function or method can have
735    #[lints(too_many_lines)]
736    too_many_lines_threshold: u64 = 100,
737    /// The order of associated items in traits.
738    #[lints(arbitrary_source_item_ordering)]
739    trait_assoc_item_kinds_order: SourceItemOrderingTraitAssocItemKinds = DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER.into(),
740    /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
741    /// reference.
742    #[default_text = "target_pointer_width * 2"]
743    #[lints(trivially_copy_pass_by_ref)]
744    trivial_copy_size_limit: Option<u64> = None,
745    /// The maximum complexity a type can have
746    #[lints(type_complexity)]
747    type_complexity_threshold: u64 = 250,
748    /// The byte size a `T` in `Box<T>` can have, below which it triggers the `clippy::unnecessary_box` lint
749    #[lints(unnecessary_box_returns)]
750    unnecessary_box_size: u64 = 128,
751    /// Should the fraction of a decimal be linted to include separators.
752    #[lints(unreadable_literal)]
753    unreadable_literal_lint_fractions: bool = true,
754    /// Enables verbose mode. Triggers if there is more than one uppercase char next to each other
755    #[lints(upper_case_acronyms)]
756    upper_case_acronyms_aggressive: bool = false,
757    /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed
758    #[lints(vec_box)]
759    vec_box_size_threshold: u64 = 4096,
760    /// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
761    #[lints(verbose_bit_mask)]
762    verbose_bit_mask_threshold: u64 = 1,
763    /// Whether to allow certain wildcard imports (prelude, super in tests).
764    #[lints(wildcard_imports)]
765    warn_on_all_wildcard_imports: bool = false,
766    /// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros.
767    #[lints(macro_metavars_in_unsafe)]
768    warn_unsafe_macro_metavars_in_private_macros: bool = false,
769}
770
771/// Search for the configuration file.
772///
773/// # Errors
774///
775/// Returns any unexpected filesystem error encountered when searching for the config file
776pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
777    /// Possible filename to search for.
778    const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
779
780    // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
781    // If neither of those exist, use ".". (Update documentation if this priority changes)
782    let mut current = env::var_os("CLIPPY_CONF_DIR")
783        .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
784        .map_or_else(|| PathBuf::from("."), PathBuf::from)
785        .canonicalize()?;
786
787    let mut found_config: Option<PathBuf> = None;
788    let mut warnings = vec![];
789
790    loop {
791        for config_file_name in &CONFIG_FILE_NAMES {
792            if let Ok(config_file) = current.join(config_file_name).canonicalize() {
793                match fs::metadata(&config_file) {
794                    Err(e) if e.kind() == io::ErrorKind::NotFound => {},
795                    Err(e) => return Err(e),
796                    Ok(md) if md.is_dir() => {},
797                    Ok(_) => {
798                        // warn if we happen to find two config files #8323
799                        if let Some(ref found_config) = found_config {
800                            warnings.push(format!(
801                                "using config file `{}`, `{}` will be ignored",
802                                found_config.display(),
803                                config_file.display()
804                            ));
805                        } else {
806                            found_config = Some(config_file);
807                        }
808                    },
809                }
810            }
811        }
812
813        if found_config.is_some() {
814            return Ok((found_config, warnings));
815        }
816
817        // If the current directory has no parent, we're done searching.
818        if !current.pop() {
819            return Ok((None, warnings));
820        }
821    }
822}
823
824fn deserialize(file: &SourceFile) -> TryConf {
825    match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(file)) {
826        Ok(mut conf) => {
827            extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
828            extend_vec_if_indicator_present(&mut conf.conf.allowed_prefixes, DEFAULT_ALLOWED_PREFIXES);
829            extend_vec_if_indicator_present(
830                &mut conf.conf.allow_renamed_params_for,
831                DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS,
832            );
833
834            // Confirms that the user has not accidentally configured ordering requirements for groups that
835            // aren't configured.
836            if let SourceItemOrderingWithinModuleItemGroupings::Custom(groupings) =
837                &conf.conf.module_items_ordered_within_groupings
838            {
839                for grouping in groupings {
840                    if !conf.conf.module_item_order_groupings.is_grouping(grouping) {
841                        // Since this isn't fixable by rustfix, don't emit a `Suggestion`. This just adds some useful
842                        // info for the user instead.
843
844                        let names = conf.conf.module_item_order_groupings.grouping_names();
845                        let suggestion = suggest_candidate(grouping, names.iter().map(String::as_str))
846                            .map(|s| format!(" perhaps you meant `{s}`?"))
847                            .unwrap_or_default();
848                        let names = names.iter().map(|s| format!("`{s}`")).join(", ");
849                        let message = format!(
850                            "unknown ordering group: `{grouping}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}"
851                        );
852
853                        let span = conf
854                            .value_spans
855                            .get("module_item_order_groupings")
856                            .cloned()
857                            .unwrap_or_default();
858                        conf.errors.push(ConfError::spanned(file, message, None, span));
859                    }
860                }
861            }
862
863            // TODO: THIS SHOULD BE TESTED, this comment will be gone soon
864            if conf.conf.allowed_idents_below_min_chars.iter().any(|e| e == "..") {
865                conf.conf
866                    .allowed_idents_below_min_chars
867                    .extend(DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string));
868            }
869            if conf.conf.doc_valid_idents.iter().any(|e| e == "..") {
870                conf.conf
871                    .doc_valid_idents
872                    .extend(DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string));
873            }
874
875            conf
876        },
877        Err(e) => TryConf::from_toml_error(file, &e),
878    }
879}
880
881fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
882    if vec.contains(&"..".to_string()) {
883        vec.extend(default.iter().map(ToString::to_string));
884    }
885}
886
887impl Conf {
888    pub fn read(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> &'static Conf {
889        static CONF: OnceLock<Conf> = OnceLock::new();
890        CONF.get_or_init(|| Conf::read_inner(sess, path))
891    }
892
893    fn read_inner(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
894        match path {
895            Ok((_, warnings)) => {
896                for warning in warnings {
897                    sess.dcx().warn(warning.clone());
898                }
899            },
900            Err(error) => {
901                sess.dcx()
902                    .err(format!("error finding Clippy's configuration file: {error}"));
903            },
904        }
905
906        let TryConf {
907            mut conf,
908            value_spans: _,
909            errors,
910            warnings,
911        } = match path {
912            Ok((Some(path), _)) => match sess.source_map().load_file(path) {
913                Ok(file) => deserialize(&file),
914                Err(error) => {
915                    sess.dcx().err(format!("failed to read `{}`: {error}", path.display()));
916                    TryConf::default()
917                },
918            },
919            _ => TryConf::default(),
920        };
921
922        conf.msrv.read_cargo(sess);
923
924        // all conf errors are non-fatal, we just use the default conf in case of error
925        for error in errors {
926            let mut diag = sess.dcx().struct_span_err(
927                error.span,
928                format!("error reading Clippy's configuration file: {}", error.message),
929            );
930
931            if let Some(sugg) = error.suggestion {
932                diag.span_suggestion(error.span, sugg.message, sugg.suggestion, Applicability::MaybeIncorrect);
933            }
934
935            diag.emit();
936        }
937
938        for warning in warnings {
939            sess.dcx().span_warn(
940                warning.span,
941                format!("error reading Clippy's configuration file: {}", warning.message),
942            );
943        }
944
945        conf
946    }
947}
948
949const SEPARATOR_WIDTH: usize = 4;
950
951#[derive(Debug)]
952struct FieldError {
953    error: String,
954    suggestion: Option<Suggestion>,
955}
956
957#[derive(Debug)]
958struct Suggestion {
959    message: &'static str,
960    suggestion: &'static str,
961}
962
963impl std::error::Error for FieldError {}
964
965impl Display for FieldError {
966    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
967        f.pad(&self.error)
968    }
969}
970
971impl serde::de::Error for FieldError {
972    fn custom<T: Display>(msg: T) -> Self {
973        Self {
974            error: msg.to_string(),
975            suggestion: None,
976        }
977    }
978
979    fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
980        // List the available fields sorted and at least one per line, more if `CLIPPY_TERMINAL_WIDTH` is
981        // set and allows it.
982        use fmt::Write;
983
984        let mut expected = expected.to_vec();
985        expected.sort_unstable();
986
987        let (rows, column_widths) = calculate_dimensions(&expected);
988
989        let mut msg = format!("unknown field `{field}`, expected one of");
990        for row in 0..rows {
991            writeln!(msg).unwrap();
992            for (column, column_width) in column_widths.iter().copied().enumerate() {
993                let index = column * rows + row;
994                let field = expected.get(index).copied().unwrap_or_default();
995                write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap();
996            }
997        }
998
999        let suggestion = suggest_candidate(field, expected).map(|suggestion| Suggestion {
1000            message: "perhaps you meant",
1001            suggestion,
1002        });
1003
1004        Self { error: msg, suggestion }
1005    }
1006}
1007
1008fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
1009    let columns = env::var("CLIPPY_TERMINAL_WIDTH")
1010        .ok()
1011        .and_then(|s| <usize as FromStr>::from_str(&s).ok())
1012        .map_or(1, |terminal_width| {
1013            let max_field_width = fields.iter().map(|field| field.len()).max().unwrap();
1014            cmp::max(1, terminal_width / (SEPARATOR_WIDTH + max_field_width))
1015        });
1016
1017    let rows = fields.len().div_ceil(columns);
1018
1019    let column_widths = (0..columns)
1020        .map(|column| {
1021            if column < columns - 1 {
1022                (0..rows)
1023                    .map(|row| {
1024                        let index = column * rows + row;
1025                        let field = fields.get(index).copied().unwrap_or_default();
1026                        field.len()
1027                    })
1028                    .max()
1029                    .unwrap()
1030            } else {
1031                // Avoid adding extra space to the last column.
1032                0
1033            }
1034        })
1035        .collect::<Vec<_>>();
1036
1037    (rows, column_widths)
1038}
1039
1040/// Given a user-provided value that couldn't be matched to a known option, finds the most likely
1041/// candidate among candidates that the user might have meant.
1042fn suggest_candidate<'a, I>(value: &str, candidates: I) -> Option<&'a str>
1043where
1044    I: IntoIterator<Item = &'a str>,
1045{
1046    candidates
1047        .into_iter()
1048        .filter_map(|expected| {
1049            let dist = edit_distance(value, expected, 4)?;
1050            Some((dist, expected))
1051        })
1052        .min_by_key(|&(dist, _)| dist)
1053        .map(|(_, suggestion)| suggestion)
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058    use serde::de::IgnoredAny;
1059    use std::collections::{HashMap, HashSet};
1060    use std::fs;
1061    use walkdir::WalkDir;
1062
1063    #[test]
1064    fn configs_are_tested() {
1065        let mut names: HashSet<String> = crate::get_configuration_metadata()
1066            .into_iter()
1067            .map(|meta| meta.name.replace('_', "-"))
1068            .collect();
1069
1070        let toml_files = WalkDir::new("../tests")
1071            .into_iter()
1072            .map(Result::unwrap)
1073            .filter(|entry| entry.file_name() == "clippy.toml");
1074
1075        for entry in toml_files {
1076            let file = fs::read_to_string(entry.path()).unwrap();
1077            #[allow(clippy::zero_sized_map_values)]
1078            if let Ok(map) = toml::from_str::<HashMap<String, IgnoredAny>>(&file) {
1079                for name in map.keys() {
1080                    names.remove(name.as_str());
1081                }
1082            }
1083        }
1084
1085        assert!(
1086            names.is_empty(),
1087            "Configuration variable lacks test: {names:?}\nAdd a test to `tests/ui-toml`"
1088        );
1089    }
1090}