clippy_config/
conf.rs

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