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