1use crate::ClippyConfiguration;
2use crate::types::{
3 DisallowedPath, DisallowedPathWithoutReplacement, InherentImplLintScope, MacroMatcher, MatchLintBehaviour,
4 PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering, SourceItemOrderingCategory,
5 SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind,
6 SourceItemOrderingTraitAssocItemKinds, SourceItemOrderingWithinModuleItemGroupings,
7};
8use clippy_utils::msrvs::Msrv;
9use itertools::Itertools;
10use rustc_errors::Applicability;
11use rustc_session::Session;
12use rustc_span::edit_distance::edit_distance;
13use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
14use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
15use serde::{Deserialize, Deserializer, Serialize};
16use std::collections::HashMap;
17use std::fmt::{Debug, Display, Formatter};
18use std::ops::Range;
19use std::path::PathBuf;
20use std::str::FromStr;
21use std::sync::OnceLock;
22use std::{cmp, env, fmt, fs, io};
23
24#[rustfmt::skip]
25const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
26 "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
27 "MHz", "GHz", "THz",
28 "AccessKit",
29 "CoAP", "CoreFoundation", "CoreGraphics", "CoreText",
30 "DevOps",
31 "Direct2D", "Direct3D", "DirectWrite", "DirectX",
32 "ECMAScript",
33 "GPLv2", "GPLv3",
34 "GitHub", "GitLab",
35 "IPv4", "IPv6",
36 "InfiniBand", "RoCE",
37 "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript",
38 "PowerPC", "PowerShell", "WebAssembly",
39 "NaN", "NaNs",
40 "OAuth", "GraphQL",
41 "SQLite", "MySQL", "PostgreSQL", "MariaDB", "MongoDB",
42 "OCaml",
43 "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry",
44 "OpenType",
45 "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport",
46 "WebP", "OpenExr", "YCbCr", "sRGB",
47 "TensorFlow",
48 "TrueType",
49 "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS",
50 "TeX", "LaTeX", "BibTeX", "BibLaTeX",
51 "MinGW",
52 "CamelCase",
53];
54const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
55const DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z", "w", "n"];
56const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"];
57const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] =
58 &["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"];
59const DEFAULT_MODULE_ITEM_ORDERING_GROUPS: &[(&str, &[SourceItemOrderingModuleItemKind])] = {
60 #[allow(clippy::enum_glob_use)] use SourceItemOrderingModuleItemKind::*;
62 &[
63 ("modules", &[ExternCrate, Mod, ForeignMod]),
64 ("use", &[Use]),
65 ("macros", &[Macro]),
66 ("global_asm", &[GlobalAsm]),
67 ("UPPER_SNAKE_CASE", &[Static, Const]),
68 ("PascalCase", &[TyAlias, Enum, Struct, Union, Trait, TraitAlias, Impl]),
69 ("lower_snake_case", &[Fn]),
70 ]
71};
72const DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER: &[SourceItemOrderingTraitAssocItemKind] = {
73 #[allow(clippy::enum_glob_use)] use SourceItemOrderingTraitAssocItemKind::*;
75 &[Const, Type, Fn]
76};
77const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
78 #[allow(clippy::enum_glob_use)] use SourceItemOrderingCategory::*;
80 &[Enum, Impl, Module, Struct, Trait]
81};
82
83#[derive(Default)]
85struct TryConf {
86 conf: Conf,
87 value_spans: HashMap<String, Range<usize>>,
88 errors: Vec<ConfError>,
89 warnings: Vec<ConfError>,
90}
91
92impl TryConf {
93 fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
94 Self {
95 conf: Conf::default(),
96 value_spans: HashMap::default(),
97 errors: vec![ConfError::from_toml(file, error)],
98 warnings: vec![],
99 }
100 }
101}
102
103#[derive(Debug)]
104struct ConfError {
105 message: String,
106 suggestion: Option<Suggestion>,
107 span: Span,
108}
109
110impl ConfError {
111 fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
112 let span = error.span().unwrap_or(0..file.normalized_source_len.0 as usize);
113 Self::spanned(file, error.message(), None, span)
114 }
115
116 fn spanned(
117 file: &SourceFile,
118 message: impl Into<String>,
119 suggestion: Option<Suggestion>,
120 span: Range<usize>,
121 ) -> Self {
122 Self {
123 message: message.into(),
124 suggestion,
125 span: span_from_toml_range(file, span),
126 }
127 }
128}
129
130pub fn sanitize_explanation(raw_docs: &str) -> String {
132 let mut explanation = String::with_capacity(128);
134 let mut in_code = false;
135 for line in raw_docs.lines() {
136 let line = line.strip_prefix(' ').unwrap_or(line);
137
138 if let Some(lang) = line.strip_prefix("```") {
139 let tag = lang.split_once(',').map_or(lang, |(left, _)| left);
140 if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") {
141 explanation += "```rust\n";
142 } else {
143 explanation += line;
144 explanation.push('\n');
145 }
146 in_code = !in_code;
147 } else if !(in_code && line.starts_with("# ")) {
148 explanation += line;
149 explanation.push('\n');
150 }
151 }
152
153 explanation
154}
155
156macro_rules! wrap_option {
157 () => {
158 None
159 };
160 ($x:literal) => {
161 Some($x)
162 };
163}
164
165macro_rules! default_text {
166 ($value:expr) => {{
167 let mut text = String::new();
168 $value.serialize(toml::ser::ValueSerializer::new(&mut text)).unwrap();
169 text
170 }};
171 ($value:expr, $override:expr) => {
172 $override.to_string()
173 };
174}
175
176macro_rules! deserialize {
177 ($map:expr, $ty:ty, $errors:expr, $file:expr) => {{
178 let raw_value = $map.next_value::<toml::Spanned<toml::Value>>()?;
179 let value_span = raw_value.span();
180 let value = match <$ty>::deserialize(raw_value.into_inner()) {
181 Err(e) => {
182 $errors.push(ConfError::spanned(
183 $file,
184 e.to_string().replace('\n', " ").trim(),
185 None,
186 value_span,
187 ));
188 continue;
189 },
190 Ok(value) => value,
191 };
192 (value, value_span)
193 }};
194
195 ($map:expr, $ty:ty, $errors:expr, $file:expr, $replacements_allowed:expr) => {{
196 let array = $map.next_value::<Vec<toml::Spanned<toml::Value>>>()?;
197 let mut disallowed_paths_span = Range {
198 start: usize::MAX,
199 end: usize::MIN,
200 };
201 let mut disallowed_paths = Vec::new();
202 for raw_value in array {
203 let value_span = raw_value.span();
204 let mut disallowed_path = match DisallowedPath::<$replacements_allowed>::deserialize(raw_value.into_inner())
205 {
206 Err(e) => {
207 $errors.push(ConfError::spanned(
208 $file,
209 e.to_string().replace('\n', " ").trim(),
210 None,
211 value_span,
212 ));
213 continue;
214 },
215 Ok(disallowed_path) => disallowed_path,
216 };
217 disallowed_paths_span = union(&disallowed_paths_span, &value_span);
218 disallowed_path.set_span(span_from_toml_range($file, value_span));
219 disallowed_paths.push(disallowed_path);
220 }
221 (disallowed_paths, disallowed_paths_span)
222 }};
223}
224
225macro_rules! define_Conf {
226 ($(
227 $(#[doc = $doc:literal])+
228 $(#[conf_deprecated($dep:literal, $new_conf:ident)])?
229 $(#[default_text = $default_text:expr])?
230 $(#[disallowed_paths_allow_replacements = $replacements_allowed:expr])?
231 $(#[lints($($for_lints:ident),* $(,)?)])?
232 $name:ident: $ty:ty = $default:expr,
233 )*) => {
234 pub struct Conf {
236 $($(#[cfg_attr(doc, doc = $doc)])+ pub $name: $ty,)*
237 }
238
239 mod defaults {
240 use super::*;
241 $(pub fn $name() -> $ty { $default })*
242 }
243
244 impl Default for Conf {
245 fn default() -> Self {
246 Self { $($name: defaults::$name(),)* }
247 }
248 }
249
250 #[derive(Deserialize)]
251 #[serde(field_identifier, rename_all = "kebab-case")]
252 #[expect(non_camel_case_types)]
253 enum Field { $($name,)* third_party, }
254
255 struct ConfVisitor<'a>(&'a SourceFile);
256
257 impl<'de> Visitor<'de> for ConfVisitor<'_> {
258 type Value = TryConf;
259
260 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
261 formatter.write_str("Conf")
262 }
263
264 fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
265 let mut value_spans = HashMap::new();
266 let mut errors = Vec::new();
267 let mut warnings = Vec::new();
268
269 $(let mut $name = None;)*
271
272 while let Some(name) = map.next_key::<toml::Spanned<String>>()? {
274 let field = match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
275 Err(e) => {
276 let e: FieldError = e;
277 errors.push(ConfError::spanned(self.0, e.error, e.suggestion, name.span()));
278 continue;
279 }
280 Ok(field) => field
281 };
282
283 match field {
284 $(Field::$name => {
285 $(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
287 let (value, value_span) =
288 deserialize!(map, $ty, errors, self.0 $(, $replacements_allowed)?);
289 if $name.is_some() {
291 errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
292 continue;
293 }
294 $name = Some(value);
295 value_spans.insert(name.get_ref().as_str().to_string(), value_span);
296 $(match $new_conf {
299 Some(_) => errors.push(ConfError::spanned(self.0, concat!(
300 "duplicate field `", stringify!($new_conf),
301 "` (provided as `", stringify!($name), "`)"
302 ), None, name.span())),
303 None => $new_conf = $name.clone(),
304 })?
305 })*
306 Field::third_party => drop(map.next_value::<IgnoredAny>())
308 }
309 }
310 let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
311 Ok(TryConf { conf, value_spans, errors, warnings })
312 }
313 }
314
315 pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
316 vec![$(
317 ClippyConfiguration {
318 name: stringify!($name).replace('_', "-"),
319 default: default_text!(defaults::$name() $(, $default_text)?),
320 lints: &[$($(stringify!($for_lints)),*)?],
321 doc: concat!($($doc, '\n',)*),
322 deprecation_reason: wrap_option!($($dep)?)
323 },
324 )*]
325 }
326 };
327}
328
329fn union(x: &Range<usize>, y: &Range<usize>) -> Range<usize> {
330 Range {
331 start: cmp::min(x.start, y.start),
332 end: cmp::max(x.end, y.end),
333 }
334}
335
336fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
337 Span::new(
338 file.start_pos + BytePos::from_usize(span.start),
339 file.start_pos + BytePos::from_usize(span.end),
340 SyntaxContext::root(),
341 None,
342 )
343}
344
345define_Conf! {
346 #[lints(absolute_paths)]
348 absolute_paths_allowed_crates: Vec<String> = Vec::new(),
349 #[lints(absolute_paths)]
352 absolute_paths_max_segments: u64 = 2,
353 #[lints(undocumented_unsafe_blocks)]
355 accept_comment_above_attributes: bool = true,
356 #[lints(undocumented_unsafe_blocks)]
358 accept_comment_above_statement: bool = true,
359 #[lints(modulo_arithmetic)]
361 allow_comparison_to_zero: bool = true,
362 #[lints(dbg_macro)]
364 allow_dbg_in_tests: bool = false,
365 #[lints(module_name_repetitions)]
367 allow_exact_repetitions: bool = true,
368 #[lints(expect_used)]
370 allow_expect_in_consts: bool = true,
371 #[lints(expect_used)]
373 allow_expect_in_tests: bool = false,
374 #[lints(indexing_slicing)]
376 allow_indexing_slicing_in_tests: bool = false,
377 #[lints(large_stack_frames)]
379 allow_large_stack_frames_in_tests: bool = true,
380 #[lints(uninlined_format_args)]
382 allow_mixed_uninlined_format_args: bool = true,
383 #[lints(needless_raw_string_hashes)]
385 allow_one_hash_in_raw_strings: bool = false,
386 #[lints(panic)]
388 allow_panic_in_tests: bool = false,
389 #[lints(print_stderr, print_stdout)]
391 allow_print_in_tests: bool = false,
392 #[lints(module_inception)]
394 allow_private_module_inception: bool = false,
395 #[lints(renamed_function_params)]
409 allow_renamed_params_for: Vec<String> =
410 DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect(),
411 #[lints(unwrap_used)]
413 allow_unwrap_in_consts: bool = true,
414 #[lints(unwrap_used)]
416 allow_unwrap_in_tests: bool = false,
417 #[lints(expect_used, unwrap_used)]
425 allow_unwrap_types: Vec<String> = Vec::new(),
426 #[lints(useless_vec)]
428 allow_useless_vec_in_tests: bool = false,
429 #[lints(path_ends_with_ext)]
431 allowed_dotfiles: Vec<String> = Vec::default(),
432 #[lints(multiple_crate_versions)]
434 allowed_duplicate_crates: Vec<String> = Vec::new(),
435 #[lints(min_ident_chars)]
439 allowed_idents_below_min_chars: Vec<String> =
440 DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect(),
441 #[lints(module_name_repetitions)]
459 allowed_prefixes: Vec<String> = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect(),
460 #[lints(disallowed_script_idents)]
462 allowed_scripts: Vec<String> = vec!["Latin".to_string()],
463 #[lints(wildcard_imports)]
477 allowed_wildcard_imports: Vec<String> = Vec::new(),
478 #[lints(arithmetic_side_effects)]
493 arithmetic_side_effects_allowed: Vec<String> = <_>::default(),
494 #[lints(arithmetic_side_effects)]
509 arithmetic_side_effects_allowed_binary: Vec<(String, String)> = <_>::default(),
510 #[lints(arithmetic_side_effects)]
518 arithmetic_side_effects_allowed_unary: Vec<String> = <_>::default(),
519 #[lints(large_const_arrays, large_stack_arrays)]
521 array_size_threshold: u64 = 16 * 1024,
522 #[lints(
524 box_collection,
525 enum_variant_names,
526 large_types_passed_by_value,
527 linkedlist,
528 needless_pass_by_ref_mut,
529 option_option,
530 owned_cow,
531 rc_buffer,
532 rc_mutex,
533 redundant_allocation,
534 ref_option,
535 single_call_fn,
536 trivially_copy_pass_by_ref,
537 unnecessary_box_returns,
538 unnecessary_wraps,
539 unused_self,
540 upper_case_acronyms,
541 vec_box,
542 wrong_self_convention,
543 )]
544 avoid_breaking_exported_api: bool = true,
545 #[disallowed_paths_allow_replacements = false]
547 #[lints(await_holding_invalid_type)]
548 await_holding_invalid_types: Vec<DisallowedPathWithoutReplacement> = Vec::new(),
549 #[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)]
553 blacklisted_names: Vec<String> = Vec::new(),
554 #[lints(cargo_common_metadata)]
556 cargo_ignore_publish: bool = false,
557 #[lints(needless_late_init)]
580 check_grouped_late_init: bool = true,
581 #[lints(incompatible_msrv)]
583 check_incompatible_msrv_in_tests: bool = false,
584 #[lints(inconsistent_struct_constructor)]
603 check_inconsistent_struct_field_initializers: bool = false,
604 #[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
606 check_private_items: bool = false,
607 #[lints(cognitive_complexity)]
609 cognitive_complexity_threshold: u64 = 25,
610 #[lints(excessive_precision)]
612 const_literal_digits_threshold: usize = 30,
613 #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
617 cyclomatic_complexity_threshold: u64 = 25,
618 #[disallowed_paths_allow_replacements = true]
627 #[lints(disallowed_fields)]
628 disallowed_fields: Vec<DisallowedPath> = Vec::new(),
629 #[disallowed_paths_allow_replacements = true]
638 #[lints(disallowed_macros)]
639 disallowed_macros: Vec<DisallowedPath> = Vec::new(),
640 #[disallowed_paths_allow_replacements = true]
649 #[lints(disallowed_methods)]
650 disallowed_methods: Vec<DisallowedPath> = Vec::new(),
651 #[lints(disallowed_names)]
655 disallowed_names: Vec<String> = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(),
656 #[disallowed_paths_allow_replacements = true]
665 #[lints(disallowed_types)]
666 disallowed_types: Vec<DisallowedPath> = Vec::new(),
667 #[lints(doc_markdown)]
673 doc_valid_idents: Vec<String> = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect(),
674 #[lints(non_send_fields_in_send_ty)]
676 enable_raw_pointer_heuristic_for_send: bool = true,
677 #[lints(explicit_iter_loop)]
695 enforce_iter_loop_reborrow: bool = false,
696 #[lints(missing_enforced_import_renames)]
698 enforced_import_renames: Vec<Rename> = Vec::new(),
699 #[lints(enum_variant_names)]
701 enum_variant_name_threshold: u64 = 3,
702 #[lints(large_enum_variant)]
704 enum_variant_size_threshold: u64 = 200,
705 #[lints(excessive_nesting)]
707 excessive_nesting_threshold: u64 = 0,
708 #[lints(large_futures)]
710 future_size_threshold: u64 = 16 * 1024,
711 #[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)]
713 ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()]),
714 #[lints(multiple_inherent_impl)]
716 inherent_impl_lint_scope: InherentImplLintScope = InherentImplLintScope::Crate,
717 #[lints(result_large_err)]
720 large_error_ignored: Vec<String> = Vec::default(),
721 #[lints(result_large_err)]
723 large_error_threshold: u64 = 128,
724 #[lints(collapsible_else_if, collapsible_if)]
727 lint_commented_code: bool = false,
728 #[conf_deprecated("Please use `check-inconsistent-struct-field-initializers` instead", check_inconsistent_struct_field_initializers)]
733 lint_inconsistent_struct_field_initializers: bool = false,
734 #[lints(decimal_literal_representation)]
736 literal_representation_threshold: u64 = 16384,
737 #[lints(manual_let_else)]
740 matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes,
741 #[lints(fn_params_excessive_bools)]
744 max_fn_params_bools: u64 = 3,
745 #[lints(large_include_file)]
747 max_include_file_size: u64 = 1_000_000,
748 #[lints(struct_excessive_bools)]
750 max_struct_bools: u64 = 3,
751 #[lints(index_refutable_slice)]
755 max_suggested_slice_pattern_length: u64 = 3,
756 #[lints(type_repetition_in_bounds)]
758 max_trait_bounds: u64 = 3,
759 #[lints(min_ident_chars)]
761 min_ident_chars_threshold: u64 = 1,
762 #[lints(missing_docs_in_private_items)]
764 missing_docs_allow_unused: bool = false,
765 #[lints(missing_docs_in_private_items)]
768 missing_docs_in_crate_items: bool = false,
769 #[lints(arbitrary_source_item_ordering)]
771 module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(),
772 #[lints(arbitrary_source_item_ordering)]
777 module_items_ordered_within_groupings: SourceItemOrderingWithinModuleItemGroupings =
778 SourceItemOrderingWithinModuleItemGroupings::None,
779 #[default_text = "current version"]
781 #[lints(
782 allow_attributes,
783 allow_attributes_without_reason,
784 almost_complete_range,
785 approx_constant,
786 assigning_clones,
787 borrow_as_ptr,
788 cast_abs_to_unsigned,
789 checked_conversions,
790 cloned_instead_of_copied,
791 collapsible_match,
792 collapsible_str_replace,
793 deprecated_cfg_attr,
794 derivable_impls,
795 err_expect,
796 filter_map_next,
797 from_over_into,
798 if_then_some_else_none,
799 index_refutable_slice,
800 inefficient_to_string,
801 io_other_error,
802 iter_kv_map,
803 legacy_numeric_constants,
804 len_zero,
805 lines_filter_map_ok,
806 manual_abs_diff,
807 manual_bits,
808 manual_c_str_literals,
809 manual_clamp,
810 manual_div_ceil,
811 manual_flatten,
812 manual_hash_one,
813 manual_is_ascii_check,
814 manual_is_power_of_two,
815 manual_is_variant_and,
816 manual_isolate_lowest_one,
817 manual_let_else,
818 manual_midpoint,
819 manual_non_exhaustive,
820 manual_noop_waker,
821 manual_option_as_slice,
822 manual_pattern_char_comparison,
823 manual_range_contains,
824 manual_rem_euclid,
825 manual_repeat_n,
826 manual_retain,
827 manual_slice_fill,
828 manual_slice_size_calculation,
829 manual_split_once,
830 manual_str_repeat,
831 manual_strip,
832 manual_take,
833 manual_try_fold,
834 map_clone,
835 map_unwrap_or,
836 map_with_unused_argument_over_ranges,
837 match_like_matches_macro,
838 mem_replace_option_with_some,
839 mem_replace_with_default,
840 missing_const_for_fn,
841 needless_borrow,
842 non_std_lazy_statics,
843 option_as_ref_deref,
844 or_fun_call,
845 ptr_as_ptr,
846 question_mark,
847 redundant_field_names,
848 redundant_static_lifetimes,
849 repeat_vec_with_capacity,
850 same_item_push,
851 seek_from_current,
852 to_digit_is_some,
853 transmute_ptr_to_ref,
854 tuple_array_conversions,
855 type_repetition_in_bounds,
856 unchecked_time_subtraction,
857 uninlined_format_args,
858 unnecessary_lazy_evaluations,
859 unnecessary_unwrap,
860 unnested_or_patterns,
861 unused_trait_names,
862 use_self,
863 zero_ptr,
864 )]
865 msrv: Msrv = Msrv::default(),
866 #[lints(large_types_passed_by_value)]
868 pass_by_value_size_limit: u64 = 256,
869 #[lints(pub_underscore_fields)]
872 pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported,
873 #[lints(use_self)]
875 recursive_self_in_type_definitions: bool = true,
876 #[lints(semicolon_inside_block)]
878 semicolon_inside_block_ignore_singleline: bool = false,
879 #[lints(semicolon_outside_block)]
881 semicolon_outside_block_ignore_multiline: bool = false,
882 #[lints(many_single_char_names)]
884 single_char_binding_names_threshold: u64 = 4,
885 #[lints(arbitrary_source_item_ordering)]
887 source_item_ordering: SourceItemOrdering = DEFAULT_SOURCE_ITEM_ORDERING.into(),
888 #[lints(large_stack_frames)]
890 stack_size_threshold: u64 = 512_000,
891 #[lints(nonstandard_macro_braces)]
897 standard_macro_braces: Vec<MacroMatcher> = Vec::new(),
898 #[lints(struct_field_names)]
900 struct_field_name_threshold: u64 = 3,
901 #[lints(indexing_slicing)]
907 suppress_restriction_lint_in_const: bool = false,
908 #[lints(boxed_local, useless_vec)]
910 too_large_for_stack: u64 = 200,
911 #[lints(too_many_arguments)]
913 too_many_arguments_threshold: u64 = 7,
914 #[lints(too_many_lines)]
916 too_many_lines_threshold: u64 = 100,
917 #[lints(arbitrary_source_item_ordering)]
919 trait_assoc_item_kinds_order: SourceItemOrderingTraitAssocItemKinds = DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER.into(),
920 #[default_text = "target_pointer_width"]
923 #[lints(trivially_copy_pass_by_ref)]
924 trivial_copy_size_limit: Option<u64> = None,
925 #[lints(type_complexity)]
927 type_complexity_threshold: u64 = 250,
928 #[lints(unnecessary_box_returns)]
930 unnecessary_box_size: u64 = 128,
931 #[lints(unreadable_literal)]
933 unreadable_literal_lint_fractions: bool = true,
934 #[lints(upper_case_acronyms)]
936 upper_case_acronyms_aggressive: bool = false,
937 #[lints(vec_box)]
939 vec_box_size_threshold: u64 = 4096,
940 #[lints(verbose_bit_mask)]
942 verbose_bit_mask_threshold: u64 = 1,
943 #[lints(wildcard_imports)]
946 warn_on_all_wildcard_imports: bool = false,
947 #[lints(macro_metavars_in_unsafe)]
949 warn_unsafe_macro_metavars_in_private_macros: bool = false,
950}
951
952pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
958 const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
960
961 let mut current = env::var_os("CLIPPY_CONF_DIR")
964 .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
965 .map_or_else(|| PathBuf::from("."), PathBuf::from)
966 .canonicalize()?;
967
968 let mut found_config: Option<PathBuf> = None;
969 let mut warnings = vec![];
970
971 loop {
972 for config_file_name in &CONFIG_FILE_NAMES {
973 if let Ok(config_file) = current.join(config_file_name).canonicalize() {
974 match fs::metadata(&config_file) {
975 Err(e) if e.kind() == io::ErrorKind::NotFound => {},
976 Err(e) => return Err(e),
977 Ok(md) if md.is_dir() => {},
978 Ok(_) => {
979 if let Some(ref found_config) = found_config {
981 warnings.push(format!(
982 "using config file `{}`, `{}` will be ignored",
983 found_config.display(),
984 config_file.display()
985 ));
986 } else {
987 found_config = Some(config_file);
988 }
989 },
990 }
991 }
992 }
993
994 if found_config.is_some() {
995 return Ok((found_config, warnings));
996 }
997
998 if !current.pop() {
1000 return Ok((None, warnings));
1001 }
1002 }
1003}
1004
1005fn deserialize(file: &SourceFile) -> TryConf {
1006 match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(file)) {
1007 Ok(mut conf) => {
1008 extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
1009 extend_vec_if_indicator_present(&mut conf.conf.allowed_prefixes, DEFAULT_ALLOWED_PREFIXES);
1010 extend_vec_if_indicator_present(
1011 &mut conf.conf.allow_renamed_params_for,
1012 DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS,
1013 );
1014
1015 if let SourceItemOrderingWithinModuleItemGroupings::Custom(groupings) =
1018 &conf.conf.module_items_ordered_within_groupings
1019 {
1020 for grouping in groupings {
1021 if !conf.conf.module_item_order_groupings.is_grouping(grouping) {
1022 let names = conf.conf.module_item_order_groupings.grouping_names();
1026 let suggestion = suggest_candidate(grouping, names.iter().map(String::as_str))
1027 .map(|s| format!(" perhaps you meant `{s}`?"))
1028 .unwrap_or_default();
1029 let names = names.iter().map(|s| format!("`{s}`")).join(", ");
1030 let message = format!(
1031 "unknown ordering group: `{grouping}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}"
1032 );
1033
1034 let span = conf
1035 .value_spans
1036 .get("module_item_order_groupings")
1037 .cloned()
1038 .unwrap_or_default();
1039 conf.errors.push(ConfError::spanned(file, message, None, span));
1040 }
1041 }
1042 }
1043
1044 if conf.conf.allowed_idents_below_min_chars.iter().any(|e| e == "..") {
1046 conf.conf
1047 .allowed_idents_below_min_chars
1048 .extend(DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string));
1049 }
1050 if conf.conf.doc_valid_idents.iter().any(|e| e == "..") {
1051 conf.conf
1052 .doc_valid_idents
1053 .extend(DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string));
1054 }
1055
1056 conf
1057 },
1058 Err(e) => TryConf::from_toml_error(file, &e),
1059 }
1060}
1061
1062fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
1063 if vec.contains(&"..".to_string()) {
1064 vec.extend(default.iter().map(ToString::to_string));
1065 }
1066}
1067
1068impl Conf {
1069 pub fn read(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> &'static Conf {
1070 static CONF: OnceLock<Conf> = OnceLock::new();
1071 CONF.get_or_init(|| Conf::read_inner(sess, path))
1072 }
1073
1074 fn read_inner(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
1075 match path {
1076 Ok((_, warnings)) => {
1077 for warning in warnings {
1078 sess.dcx().warn(warning.clone());
1079 }
1080 },
1081 Err(error) => {
1082 sess.dcx()
1083 .err(format!("error finding Clippy's configuration file: {error}"));
1084 },
1085 }
1086
1087 let TryConf {
1088 mut conf,
1089 value_spans: _,
1090 errors,
1091 warnings,
1092 } = match path {
1093 Ok((Some(path), _)) => match sess.source_map().load_file(path) {
1094 Ok(file) => deserialize(&file),
1095 Err(error) => {
1096 sess.dcx().err(format!("failed to read `{}`: {error}", path.display()));
1097 TryConf::default()
1098 },
1099 },
1100 _ => TryConf::default(),
1101 };
1102
1103 conf.msrv.read_cargo(sess);
1104
1105 for error in errors {
1107 let mut diag = sess.dcx().struct_span_err(
1108 error.span,
1109 format!("error reading Clippy's configuration file: {}", error.message),
1110 );
1111
1112 if let Some(sugg) = error.suggestion {
1113 diag.span_suggestion(error.span, sugg.message, sugg.suggestion, Applicability::MaybeIncorrect);
1114 }
1115
1116 diag.emit();
1117 }
1118
1119 for warning in warnings {
1120 sess.dcx().span_warn(
1121 warning.span,
1122 format!("error reading Clippy's configuration file: {}", warning.message),
1123 );
1124 }
1125
1126 conf
1127 }
1128}
1129
1130const SEPARATOR_WIDTH: usize = 4;
1131
1132#[derive(Debug)]
1133struct FieldError {
1134 error: String,
1135 suggestion: Option<Suggestion>,
1136}
1137
1138#[derive(Debug)]
1139struct Suggestion {
1140 message: &'static str,
1141 suggestion: &'static str,
1142}
1143
1144impl std::error::Error for FieldError {}
1145
1146impl Display for FieldError {
1147 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1148 f.pad(&self.error)
1149 }
1150}
1151
1152impl serde::de::Error for FieldError {
1153 fn custom<T: Display>(msg: T) -> Self {
1154 Self {
1155 error: msg.to_string(),
1156 suggestion: None,
1157 }
1158 }
1159
1160 fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
1161 use fmt::Write;
1164
1165 let metadata = get_configuration_metadata();
1166 let deprecated = metadata
1167 .iter()
1168 .filter_map(|conf| {
1169 if conf.deprecation_reason.is_some() {
1170 Some(conf.name.as_str())
1171 } else {
1172 None
1173 }
1174 })
1175 .collect::<Vec<_>>();
1176
1177 let mut expected = expected
1178 .iter()
1179 .copied()
1180 .filter(|name| !deprecated.contains(name))
1181 .collect::<Vec<_>>();
1182 expected.sort_unstable();
1183
1184 let (rows, column_widths) = calculate_dimensions(&expected);
1185
1186 let mut msg = format!("unknown field `{field}`, expected one of");
1187 for row in 0..rows {
1188 writeln!(msg).unwrap();
1189 for (column, column_width) in column_widths.iter().copied().enumerate() {
1190 let index = column * rows + row;
1191 let field = expected.get(index).copied().unwrap_or_default();
1192 write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap();
1193 }
1194 }
1195
1196 let suggestion = suggest_candidate(field, expected).map(|suggestion| Suggestion {
1197 message: "perhaps you meant",
1198 suggestion,
1199 });
1200
1201 Self { error: msg, suggestion }
1202 }
1203}
1204
1205fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
1206 let columns = env::var("CLIPPY_TERMINAL_WIDTH")
1207 .ok()
1208 .and_then(|s| <usize as FromStr>::from_str(&s).ok())
1209 .map_or(1, |terminal_width| {
1210 let max_field_width = fields.iter().map(|field| field.len()).max().unwrap();
1211 cmp::max(1, terminal_width / (SEPARATOR_WIDTH + max_field_width))
1212 });
1213
1214 let rows = fields.len().div_ceil(columns);
1215
1216 let column_widths = (0..columns)
1217 .map(|column| {
1218 if column < columns - 1 {
1219 (0..rows)
1220 .map(|row| {
1221 let index = column * rows + row;
1222 let field = fields.get(index).copied().unwrap_or_default();
1223 field.len()
1224 })
1225 .max()
1226 .unwrap()
1227 } else {
1228 0
1230 }
1231 })
1232 .collect::<Vec<_>>();
1233
1234 (rows, column_widths)
1235}
1236
1237fn suggest_candidate<'a, I>(value: &str, candidates: I) -> Option<&'a str>
1240where
1241 I: IntoIterator<Item = &'a str>,
1242{
1243 candidates
1244 .into_iter()
1245 .filter_map(|expected| {
1246 let dist = edit_distance(value, expected, 4)?;
1247 Some((dist, expected))
1248 })
1249 .min_by_key(|&(dist, _)| dist)
1250 .map(|(_, suggestion)| suggestion)
1251}
1252
1253#[cfg(test)]
1254mod tests {
1255 use serde::de::IgnoredAny;
1256 use std::collections::{HashMap, HashSet};
1257 use std::fs;
1258 use walkdir::WalkDir;
1259
1260 #[test]
1261 fn configs_are_tested() {
1262 let mut names: HashSet<String> = crate::get_configuration_metadata()
1263 .into_iter()
1264 .filter_map(|meta| {
1265 if meta.deprecation_reason.is_none() {
1266 Some(meta.name.replace('_', "-"))
1267 } else {
1268 None
1269 }
1270 })
1271 .collect();
1272
1273 let toml_files = WalkDir::new("../tests")
1274 .into_iter()
1275 .map(Result::unwrap)
1276 .filter(|entry| entry.file_name() == "clippy.toml");
1277
1278 for entry in toml_files {
1279 let file = fs::read_to_string(entry.path()).unwrap();
1280 #[expect(clippy::zero_sized_map_values)]
1281 if let Ok(map) = toml::from_str::<HashMap<String, IgnoredAny>>(&file) {
1282 for name in map.keys() {
1283 names.remove(name.as_str());
1284 }
1285 }
1286 }
1287
1288 assert!(
1289 names.is_empty(),
1290 "Configuration variable lacks test: {names:?}\nAdd a test to `tests/ui-toml`"
1291 );
1292 }
1293}