Skip to main content

clippy_config/
conf.rs

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