Skip to main content

clippy_config/
conf.rs

1use crate::ConfMetadata;
2use crate::de::{DeserializeOrDefault, DiagCtxt, FromDefault, create_value_list_msg, find_closest_match};
3use crate::types::{
4    DisallowedPath, DisallowedPathWithoutReplacement, InherentImplLintScope, MacroMatcher, MatchLintBehaviour,
5    PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering, SourceItemOrderingModuleItemGroupings,
6    SourceItemOrderingTraitAssocItemKinds, SourceItemOrderingWithinModuleItemGroupings, TraitImplItemOrder,
7};
8use rustc_attr_parsing::parse_version;
9use rustc_data_structures::fx::FxHashSet;
10use rustc_errors::Applicability;
11use rustc_hir::attrs::RustcVersion;
12use rustc_session::Session;
13use rustc_span::{Pos as _, SourceFile, Symbol};
14use std::path::PathBuf;
15use std::sync::{Arc, OnceLock};
16use std::{env, fs, io};
17use toml::de::DeTable;
18
19#[rustfmt::skip]
20static DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
21    "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
22    "MHz", "GHz", "THz",
23    "AccessKit",
24    "CoAP", "CoreFoundation", "CoreGraphics", "CoreText",
25    "DevOps",
26    "Direct2D", "Direct3D", "DirectWrite", "DirectX",
27    "ECMAScript",
28    "GPLv2", "GPLv3",
29    "GitHub", "GitLab",
30    "IPv4", "IPv6",
31    "InfiniBand", "RoCE",
32    "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript",
33    "PowerPC", "PowerShell", "WebAssembly",
34    "NaN", "NaNs",
35    "OAuth", "GraphQL",
36    "SQLite", "MySQL", "PostgreSQL", "MariaDB", "MongoDB",
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", "NixOS",
45    "TeX", "LaTeX", "BibTeX", "BibLaTeX",
46    "MinGW",
47    "CamelCase",
48];
49static DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
50static DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z", "w", "n"];
51static DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"];
52static DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] =
53    &["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"];
54
55static DEFAULT_ALLOWED_SCRIPTS: &[&str] = &["Latin"];
56static DEFAULT_IGNORE_INTERIOR_MUTABILITY: &[&str] = &["bytes::Bytes"];
57
58macro_rules! first_expr {
59    ($e:expr $(,$_e:expr)*) => {
60        $e
61    };
62}
63
64macro_rules! filtered_names {
65    (($($names:literal)*)) => { &[$($names),*] };
66    (($($names:literal)*) $name:literal $($rest:tt)*) => {
67        filtered_names!(($($names)* $name) $($rest)*)
68    };
69    (($($names:literal)*) $new_name:ident $name:literal $($rest:tt)*) => {
70        filtered_names!(($($names)*) $($rest)*)
71    };
72}
73
74macro_rules! define_Conf {
75    (
76        $(
77            $(#[doc = $doc:literal])*
78            $(#[default_text = $default_text:literal])?
79            $(#[rename = $new_name:ident])?
80            $(#[lints($($for_lints:ident),* $(,)?)])?
81            // The type must exist for regular fields and shouldn't exist for deprecated ones.
82            $name:ident($name_str:literal) $(: $ty:ty $(= $default:expr)?)?,
83        )*
84    ) => {
85        #[allow(non_camel_case_types)]
86        #[derive(Clone, Copy)]
87        enum ConfField {
88            $($name,)*
89            ThirdParty,
90        }
91        impl ConfField {
92            const NAMES: &'static [&'static str] = &[$($name_str,)* "third-party"];
93            const FIELDS: &'static [Self] = &[$(first_expr!($(Self::$new_name,)? Self::$name),)* Self::ThirdParty];
94            const SUGG_NAMES: &'static [&'static str] = filtered_names!(() $($($new_name)? $name_str)* "third-party");
95
96            fn name(self) -> &'static str {
97                Self::NAMES[self as usize]
98            }
99
100            fn new_field(self) -> Self {
101                Self::FIELDS[self as usize]
102            }
103
104            fn parse(s: &str) -> Option<Self> {
105                match s {
106                    $($name_str => Some(Self::$name),)*
107                    "third-party" => Some(Self::ThirdParty),
108                    _ => None,
109                }
110            }
111        }
112
113        /// Clippy lint configuration
114        pub struct Conf {
115            // TODO: emit documentation
116            $($(pub $name: $ty,)?)*
117        }
118
119        impl Default for Conf {
120            fn default() -> Self {
121                Self {
122                    $($($name: <$ty as FromDefault<_>>::from_default(first_expr!($($default,)? ())),)?)*
123                }
124            }
125        }
126
127        impl Conf {
128            pub fn get_metadata() -> Vec<ConfMetadata> {
129                vec![$(
130                    ConfMetadata {
131                        name: $name_str,
132                        default: first_expr!(
133                            $($default_text.into(),)?
134                            $(<$ty as FromDefault<_>>::display_default(first_expr!($($default,)? ())).to_string(),)?
135                            String::new()
136                        ),
137                        lints: &[$($(stringify!($for_lints)),*)?],
138                        doc: concat!($($doc, '\n',)*),
139                        renamed_to: first_expr!($(Some(ConfField::$new_name.name()),)? None),
140                    },
141                )*]
142            }
143
144            fn deserialize(dcx: &DiagCtxt<'_>, table: &toml::de::DeTable<'_>) -> Self {
145                $($(let mut $name: Option<$ty> = None;)?)*
146
147                for (key, value) in table.iter() {
148                    let Some(mut conf_key) = ConfField::parse(key.get_ref()) else {
149                        let sp = dcx.make_sp(key.span());
150                        let mut diag = dcx.inner.struct_span_err(sp, "unknown field name");
151                        if let Some(sugg) = find_closest_match(key.get_ref(), ConfField::SUGG_NAMES) {
152                            diag.span_suggestion(sp, "did you mean", sugg, Applicability::MaybeIncorrect);
153                        }
154                        diag.note_once(create_value_list_msg(dcx, ConfField::SUGG_NAMES));
155                        diag.emit();
156                        continue;
157                    };
158                    loop {
159                        match conf_key {
160                            $($(ConfField::$name => {
161                                // Duplicate keys are handled by the toml parser.
162                                $name = Some(
163                                    <$ty as DeserializeOrDefault<_>>::deserialize_or_default(
164                                        dcx,
165                                        value.into(),
166                                        first_expr!($($default,)? ()),
167                                    ),
168                                );
169                            },)?)*
170                            ConfField::ThirdParty => {},
171                            // All deprecated fields.
172                            _ => {
173                                let sp = dcx.make_sp(table.get_key_value(key).unwrap().0.span());
174                                conf_key = conf_key.new_field();
175                                let other_value = table.get_key_value(conf_key.name());
176                                dcx.inner.struct_span_warn(sp, format!("use of a deprecated field"))
177                                    .with_span_suggestion(
178                                    sp, "use new name", conf_key.name(),
179                                    if other_value.is_some() {
180                                        Applicability::MaybeIncorrect
181                                    } else {
182                                        Applicability::MachineApplicable
183                                    }
184                                ).emit();
185
186                                if let Some((other_key, _)) = other_value {
187                                    dcx.inner.struct_span_err(sp, format!("duplicate key in document root"))
188                                        .with_span_note(dcx.make_sp(other_key.span()), "previous definition here")
189                                        .emit();
190                                } else {
191                                    continue;
192                                }
193                            },
194                        }
195                        break;
196                    }
197                }
198
199                Self {$($(
200                    $name: $name.unwrap_or_else(
201                        || <$ty as FromDefault<_>>::from_default(first_expr!($($default,)? ()))
202                    ),
203                )?)*}
204            }
205        }
206
207        #[test]
208        fn check_conf_order() {
209            for [x, y] in ConfField::NAMES[..ConfField::NAMES.len() - 1].array_windows::<2>() {
210                assert!(x <= y, "configuration `{x}` and `{y}` are out of order");
211            }
212        }
213
214        #[test]
215        fn check_conf_names() {$(
216            assert_eq!(stringify!($name).replace('_', "-"), $name_str);
217        )*}
218    };
219}
220
221define_Conf! {
222    /// Which crates to allow absolute paths from
223    #[lints(absolute_paths)]
224    absolute_paths_allowed_crates("absolute-paths-allowed-crates"): FxHashSet<Symbol>,
225    /// The maximum number of segments a path can have before being linted, anything above this will
226    /// be linted.
227    #[lints(absolute_paths)]
228    absolute_paths_max_segments("absolute-paths-max-segments"): u64 = 2,
229    /// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block
230    #[lints(undocumented_unsafe_blocks)]
231    accept_comment_above_attributes("accept-comment-above-attributes"): bool = true,
232    /// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block
233    #[lints(undocumented_unsafe_blocks)]
234    accept_comment_above_statement("accept-comment-above-statement"): bool = true,
235    /// Don't lint when comparing the result of a modulo operation to zero.
236    #[lints(modulo_arithmetic)]
237    allow_comparison_to_zero("allow-comparison-to-zero"): bool = true,
238    /// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]`
239    #[lints(dbg_macro)]
240    allow_dbg_in_tests("allow-dbg-in-tests"): bool = false,
241    /// Whether an item should be allowed to have the same name as its containing module
242    #[lints(module_name_repetitions)]
243    allow_exact_repetitions("allow-exact-repetitions"): bool = true,
244    /// Whether `expect` should be allowed in code always evaluated at compile time
245    #[lints(expect_used)]
246    allow_expect_in_consts("allow-expect-in-consts"): bool = true,
247    /// Whether `expect` should be allowed in test functions or `#[cfg(test)]`
248    #[lints(expect_used)]
249    allow_expect_in_tests("allow-expect-in-tests"): bool = false,
250    /// Whether `indexing_slicing` should be allowed in test functions or `#[cfg(test)]`
251    #[lints(indexing_slicing)]
252    allow_indexing_slicing_in_tests("allow-indexing-slicing-in-tests"): bool = false,
253    /// Whether functions inside `#[cfg(test)]` modules or test functions should be checked.
254    #[lints(large_stack_frames)]
255    allow_large_stack_frames_in_tests("allow-large-stack-frames-in-tests"): bool = true,
256    /// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`
257    #[lints(uninlined_format_args)]
258    allow_mixed_uninlined_format_args("allow-mixed-uninlined-format-args"): bool = true,
259    /// Whether to allow `r#""#` when `r""` can be used
260    #[lints(needless_raw_string_hashes)]
261    allow_one_hash_in_raw_strings("allow-one-hash-in-raw-strings"): bool = false,
262    /// Whether `panic` should be allowed in test functions or `#[cfg(test)]`
263    #[lints(panic)]
264    allow_panic_in_tests("allow-panic-in-tests"): bool = false,
265    /// Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]`
266    #[lints(print_stderr, print_stdout)]
267    allow_print_in_tests("allow-print-in-tests"): bool = false,
268    /// Whether to allow module inception if it's not public.
269    #[lints(module_inception)]
270    allow_private_module_inception("allow-private-module-inception"): bool = false,
271    /// List of trait paths to ignore when checking renamed function parameters.
272    ///
273    /// #### Example
274    ///
275    /// ```toml
276    /// allow-renamed-params-for = [ "std::convert::From" ]
277    /// ```
278    ///
279    /// #### Noteworthy
280    ///
281    /// - By default, the following traits are ignored: `From`, `TryFrom`, `FromStr`
282    /// - `".."` can be used as part of the list to indicate that the configured values should be appended to the
283    /// default configuration of Clippy. By default, any configuration will replace the default value.
284    #[lints(renamed_function_params)]
285    allow_renamed_params_for("allow-renamed-params-for"): Vec<String> = DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS,
286    /// Whether `unwrap` should be allowed in code always evaluated at compile time
287    #[lints(unwrap_used)]
288    allow_unwrap_in_consts("allow-unwrap-in-consts"): bool = true,
289    /// Whether `unwrap` should be allowed in test functions or `#[cfg(test)]`
290    #[lints(unwrap_used)]
291    allow_unwrap_in_tests("allow-unwrap-in-tests"): bool = false,
292    /// List of types to allow `unwrap()` and `expect()` on.
293    ///
294    /// #### Example
295    ///
296    /// ```toml
297    /// allow-unwrap-types = [ "std::sync::LockResult" ]
298    /// ```
299    #[lints(expect_used, unwrap_used)]
300    allow_unwrap_types("allow-unwrap-types"): Vec<String>,
301    /// Whether `useless_vec` should ignore test functions or `#[cfg(test)]`
302    #[lints(useless_vec)]
303    allow_useless_vec_in_tests("allow-useless-vec-in-tests"): bool = false,
304    /// Additional dotfiles (files or directories starting with a dot) to allow
305    #[lints(path_ends_with_ext)]
306    allowed_dotfiles("allowed-dotfiles"): Vec<String>,
307    /// A list of crate names to allow duplicates of
308    #[lints(multiple_crate_versions)]
309    allowed_duplicate_crates("allowed-duplicate-crates"): FxHashSet<String>,
310    /// Allowed names below the minimum allowed characters. The value `".."` can be used as part of
311    /// the list to indicate that the configured values should be appended to the default
312    /// configuration of Clippy. By default, any configuration will replace the default value.
313    #[lints(min_ident_chars)]
314    allowed_idents_below_min_chars("allowed-idents-below-min-chars"): FxHashSet<String> = DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS,
315    /// List of prefixes to allow when determining whether an item's name ends with the module's name.
316    /// If the rest of an item's name is an allowed prefix (e.g. item `ToFoo` or `to_foo` in module `foo`),
317    /// then don't emit a warning.
318    ///
319    /// #### Example
320    ///
321    /// ```toml
322    /// allowed-prefixes = [ "to", "from" ]
323    /// ```
324    ///
325    /// #### Noteworthy
326    ///
327    /// - By default, the following prefixes are allowed: `to`, `as`, `into`, `from`, `try_into` and `try_from`
328    /// - PascalCase variant is included automatically for each snake_case variant (e.g. if `try_into` is included,
329    ///   `TryInto` will also be included)
330    /// - Use `".."` as part of the list to indicate that the configured values should be appended to the
331    /// default configuration of Clippy. By default, any configuration will replace the default value
332    #[lints(module_name_repetitions)]
333    allowed_prefixes("allowed-prefixes"): Vec<String> = DEFAULT_ALLOWED_PREFIXES,
334    /// The list of unicode scripts allowed to be used in the scope.
335    #[lints(disallowed_script_idents)]
336    allowed_scripts("allowed-scripts"): Vec<String> = DEFAULT_ALLOWED_SCRIPTS,
337    /// List of path segments allowed to have wildcard imports.
338    ///
339    /// #### Example
340    ///
341    /// ```toml
342    /// allowed-wildcard-imports = [ "utils", "common" ]
343    /// ```
344    ///
345    /// #### Noteworthy
346    ///
347    /// 1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`.
348    /// 2. Paths with any segment that containing the word 'prelude'
349    /// are already allowed by default.
350    #[lints(wildcard_imports)]
351    allowed_wildcard_imports("allowed-wildcard-imports"): FxHashSet<String>,
352    /// Suppress checking of the passed type names in all types of operations.
353    ///
354    /// If a specific operation is desired, consider using `arithmetic_side_effects_allowed_binary` or `arithmetic_side_effects_allowed_unary` instead.
355    ///
356    /// #### Example
357    ///
358    /// ```toml
359    /// arithmetic-side-effects-allowed = ["SomeType", "AnotherType"]
360    /// ```
361    ///
362    /// #### Noteworthy
363    ///
364    /// A type, say `SomeType`, listed in this configuration has the same behavior of
365    /// `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`.
366    #[lints(arithmetic_side_effects)]
367    arithmetic_side_effects_allowed("arithmetic-side-effects-allowed"): Vec<String>,
368    /// Suppress checking of the passed type pair names in binary operations like addition or
369    /// multiplication.
370    ///
371    /// Supports the "*" wildcard to indicate that a certain type won't trigger the lint regardless
372    /// of the involved counterpart. For example, `["SomeType", "*"]` or `["*", "AnotherType"]`.
373    ///
374    /// Pairs are asymmetric, which means that `["SomeType", "AnotherType"]` is not the same as
375    /// `["AnotherType", "SomeType"]`.
376    ///
377    /// #### Example
378    ///
379    /// ```toml
380    /// arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType", "*"]]
381    /// ```
382    #[lints(arithmetic_side_effects)]
383    arithmetic_side_effects_allowed_binary("arithmetic-side-effects-allowed-binary"): Vec<[String; 2]>,
384    /// Suppress checking of the passed type names in unary operations like "negation" (`-`).
385    ///
386    /// #### Example
387    ///
388    /// ```toml
389    /// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
390    /// ```
391    #[lints(arithmetic_side_effects)]
392    arithmetic_side_effects_allowed_unary("arithmetic-side-effects-allowed-unary"): Vec<String>,
393    /// The maximum allowed size for arrays on the stack
394    #[lints(large_const_arrays, large_stack_arrays)]
395    array_size_threshold("array-size-threshold"): u64 = 16 * 1024,
396    /// Suppress lints whenever the suggested change would cause breakage for other crates.
397    #[lints(
398        box_collection,
399        enum_variant_names,
400        large_types_passed_by_value,
401        linkedlist,
402        needless_pass_by_ref_mut,
403        option_option,
404        owned_cow,
405        rc_buffer,
406        rc_mutex,
407        redundant_allocation,
408        ref_option,
409        single_call_fn,
410        trivially_copy_pass_by_ref,
411        unnecessary_box_returns,
412        unnecessary_wraps,
413        unused_self,
414        upper_case_acronyms,
415        vec_box,
416        wrong_self_convention,
417    )]
418    avoid_breaking_exported_api("avoid-breaking-exported-api"): bool = true,
419    /// The list of types which may not be held across an await point.
420    #[lints(await_holding_invalid_type)]
421    await_holding_invalid_types("await-holding-invalid-types"): Vec<DisallowedPathWithoutReplacement>,
422    #[rename = disallowed_names]
423    blacklisted_names("blacklisted-names"),
424    /// For internal testing only, ignores the current `publish` settings in the Cargo manifest.
425    #[lints(cargo_common_metadata)]
426    cargo_ignore_publish("cargo-ignore-publish"): bool = false,
427    /// Whether to check for grouped late initializations from multiple `let` statements.
428    ///
429    /// #### Example
430    /// ```rust
431    /// let a;
432    /// let b;
433    /// if true {
434    ///     a = 1;
435    ///     b = 2;
436    /// } else {
437    ///     a = 3;
438    ///     b = 4;
439    /// }
440    /// ```
441    /// Use instead:
442    /// ```rust
443    /// let (a, b) = if true {
444    ///     (1, 2)
445    /// } else {
446    ///     (3, 4)
447    /// };
448    /// ```
449    #[lints(needless_late_init)]
450    check_grouped_late_init("check-grouped-late-init"): bool = true,
451    /// Whether to check MSRV compatibility in `#[test]` and `#[cfg(test)]` code.
452    #[lints(incompatible_msrv)]
453    check_incompatible_msrv_in_tests("check-incompatible-msrv-in-tests"): bool = false,
454    /// Whether to suggest reordering constructor fields when initializers are present.
455    ///
456    /// Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
457    /// suggested code would compile, it can change semantics if the initializer expressions have side effects. The
458    /// following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
459    ///
460    /// ```rust
461    /// struct MyStruct {
462    ///     vector: Vec<u32>,
463    ///     length: usize
464    /// }
465    /// fn main() {
466    ///     let vector = vec![1,2,3];
467    ///     MyStruct { length: vector.len(), vector};
468    /// }
469    /// ```
470    ///
471    /// [from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
472    #[lints(inconsistent_struct_constructor)]
473    check_inconsistent_struct_field_initializers("check-inconsistent-struct-field-initializers"): bool = false,
474    /// Whether to also run the listed lints on private items.
475    #[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
476    check_private_items("check-private-items"): bool = false,
477    /// The maximum cognitive complexity a function can have
478    #[lints(cognitive_complexity)]
479    cognitive_complexity_threshold("cognitive-complexity-threshold"): u64 = 25,
480    /// The minimum digits a const float literal must have to supress the `excessive_precicion` lint
481    #[lints(excessive_precision)]
482    const_literal_digits_threshold("const-literal-digits-threshold"): u32 = 30,
483    #[rename = cognitive_complexity_threshold]
484    cyclomatic_complexity_threshold("cyclomatic-complexity-threshold"),
485    /// The list of disallowed fields, written as fully qualified paths.
486    ///
487    /// **Fields:**
488    /// - `path` (required): the fully qualified path to the field that should be disallowed
489    /// - `reason` (optional): explanation why this field is disallowed
490    /// - `replacement` (optional): suggested alternative method
491    /// - `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
492    ///   if the path doesn't exist, instead of emitting an error
493    #[lints(disallowed_fields)]
494    disallowed_fields("disallowed-fields"): Vec<DisallowedPath>,
495    /// The list of disallowed macros, written as fully qualified paths.
496    ///
497    /// **Fields:**
498    /// - `path` (required): the fully qualified path to the macro that should be disallowed
499    /// - `reason` (optional): explanation why this macro is disallowed
500    /// - `replacement` (optional): suggested alternative macro
501    /// - `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
502    ///   if the path doesn't exist, instead of emitting an error
503    #[lints(disallowed_macros)]
504    disallowed_macros("disallowed-macros"): Vec<DisallowedPath>,
505    /// The list of disallowed methods, written as fully qualified paths.
506    ///
507    /// **Fields:**
508    /// - `path` (required): the fully qualified path to the method that should be disallowed
509    /// - `reason` (optional): explanation why this method is disallowed
510    /// - `replacement` (optional): suggested alternative method
511    /// - `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
512    ///   if the path doesn't exist, instead of emitting an error
513    #[lints(disallowed_methods)]
514    disallowed_methods("disallowed-methods"): Vec<DisallowedPath>,
515    /// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
516    /// `".."` can be used as part of the list to indicate that the configured values should be appended to the
517    /// default configuration of Clippy. By default, any configuration will replace the default value.
518    #[lints(disallowed_names)]
519    disallowed_names("disallowed-names"): Vec<String> = DEFAULT_DISALLOWED_NAMES,
520    /// The list of disallowed types, written as fully qualified paths.
521    ///
522    /// **Fields:**
523    /// - `path` (required): the fully qualified path to the type that should be disallowed
524    /// - `reason` (optional): explanation why this type is disallowed
525    /// - `replacement` (optional): suggested alternative type
526    /// - `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
527    ///   if the path doesn't exist, instead of emitting an error
528    #[lints(disallowed_types)]
529    disallowed_types("disallowed-types"): Vec<DisallowedPath>,
530    /// The list of words this lint should not consider as identifiers needing ticks. The value
531    /// `".."` can be used as part of the list to indicate that the configured values should be appended to the
532    /// default configuration of Clippy. By default, any configuration will replace the default value. For example:
533    /// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
534    /// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
535    #[lints(doc_markdown)]
536    doc_valid_idents("doc-valid-idents"): FxHashSet<String> = DEFAULT_DOC_VALID_IDENTS,
537    /// Whether to apply the raw pointer heuristic to determine if a type is `Send`.
538    #[lints(non_send_fields_in_send_ty)]
539    enable_raw_pointer_heuristic_for_send("enable-raw-pointer-heuristic-for-send"): bool = true,
540    /// Whether to recommend using implicit into iter for reborrowed values.
541    ///
542    /// #### Example
543    /// ```no_run
544    /// let mut vec = vec![1, 2, 3];
545    /// let rmvec = &mut vec;
546    /// for _ in rmvec.iter() {}
547    /// for _ in rmvec.iter_mut() {}
548    /// ```
549    ///
550    /// Use instead:
551    /// ```no_run
552    /// let mut vec = vec![1, 2, 3];
553    /// let rmvec = &mut vec;
554    /// for _ in &*rmvec {}
555    /// for _ in &mut *rmvec {}
556    /// ```
557    #[lints(explicit_iter_loop)]
558    enforce_iter_loop_reborrow("enforce-iter-loop-reborrow"): bool = false,
559    /// The list of imports to always rename, a fully qualified path followed by the rename.
560    #[lints(missing_enforced_import_renames)]
561    enforced_import_renames("enforced-import-renames"): Vec<Rename>,
562    /// The minimum number of enum variants for the lints about variant names to trigger
563    #[lints(enum_variant_names)]
564    enum_variant_name_threshold("enum-variant-name-threshold"): u64 = 3,
565    /// The maximum size of an enum's variant to avoid box suggestion
566    #[lints(large_enum_variant)]
567    enum_variant_size_threshold("enum-variant-size-threshold"): u64 = 200,
568    /// The maximum amount of nesting a block can reside in
569    #[lints(excessive_nesting)]
570    excessive_nesting_threshold("excessive-nesting-threshold"): u64 = 0,
571    /// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
572    #[lints(large_futures)]
573    future_size_threshold("future-size-threshold"): u64 = 16 * 1024,
574    /// A list of paths to types that should be treated as if they do not contain interior mutability
575    #[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)]
576    ignore_interior_mutability("ignore-interior-mutability"): Vec<String> = DEFAULT_IGNORE_INTERIOR_MUTABILITY,
577    /// Sets the scope ("crate", "file", or "module") in which duplicate inherent `impl` blocks for the same type are linted.
578    #[lints(multiple_inherent_impl)]
579    inherent_impl_lint_scope("inherent-impl-lint-scope"): InherentImplLintScope = InherentImplLintScope::Crate,
580    /// A list of paths to types that should be ignored as overly large `Err`-variants in a
581    /// `Result` returned from a function
582    #[lints(result_large_err)]
583    large_error_ignored("large-error-ignored"): Vec<String>,
584    /// The maximum size of the `Err`-variant in a `Result` returned from a function
585    #[lints(result_large_err)]
586    large_error_threshold("large-error-threshold"): u64 = 128,
587    /// Whether collapsible `if` and `else if` chains are linted if they contain comments inside the parts
588    /// that would be collapsed.
589    #[lints(collapsible_else_if, collapsible_if)]
590    lint_commented_code("lint-commented-code"): bool = false,
591    #[rename = check_inconsistent_struct_field_initializers]
592    lint_inconsistent_struct_field_initializers("lint-inconsistent-struct-field-initializers"): bool = false,
593    /// The lower bound for linting decimal literals
594    #[lints(decimal_literal_representation)]
595    literal_representation_threshold("literal-representation-threshold"): u64 = 16384,
596    /// Whether the matches should be considered by the lint, and whether there should
597    /// be filtering for common types.
598    #[lints(manual_let_else)]
599    matches_for_let_else("matches-for-let-else"): MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes,
600    /// The maximum number of bool parameters a function can have.
601    /// Use `0` to lint on any function with a bool parameter.
602    #[lints(fn_params_excessive_bools)]
603    max_fn_params_bools("max-fn-params-bools"): u64 = 3,
604    /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes
605    #[lints(large_include_file)]
606    max_include_file_size("max-include-file-size"): u64 = 1_000_000,
607    /// The maximum number of bool fields a struct can have
608    #[lints(struct_excessive_bools)]
609    max_struct_bools("max-struct-bools"): u64 = 3,
610    /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
611    /// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed.
612    /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
613    #[lints(index_refutable_slice)]
614    max_suggested_slice_pattern_length("max-suggested-slice-pattern-length"): u64 = 3,
615    /// The maximum number of bounds a trait can have to be linted
616    #[lints(type_repetition_in_bounds)]
617    max_trait_bounds("max-trait-bounds"): u64 = 3,
618    /// Whether to lint idents that have too few chars even when following trait declaration.
619    #[lints(min_ident_chars)]
620    min_ident_chars_lint_trait_impl("min-ident-chars-lint-trait-impl"): bool = false,
621    /// Minimum chars an ident can have, anything below or equal to this will be linted.
622    #[lints(min_ident_chars)]
623    min_ident_chars_threshold("min-ident-chars-threshold"): u64 = 1,
624    /// Whether to allow fields starting with an underscore to skip documentation requirements
625    #[lints(missing_docs_in_private_items)]
626    missing_docs_allow_unused("missing-docs-allow-unused"): bool = false,
627    /// Whether to **only** check for missing documentation in items visible within the current
628    /// crate. For example, `pub(crate)` items.
629    #[lints(missing_docs_in_private_items)]
630    missing_docs_in_crate_items("missing-docs-in-crate-items"): bool = false,
631    /// The named groupings of different source item kinds within modules.
632    #[lints(arbitrary_source_item_ordering)]
633    module_item_order_groupings("module-item-order-groupings"): SourceItemOrderingModuleItemGroupings,
634    /// Whether the items within module groups should be ordered alphabetically or not.
635    ///
636    /// This option can be configured to "all", "none", or a list of specific grouping names that should be checked
637    /// (e.g. only "enums").
638    #[lints(arbitrary_source_item_ordering)]
639    module_items_ordered_within_groupings("module-items-ordered-within-groupings"): SourceItemOrderingWithinModuleItemGroupings,
640    /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
641    #[default_text = "current version"]
642    #[lints(
643        allow_attributes,
644        allow_attributes_without_reason,
645        almost_complete_range,
646        approx_constant,
647        assigning_clones,
648        borrow_as_ptr,
649        cast_abs_to_unsigned,
650        checked_conversions,
651        cloned_instead_of_copied,
652        collapsible_match,
653        collapsible_str_replace,
654        deprecated_cfg_attr,
655        derivable_impls,
656        err_expect,
657        filter_map_next,
658        from_over_into,
659        if_then_some_else_none,
660        implicit_saturating_sub,
661        index_refutable_slice,
662        inefficient_to_string,
663        io_other_error,
664        iter_kv_map,
665        legacy_numeric_constants,
666        len_zero,
667        lines_filter_map_ok,
668        manual_abs_diff,
669        manual_bits,
670        manual_c_str_literals,
671        manual_clamp,
672        manual_div_ceil,
673        manual_flatten,
674        manual_hash_one,
675        manual_is_ascii_check,
676        manual_is_power_of_two,
677        manual_is_variant_and,
678        manual_isolate_lowest_one,
679        manual_let_else,
680        manual_midpoint,
681        manual_non_exhaustive,
682        manual_noop_waker,
683        manual_option_as_slice,
684        manual_pattern_char_comparison,
685        manual_range_contains,
686        manual_rem_euclid,
687        manual_repeat_n,
688        manual_retain,
689        manual_slice_fill,
690        manual_slice_size_calculation,
691        manual_split_once,
692        manual_str_repeat,
693        manual_strip,
694        manual_take,
695        manual_try_fold,
696        map_clone,
697        map_unwrap_or,
698        map_with_unused_argument_over_ranges,
699        match_like_matches_macro,
700        mem_replace_option_with_some,
701        mem_replace_with_default,
702        missing_const_for_fn,
703        needless_borrow,
704        non_std_lazy_statics,
705        nonnull_unchecked_on_box_ptr,
706        option_as_ref_deref,
707        or_fun_call,
708        ptr_as_ptr,
709        question_mark,
710        redundant_field_names,
711        redundant_static_lifetimes,
712        repeat_vec_with_capacity,
713        same_item_push,
714        seek_from_current,
715        to_digit_is_some,
716        transmute_ptr_to_ref,
717        tuple_array_conversions,
718        type_repetition_in_bounds,
719        unchecked_time_subtraction,
720        uninlined_format_args,
721        unnecessary_lazy_evaluations,
722        unnecessary_unwrap,
723        unnested_or_patterns,
724        unused_trait_names,
725        use_self,
726        zero_ptr,
727    )]
728    msrv("msrv"): Option<RustcVersion>,
729    /// The minimum size (in bytes) to consider a type for passing by reference instead of by value.
730    #[lints(large_types_passed_by_value)]
731    pass_by_value_size_limit("pass-by-value-size-limit"): u64 = 256,
732    /// Lint "public" fields in a struct that are prefixed with an underscore based on their
733    /// exported visibility, or whether they are marked as "pub".
734    #[lints(pub_underscore_fields)]
735    pub_underscore_fields_behavior("pub-underscore-fields-behavior"): PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported,
736    /// Whether the type itself in a struct or enum should be replaced with `Self` when encountering recursive types.
737    #[lints(use_self)]
738    recursive_self_in_type_definitions("recursive-self-in-type-definitions"): bool = true,
739    /// Whether to lint only if it's multiline.
740    #[lints(semicolon_inside_block)]
741    semicolon_inside_block_ignore_singleline("semicolon-inside-block-ignore-singleline"): bool = false,
742    /// Whether to lint only if it's singleline.
743    #[lints(semicolon_outside_block)]
744    semicolon_outside_block_ignore_multiline("semicolon-outside-block-ignore-multiline"): bool = false,
745    /// The maximum number of single char bindings a scope may have
746    #[lints(many_single_char_names)]
747    single_char_binding_names_threshold("single-char-binding-names-threshold"): u64 = 4,
748    /// Which kind of elements should be ordered internally, possible values being `enum`, `impl`, `module`, `struct`, `trait`.
749    #[lints(arbitrary_source_item_ordering)]
750    source_item_ordering("source-item-ordering"): SourceItemOrdering,
751    /// The maximum allowed stack size for functions in bytes
752    #[lints(large_stack_frames)]
753    stack_size_threshold("stack-size-threshold"): u64 = 512_000,
754    /// Enforce the named macros always use the braces specified.
755    ///
756    /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
757    /// could be used with a full path two `MacroMatcher`s have to be added one with the full path
758    /// `crate_name::macro_name` and one with just the macro name.
759    #[lints(nonstandard_macro_braces)]
760    standard_macro_braces("standard-macro-braces"): Vec<MacroMatcher>,
761    /// The minimum number of struct fields for the lints about field names to trigger
762    #[lints(struct_field_names)]
763    struct_field_name_threshold("struct-field-name-threshold"): u64 = 3,
764    /// Whether to suppress a restriction lint in constant code. In same
765    /// cases the restructured operation might not be unavoidable, as the
766    /// suggested counterparts are unavailable in constant code. This
767    /// configuration will cause restriction lints to trigger even
768    /// if no suggestion can be made.
769    #[lints(indexing_slicing)]
770    suppress_restriction_lint_in_const("suppress-restriction-lint-in-const"): bool = false,
771    /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
772    #[lints(boxed_local, useless_vec)]
773    too_large_for_stack("too-large-for-stack"): u64 = 200,
774    /// The maximum number of argument a function or method can have
775    #[lints(too_many_arguments)]
776    too_many_arguments_threshold("too-many-arguments-threshold"): u64 = 7,
777    /// The maximum number of lines a function or method can have
778    #[lints(too_many_lines)]
779    too_many_lines_threshold("too-many-lines-threshold"): u64 = 100,
780    /// The order of associated items in traits.
781    #[lints(arbitrary_source_item_ordering)]
782    trait_assoc_item_kinds_order("trait-assoc-item-kinds-order"): SourceItemOrderingTraitAssocItemKinds,
783    /// The required ordering of associated items in trait impls: purely alphabetical,
784    /// following the trait definition order, or accepting either.
785    ///
786    /// Note that the trait definition order may change between versions of the
787    /// crate defining the trait without being considered a breaking change.
788    ///
789    /// Examples:
790    /// When using trait definition item ordering:
791    /// ```toml
792    /// trait-impl-item-order = "trait_item_ordering"
793    /// ```
794    /// When using trait definition item ordering and alphabetical for fallbacks:
795    /// ```toml
796    /// trait-impl-item-order = "alphabetical_or_trait_item_ordering"
797    /// ```
798    #[lints(arbitrary_source_item_ordering)]
799    trait_impl_item_order("trait-impl-item-order"): TraitImplItemOrder,
800    /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
801    /// reference.
802    #[default_text = "target_pointer_width"]
803    #[lints(trivially_copy_pass_by_ref)]
804    trivial_copy_size_limit("trivial-copy-size-limit"): Option<u64>,
805    /// The maximum complexity a type can have
806    #[lints(type_complexity)]
807    type_complexity_threshold("type-complexity-threshold"): u64 = 250,
808    /// The byte size a `T` in `Box<T>` can have, below which it triggers the `clippy::unnecessary_box` lint
809    #[lints(unnecessary_box_returns)]
810    unnecessary_box_size("unnecessary-box-size"): u64 = 128,
811    /// Should the fraction of a decimal be linted to include separators.
812    #[lints(unreadable_literal)]
813    unreadable_literal_lint_fractions("unreadable-literal-lint-fractions"): bool = true,
814    /// Enables verbose mode. Triggers if there is more than one uppercase char next to each other
815    #[lints(upper_case_acronyms)]
816    upper_case_acronyms_aggressive("upper-case-acronyms-aggressive"): bool = false,
817    /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed
818    #[lints(vec_box)]
819    vec_box_size_threshold("vec-box-size-threshold"): u64 = 4096,
820    /// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
821    #[lints(verbose_bit_mask)]
822    verbose_bit_mask_threshold("verbose-bit-mask-threshold"): u64 = 1,
823    /// Whether to emit warnings on all wildcard imports, including those from `prelude`, from `super` in tests,
824    /// or for `pub use` reexports.
825    #[lints(wildcard_imports)]
826    warn_on_all_wildcard_imports("warn-on-all-wildcard-imports"): bool = false,
827    /// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros.
828    #[lints(macro_metavars_in_unsafe)]
829    warn_unsafe_macro_metavars_in_private_macros("warn-unsafe-macro-metavars-in-private-macros"): bool = false,
830}
831
832// Remove code tags and code behind '# 's, as they are not needed for the lint docs and --explain
833pub fn sanitize_explanation(raw_docs: &str) -> String {
834    // Remove tags and hidden code:
835    let mut explanation = String::with_capacity(128);
836    let mut in_code = false;
837    for line in raw_docs.lines() {
838        let line = line.strip_prefix(' ').unwrap_or(line);
839
840        if let Some(lang) = line.strip_prefix("```") {
841            let tag = lang.split_once(',').map_or(lang, |(left, _)| left);
842            if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") {
843                explanation += "```rust\n";
844            } else {
845                explanation += line;
846                explanation.push('\n');
847            }
848            in_code = !in_code;
849        } else if !(in_code && line.starts_with("# ")) {
850            explanation += line;
851            explanation.push('\n');
852        }
853    }
854
855    explanation
856}
857
858/// Searches for and loads the config file into the source map.
859///
860/// # Errors
861///
862/// Returns any unexpected filesystem error encountered when searching for the config file
863fn load_conf_file(sess: &Session) -> Option<Arc<SourceFile>> {
864    /// Possible filename to search for.
865    const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
866
867    // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
868    // If neither of those exist, use ".". (Update documentation if this priority changes)
869    const CONFIG_VARS: [(&str, &str); 2] = [
870        ("CLIPPY_CONF_DIR", "failed to read `CLIPPY_CONF_DIR` as a directory"),
871        (
872            "CARGO_MANIFEST_DIR",
873            "failed to read `CARGO_MANIFEST_DIR` as a directory",
874        ),
875    ];
876    let (current, msg) = CONFIG_VARS
877        .into_iter()
878        .find_map(|(var, msg)| env::var_os(var).map(|p| (PathBuf::from(p), msg)))
879        .unwrap_or_else(|| (PathBuf::from("."), "failed to get the current directory"));
880    let mut current = match current.canonicalize() {
881        Ok(x) => x,
882        Err(e) => {
883            sess.dcx().err(format!("{msg}: {e}"));
884            return None;
885        },
886    };
887
888    let mut loaded_config: Option<(PathBuf, Arc<SourceFile>)> = None;
889    loop {
890        for config_file_name in CONFIG_FILE_NAMES {
891            if let Ok(config_path) = current.join(config_file_name).canonicalize() {
892                if let Some((loaded_path, _)) = &loaded_config {
893                    if fs::metadata(loaded_path).is_ok_and(|x| x.is_file()) {
894                        // Warn if `.clippy.toml` and `clippy.toml` exist
895                        sess.dcx().warn(format!(
896                            "using config file `{}`, `{}` will be ignored",
897                            loaded_path.display(),
898                            config_path.display(),
899                        ));
900                    }
901                } else {
902                    match sess.source_map().load_file(&config_path) {
903                        Ok(src) => loaded_config = Some((config_path, src)),
904                        Err(e)
905                            if matches!(
906                                e.kind(),
907                                io::ErrorKind::NotFound | io::ErrorKind::IsADirectory | io::ErrorKind::NotADirectory
908                            ) => {},
909                        Err(e) => {
910                            sess.dcx()
911                                .err(format!("error reading `{}`: {e}", config_path.display()));
912                            return None;
913                        },
914                    }
915                }
916            }
917        }
918
919        // Don't mention config files in parent directories.
920        if let Some((_, src)) = loaded_config {
921            return Some(src);
922        }
923
924        // If the current directory has no parent, we're done searching.
925        if !current.pop() {
926            return None;
927        }
928    }
929}
930
931impl Conf {
932    pub fn load(sess: &Session) -> &'static Conf {
933        static CONF: OnceLock<Conf> = OnceLock::new();
934        CONF.get_or_init(|| Conf::load_inner(sess))
935    }
936
937    fn load_inner(sess: &Session) -> Conf {
938        let mut conf = if let Some(src) = load_conf_file(sess) {
939            let dcx = DiagCtxt::new(sess, src.start_pos.to_usize());
940            let src = src.src.as_ref().unwrap();
941
942            let (toml, errs) = DeTable::parse_recoverable(src.as_str());
943            for e in errs {
944                match e.span() {
945                    Some(sp) => dcx.span_err(sp, e.message().to_owned()),
946                    None => {
947                        dcx.inner
948                            .struct_err(format!("error parsing `clippy.toml`: {}", e.message()))
949                            .emit();
950                    },
951                }
952            }
953            Conf::deserialize(&dcx, toml.get_ref())
954        } else {
955            Conf::default()
956        };
957
958        let cargo_msrv = env::var("CARGO_PKG_RUST_VERSION")
959            .ok()
960            .and_then(|v| parse_version(Symbol::intern(&v)));
961        match (&conf.msrv, cargo_msrv) {
962            (None, Some(cargo_msrv)) => conf.msrv = Some(cargo_msrv),
963            (Some(clippy_msrv), Some(cargo_msrv)) => {
964                if *clippy_msrv != cargo_msrv {
965                    sess.dcx().warn(format!(
966                        "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
967                    ));
968                }
969            },
970            (_, None) => {},
971        }
972
973        conf
974    }
975}
976
977#[cfg(test)]
978mod tests {
979    use rustc_data_structures::fx::FxHashSet;
980    use std::fs;
981    use toml::de::DeTable;
982    use walkdir::WalkDir;
983
984    #[test]
985    fn configs_are_tested() {
986        let mut names: FxHashSet<_> = super::Conf::get_metadata()
987            .into_iter()
988            .filter(|meta| meta.renamed_to.is_none())
989            .map(|meta| meta.name)
990            .collect();
991
992        let toml_files = WalkDir::new("../tests")
993            .into_iter()
994            .map(Result::unwrap)
995            .filter(|entry| entry.file_name() == "clippy.toml");
996
997        for entry in toml_files {
998            let file = fs::read_to_string(entry.path()).unwrap();
999            if let Ok(toml) = DeTable::parse(&file) {
1000                for (key, _) in toml.as_ref() {
1001                    names.remove(&**key.get_ref());
1002                }
1003            }
1004        }
1005
1006        assert!(
1007            names.is_empty(),
1008            "Configuration variable lacks test: {names:?}\nAdd a test to `tests/ui-toml`"
1009        );
1010    }
1011}