clippy_config/
conf.rs

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