Skip to main content

clippy_config/
conf.rs

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