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 "OCaml",
42 "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry",
43 "OpenType",
44 "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport",
45 "WebP", "OpenExr", "YCbCr", "sRGB",
46 "TensorFlow",
47 "TrueType",
48 "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS",
49 "TeX", "LaTeX", "BibTeX", "BibLaTeX",
50 "MinGW",
51 "CamelCase",
52];
53const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
54const DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z", "w", "n"];
55const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"];
56const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] =
57 &["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"];
58const DEFAULT_MODULE_ITEM_ORDERING_GROUPS: &[(&str, &[SourceItemOrderingModuleItemKind])] = {
59 #[allow(clippy::enum_glob_use)] use SourceItemOrderingModuleItemKind::*;
61 &[
62 ("modules", &[ExternCrate, Mod, ForeignMod]),
63 ("use", &[Use]),
64 ("macros", &[Macro]),
65 ("global_asm", &[GlobalAsm]),
66 ("UPPER_SNAKE_CASE", &[Static, Const]),
67 ("PascalCase", &[TyAlias, Enum, Struct, Union, Trait, TraitAlias, Impl]),
68 ("lower_snake_case", &[Fn]),
69 ]
70};
71const DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER: &[SourceItemOrderingTraitAssocItemKind] = {
72 #[allow(clippy::enum_glob_use)] use SourceItemOrderingTraitAssocItemKind::*;
74 &[Const, Type, Fn]
75};
76const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
77 #[allow(clippy::enum_glob_use)] use SourceItemOrderingCategory::*;
79 &[Enum, Impl, Module, Struct, Trait]
80};
81
82#[derive(Default)]
84struct TryConf {
85 conf: Conf,
86 value_spans: HashMap<String, Range<usize>>,
87 errors: Vec<ConfError>,
88 warnings: Vec<ConfError>,
89}
90
91impl TryConf {
92 fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
93 Self {
94 conf: Conf::default(),
95 value_spans: HashMap::default(),
96 errors: vec![ConfError::from_toml(file, error)],
97 warnings: vec![],
98 }
99 }
100}
101
102#[derive(Debug)]
103struct ConfError {
104 message: String,
105 suggestion: Option<Suggestion>,
106 span: Span,
107}
108
109impl ConfError {
110 fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
111 let span = error.span().unwrap_or(0..file.normalized_source_len.0 as usize);
112 Self::spanned(file, error.message(), None, span)
113 }
114
115 fn spanned(
116 file: &SourceFile,
117 message: impl Into<String>,
118 suggestion: Option<Suggestion>,
119 span: Range<usize>,
120 ) -> Self {
121 Self {
122 message: message.into(),
123 suggestion,
124 span: span_from_toml_range(file, span),
125 }
126 }
127}
128
129pub fn sanitize_explanation(raw_docs: &str) -> String {
131 let mut explanation = String::with_capacity(128);
133 let mut in_code = false;
134 for line in raw_docs.lines() {
135 let line = line.strip_prefix(' ').unwrap_or(line);
136
137 if let Some(lang) = line.strip_prefix("```") {
138 let tag = lang.split_once(',').map_or(lang, |(left, _)| left);
139 if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") {
140 explanation += "```rust\n";
141 } else {
142 explanation += line;
143 explanation.push('\n');
144 }
145 in_code = !in_code;
146 } else if !(in_code && line.starts_with("# ")) {
147 explanation += line;
148 explanation.push('\n');
149 }
150 }
151
152 explanation
153}
154
155macro_rules! wrap_option {
156 () => {
157 None
158 };
159 ($x:literal) => {
160 Some($x)
161 };
162}
163
164macro_rules! default_text {
165 ($value:expr) => {{
166 let mut text = String::new();
167 $value.serialize(toml::ser::ValueSerializer::new(&mut text)).unwrap();
168 text
169 }};
170 ($value:expr, $override:expr) => {
171 $override.to_string()
172 };
173}
174
175macro_rules! deserialize {
176 ($map:expr, $ty:ty, $errors:expr, $file:expr) => {{
177 let raw_value = $map.next_value::<toml::Spanned<toml::Value>>()?;
178 let value_span = raw_value.span();
179 let value = match <$ty>::deserialize(raw_value.into_inner()) {
180 Err(e) => {
181 $errors.push(ConfError::spanned(
182 $file,
183 e.to_string().replace('\n', " ").trim(),
184 None,
185 value_span,
186 ));
187 continue;
188 },
189 Ok(value) => value,
190 };
191 (value, value_span)
192 }};
193
194 ($map:expr, $ty:ty, $errors:expr, $file:expr, $replacements_allowed:expr) => {{
195 let array = $map.next_value::<Vec<toml::Spanned<toml::Value>>>()?;
196 let mut disallowed_paths_span = Range {
197 start: usize::MAX,
198 end: usize::MIN,
199 };
200 let mut disallowed_paths = Vec::new();
201 for raw_value in array {
202 let value_span = raw_value.span();
203 let mut disallowed_path = match DisallowedPath::<$replacements_allowed>::deserialize(raw_value.into_inner())
204 {
205 Err(e) => {
206 $errors.push(ConfError::spanned(
207 $file,
208 e.to_string().replace('\n', " ").trim(),
209 None,
210 value_span,
211 ));
212 continue;
213 },
214 Ok(disallowed_path) => disallowed_path,
215 };
216 disallowed_paths_span = union(&disallowed_paths_span, &value_span);
217 disallowed_path.set_span(span_from_toml_range($file, value_span));
218 disallowed_paths.push(disallowed_path);
219 }
220 (disallowed_paths, disallowed_paths_span)
221 }};
222}
223
224macro_rules! define_Conf {
225 ($(
226 $(#[doc = $doc:literal])+
227 $(#[conf_deprecated($dep:literal, $new_conf:ident)])?
228 $(#[default_text = $default_text:expr])?
229 $(#[disallowed_paths_allow_replacements = $replacements_allowed:expr])?
230 $(#[lints($($for_lints:ident),* $(,)?)])?
231 $name:ident: $ty:ty = $default:expr,
232 )*) => {
233 pub struct Conf {
235 $($(#[cfg_attr(doc, doc = $doc)])+ pub $name: $ty,)*
236 }
237
238 mod defaults {
239 use super::*;
240 $(pub fn $name() -> $ty { $default })*
241 }
242
243 impl Default for Conf {
244 fn default() -> Self {
245 Self { $($name: defaults::$name(),)* }
246 }
247 }
248
249 #[derive(Deserialize)]
250 #[serde(field_identifier, rename_all = "kebab-case")]
251 #[expect(non_camel_case_types)]
252 enum Field { $($name,)* third_party, }
253
254 struct ConfVisitor<'a>(&'a SourceFile);
255
256 impl<'de> Visitor<'de> for ConfVisitor<'_> {
257 type Value = TryConf;
258
259 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
260 formatter.write_str("Conf")
261 }
262
263 fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
264 let mut value_spans = HashMap::new();
265 let mut errors = Vec::new();
266 let mut warnings = Vec::new();
267
268 $(let mut $name = None;)*
270
271 while let Some(name) = map.next_key::<toml::Spanned<String>>()? {
273 let field = match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
274 Err(e) => {
275 let e: FieldError = e;
276 errors.push(ConfError::spanned(self.0, e.error, e.suggestion, name.span()));
277 continue;
278 }
279 Ok(field) => field
280 };
281
282 match field {
283 $(Field::$name => {
284 $(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
286 let (value, value_span) =
287 deserialize!(map, $ty, errors, self.0 $(, $replacements_allowed)?);
288 if $name.is_some() {
290 errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
291 continue;
292 }
293 $name = Some(value);
294 value_spans.insert(name.get_ref().as_str().to_string(), value_span);
295 $(match $new_conf {
298 Some(_) => errors.push(ConfError::spanned(self.0, concat!(
299 "duplicate field `", stringify!($new_conf),
300 "` (provided as `", stringify!($name), "`)"
301 ), None, name.span())),
302 None => $new_conf = $name.clone(),
303 })?
304 })*
305 Field::third_party => drop(map.next_value::<IgnoredAny>())
307 }
308 }
309 let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
310 Ok(TryConf { conf, value_spans, errors, warnings })
311 }
312 }
313
314 pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
315 vec![$(
316 ClippyConfiguration {
317 name: stringify!($name).replace('_', "-"),
318 default: default_text!(defaults::$name() $(, $default_text)?),
319 lints: &[$($(stringify!($for_lints)),*)?],
320 doc: concat!($($doc, '\n',)*),
321 deprecation_reason: wrap_option!($($dep)?)
322 },
323 )*]
324 }
325 };
326}
327
328fn union(x: &Range<usize>, y: &Range<usize>) -> Range<usize> {
329 Range {
330 start: cmp::min(x.start, y.start),
331 end: cmp::max(x.end, y.end),
332 }
333}
334
335fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
336 Span::new(
337 file.start_pos + BytePos::from_usize(span.start),
338 file.start_pos + BytePos::from_usize(span.end),
339 SyntaxContext::root(),
340 None,
341 )
342}
343
344define_Conf! {
345 #[lints(absolute_paths)]
347 absolute_paths_allowed_crates: Vec<String> = Vec::new(),
348 #[lints(absolute_paths)]
351 absolute_paths_max_segments: u64 = 2,
352 #[lints(undocumented_unsafe_blocks)]
354 accept_comment_above_attributes: bool = true,
355 #[lints(undocumented_unsafe_blocks)]
357 accept_comment_above_statement: bool = true,
358 #[lints(modulo_arithmetic)]
360 allow_comparison_to_zero: bool = true,
361 #[lints(dbg_macro)]
363 allow_dbg_in_tests: bool = false,
364 #[lints(module_name_repetitions)]
366 allow_exact_repetitions: bool = true,
367 #[lints(expect_used)]
369 allow_expect_in_consts: bool = true,
370 #[lints(expect_used)]
372 allow_expect_in_tests: bool = false,
373 #[lints(indexing_slicing)]
375 allow_indexing_slicing_in_tests: bool = false,
376 #[lints(large_stack_frames)]
378 allow_large_stack_frames_in_tests: bool = true,
379 #[lints(uninlined_format_args)]
381 allow_mixed_uninlined_format_args: bool = true,
382 #[lints(needless_raw_string_hashes)]
384 allow_one_hash_in_raw_strings: bool = false,
385 #[lints(panic)]
387 allow_panic_in_tests: bool = false,
388 #[lints(print_stderr, print_stdout)]
390 allow_print_in_tests: bool = false,
391 #[lints(module_inception)]
393 allow_private_module_inception: bool = false,
394 #[lints(renamed_function_params)]
408 allow_renamed_params_for: Vec<String> =
409 DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect(),
410 #[lints(unwrap_used)]
412 allow_unwrap_in_consts: bool = true,
413 #[lints(unwrap_used)]
415 allow_unwrap_in_tests: bool = false,
416 #[lints(expect_used, unwrap_used)]
424 allow_unwrap_types: Vec<String> = Vec::new(),
425 #[lints(useless_vec)]
427 allow_useless_vec_in_tests: bool = false,
428 #[lints(path_ends_with_ext)]
430 allowed_dotfiles: Vec<String> = Vec::default(),
431 #[lints(multiple_crate_versions)]
433 allowed_duplicate_crates: Vec<String> = Vec::new(),
434 #[lints(min_ident_chars)]
438 allowed_idents_below_min_chars: Vec<String> =
439 DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect(),
440 #[lints(module_name_repetitions)]
458 allowed_prefixes: Vec<String> = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect(),
459 #[lints(disallowed_script_idents)]
461 allowed_scripts: Vec<String> = vec!["Latin".to_string()],
462 #[lints(wildcard_imports)]
476 allowed_wildcard_imports: Vec<String> = Vec::new(),
477 #[lints(arithmetic_side_effects)]
492 arithmetic_side_effects_allowed: Vec<String> = <_>::default(),
493 #[lints(arithmetic_side_effects)]
508 arithmetic_side_effects_allowed_binary: Vec<(String, String)> = <_>::default(),
509 #[lints(arithmetic_side_effects)]
517 arithmetic_side_effects_allowed_unary: Vec<String> = <_>::default(),
518 #[lints(large_const_arrays, large_stack_arrays)]
520 array_size_threshold: u64 = 16 * 1024,
521 #[lints(
523 box_collection,
524 enum_variant_names,
525 large_types_passed_by_value,
526 linkedlist,
527 needless_pass_by_ref_mut,
528 option_option,
529 owned_cow,
530 rc_buffer,
531 rc_mutex,
532 redundant_allocation,
533 ref_option,
534 single_call_fn,
535 trivially_copy_pass_by_ref,
536 unnecessary_box_returns,
537 unnecessary_wraps,
538 unused_self,
539 upper_case_acronyms,
540 vec_box,
541 wrong_self_convention,
542 )]
543 avoid_breaking_exported_api: bool = true,
544 #[disallowed_paths_allow_replacements = false]
546 #[lints(await_holding_invalid_type)]
547 await_holding_invalid_types: Vec<DisallowedPathWithoutReplacement> = Vec::new(),
548 #[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)]
552 blacklisted_names: Vec<String> = Vec::new(),
553 #[lints(cargo_common_metadata)]
555 cargo_ignore_publish: bool = false,
556 #[lints(incompatible_msrv)]
558 check_incompatible_msrv_in_tests: bool = false,
559 #[lints(inconsistent_struct_constructor)]
578 check_inconsistent_struct_field_initializers: bool = false,
579 #[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
581 check_private_items: bool = false,
582 #[lints(cognitive_complexity)]
584 cognitive_complexity_threshold: u64 = 25,
585 #[lints(excessive_precision)]
587 const_literal_digits_threshold: usize = 30,
588 #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
592 cyclomatic_complexity_threshold: u64 = 25,
593 #[disallowed_paths_allow_replacements = true]
602 #[lints(disallowed_fields)]
603 disallowed_fields: Vec<DisallowedPath> = Vec::new(),
604 #[disallowed_paths_allow_replacements = true]
613 #[lints(disallowed_macros)]
614 disallowed_macros: Vec<DisallowedPath> = Vec::new(),
615 #[disallowed_paths_allow_replacements = true]
624 #[lints(disallowed_methods)]
625 disallowed_methods: Vec<DisallowedPath> = Vec::new(),
626 #[lints(disallowed_names)]
630 disallowed_names: Vec<String> = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(),
631 #[disallowed_paths_allow_replacements = true]
640 #[lints(disallowed_types)]
641 disallowed_types: Vec<DisallowedPath> = Vec::new(),
642 #[lints(doc_markdown)]
648 doc_valid_idents: Vec<String> = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect(),
649 #[lints(non_send_fields_in_send_ty)]
651 enable_raw_pointer_heuristic_for_send: bool = true,
652 #[lints(explicit_iter_loop)]
670 enforce_iter_loop_reborrow: bool = false,
671 #[lints(missing_enforced_import_renames)]
673 enforced_import_renames: Vec<Rename> = Vec::new(),
674 #[lints(enum_variant_names)]
676 enum_variant_name_threshold: u64 = 3,
677 #[lints(large_enum_variant)]
679 enum_variant_size_threshold: u64 = 200,
680 #[lints(excessive_nesting)]
682 excessive_nesting_threshold: u64 = 0,
683 #[lints(large_futures)]
685 future_size_threshold: u64 = 16 * 1024,
686 #[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)]
688 ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()]),
689 #[lints(multiple_inherent_impl)]
691 inherent_impl_lint_scope: InherentImplLintScope = InherentImplLintScope::Crate,
692 #[lints(result_large_err)]
695 large_error_ignored: Vec<String> = Vec::default(),
696 #[lints(result_large_err)]
698 large_error_threshold: u64 = 128,
699 #[lints(collapsible_else_if, collapsible_if)]
702 lint_commented_code: bool = false,
703 #[conf_deprecated("Please use `check-inconsistent-struct-field-initializers` instead", check_inconsistent_struct_field_initializers)]
708 lint_inconsistent_struct_field_initializers: bool = false,
709 #[lints(decimal_literal_representation)]
711 literal_representation_threshold: u64 = 16384,
712 #[lints(manual_let_else)]
715 matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes,
716 #[lints(fn_params_excessive_bools)]
718 max_fn_params_bools: u64 = 3,
719 #[lints(large_include_file)]
721 max_include_file_size: u64 = 1_000_000,
722 #[lints(struct_excessive_bools)]
724 max_struct_bools: u64 = 3,
725 #[lints(index_refutable_slice)]
729 max_suggested_slice_pattern_length: u64 = 3,
730 #[lints(type_repetition_in_bounds)]
732 max_trait_bounds: u64 = 3,
733 #[lints(min_ident_chars)]
735 min_ident_chars_threshold: u64 = 1,
736 #[lints(missing_docs_in_private_items)]
738 missing_docs_allow_unused: bool = false,
739 #[lints(missing_docs_in_private_items)]
742 missing_docs_in_crate_items: bool = false,
743 #[lints(arbitrary_source_item_ordering)]
745 module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(),
746 #[lints(arbitrary_source_item_ordering)]
751 module_items_ordered_within_groupings: SourceItemOrderingWithinModuleItemGroupings =
752 SourceItemOrderingWithinModuleItemGroupings::None,
753 #[default_text = "current version"]
755 #[lints(
756 allow_attributes,
757 allow_attributes_without_reason,
758 almost_complete_range,
759 approx_constant,
760 assigning_clones,
761 borrow_as_ptr,
762 cast_abs_to_unsigned,
763 checked_conversions,
764 cloned_instead_of_copied,
765 collapsible_match,
766 collapsible_str_replace,
767 deprecated_cfg_attr,
768 derivable_impls,
769 err_expect,
770 filter_map_next,
771 from_over_into,
772 if_then_some_else_none,
773 index_refutable_slice,
774 inefficient_to_string,
775 io_other_error,
776 iter_kv_map,
777 legacy_numeric_constants,
778 len_zero,
779 lines_filter_map_ok,
780 manual_abs_diff,
781 manual_bits,
782 manual_c_str_literals,
783 manual_clamp,
784 manual_div_ceil,
785 manual_flatten,
786 manual_hash_one,
787 manual_is_ascii_check,
788 manual_is_power_of_two,
789 manual_let_else,
790 manual_midpoint,
791 manual_non_exhaustive,
792 manual_noop_waker,
793 manual_option_as_slice,
794 manual_pattern_char_comparison,
795 manual_range_contains,
796 manual_rem_euclid,
797 manual_repeat_n,
798 manual_retain,
799 manual_slice_fill,
800 manual_slice_size_calculation,
801 manual_split_once,
802 manual_str_repeat,
803 manual_strip,
804 manual_take,
805 manual_try_fold,
806 map_clone,
807 map_unwrap_or,
808 map_with_unused_argument_over_ranges,
809 match_like_matches_macro,
810 mem_replace_option_with_some,
811 mem_replace_with_default,
812 missing_const_for_fn,
813 needless_borrow,
814 non_std_lazy_statics,
815 option_as_ref_deref,
816 or_fun_call,
817 ptr_as_ptr,
818 question_mark,
819 redundant_field_names,
820 redundant_static_lifetimes,
821 repeat_vec_with_capacity,
822 same_item_push,
823 seek_from_current,
824 to_digit_is_some,
825 transmute_ptr_to_ref,
826 tuple_array_conversions,
827 type_repetition_in_bounds,
828 unchecked_time_subtraction,
829 uninlined_format_args,
830 unnecessary_lazy_evaluations,
831 unnecessary_unwrap,
832 unnested_or_patterns,
833 unused_trait_names,
834 use_self,
835 zero_ptr,
836 )]
837 msrv: Msrv = Msrv::default(),
838 #[lints(large_types_passed_by_value)]
840 pass_by_value_size_limit: u64 = 256,
841 #[lints(pub_underscore_fields)]
844 pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported,
845 #[lints(use_self)]
847 recursive_self_in_type_definitions: bool = true,
848 #[lints(semicolon_inside_block)]
850 semicolon_inside_block_ignore_singleline: bool = false,
851 #[lints(semicolon_outside_block)]
853 semicolon_outside_block_ignore_multiline: bool = false,
854 #[lints(many_single_char_names)]
856 single_char_binding_names_threshold: u64 = 4,
857 #[lints(arbitrary_source_item_ordering)]
859 source_item_ordering: SourceItemOrdering = DEFAULT_SOURCE_ITEM_ORDERING.into(),
860 #[lints(large_stack_frames)]
862 stack_size_threshold: u64 = 512_000,
863 #[lints(nonstandard_macro_braces)]
869 standard_macro_braces: Vec<MacroMatcher> = Vec::new(),
870 #[lints(struct_field_names)]
872 struct_field_name_threshold: u64 = 3,
873 #[lints(indexing_slicing)]
879 suppress_restriction_lint_in_const: bool = false,
880 #[lints(boxed_local, useless_vec)]
882 too_large_for_stack: u64 = 200,
883 #[lints(too_many_arguments)]
885 too_many_arguments_threshold: u64 = 7,
886 #[lints(too_many_lines)]
888 too_many_lines_threshold: u64 = 100,
889 #[lints(arbitrary_source_item_ordering)]
891 trait_assoc_item_kinds_order: SourceItemOrderingTraitAssocItemKinds = DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER.into(),
892 #[default_text = "target_pointer_width"]
895 #[lints(trivially_copy_pass_by_ref)]
896 trivial_copy_size_limit: Option<u64> = None,
897 #[lints(type_complexity)]
899 type_complexity_threshold: u64 = 250,
900 #[lints(unnecessary_box_returns)]
902 unnecessary_box_size: u64 = 128,
903 #[lints(unreadable_literal)]
905 unreadable_literal_lint_fractions: bool = true,
906 #[lints(upper_case_acronyms)]
908 upper_case_acronyms_aggressive: bool = false,
909 #[lints(vec_box)]
911 vec_box_size_threshold: u64 = 4096,
912 #[lints(verbose_bit_mask)]
914 verbose_bit_mask_threshold: u64 = 1,
915 #[lints(wildcard_imports)]
918 warn_on_all_wildcard_imports: bool = false,
919 #[lints(macro_metavars_in_unsafe)]
921 warn_unsafe_macro_metavars_in_private_macros: bool = false,
922}
923
924pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
930 const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
932
933 let mut current = env::var_os("CLIPPY_CONF_DIR")
936 .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
937 .map_or_else(|| PathBuf::from("."), PathBuf::from)
938 .canonicalize()?;
939
940 let mut found_config: Option<PathBuf> = None;
941 let mut warnings = vec![];
942
943 loop {
944 for config_file_name in &CONFIG_FILE_NAMES {
945 if let Ok(config_file) = current.join(config_file_name).canonicalize() {
946 match fs::metadata(&config_file) {
947 Err(e) if e.kind() == io::ErrorKind::NotFound => {},
948 Err(e) => return Err(e),
949 Ok(md) if md.is_dir() => {},
950 Ok(_) => {
951 if let Some(ref found_config) = found_config {
953 warnings.push(format!(
954 "using config file `{}`, `{}` will be ignored",
955 found_config.display(),
956 config_file.display()
957 ));
958 } else {
959 found_config = Some(config_file);
960 }
961 },
962 }
963 }
964 }
965
966 if found_config.is_some() {
967 return Ok((found_config, warnings));
968 }
969
970 if !current.pop() {
972 return Ok((None, warnings));
973 }
974 }
975}
976
977fn deserialize(file: &SourceFile) -> TryConf {
978 match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(file)) {
979 Ok(mut conf) => {
980 extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
981 extend_vec_if_indicator_present(&mut conf.conf.allowed_prefixes, DEFAULT_ALLOWED_PREFIXES);
982 extend_vec_if_indicator_present(
983 &mut conf.conf.allow_renamed_params_for,
984 DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS,
985 );
986
987 if let SourceItemOrderingWithinModuleItemGroupings::Custom(groupings) =
990 &conf.conf.module_items_ordered_within_groupings
991 {
992 for grouping in groupings {
993 if !conf.conf.module_item_order_groupings.is_grouping(grouping) {
994 let names = conf.conf.module_item_order_groupings.grouping_names();
998 let suggestion = suggest_candidate(grouping, names.iter().map(String::as_str))
999 .map(|s| format!(" perhaps you meant `{s}`?"))
1000 .unwrap_or_default();
1001 let names = names.iter().map(|s| format!("`{s}`")).join(", ");
1002 let message = format!(
1003 "unknown ordering group: `{grouping}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}"
1004 );
1005
1006 let span = conf
1007 .value_spans
1008 .get("module_item_order_groupings")
1009 .cloned()
1010 .unwrap_or_default();
1011 conf.errors.push(ConfError::spanned(file, message, None, span));
1012 }
1013 }
1014 }
1015
1016 if conf.conf.allowed_idents_below_min_chars.iter().any(|e| e == "..") {
1018 conf.conf
1019 .allowed_idents_below_min_chars
1020 .extend(DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string));
1021 }
1022 if conf.conf.doc_valid_idents.iter().any(|e| e == "..") {
1023 conf.conf
1024 .doc_valid_idents
1025 .extend(DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string));
1026 }
1027
1028 conf
1029 },
1030 Err(e) => TryConf::from_toml_error(file, &e),
1031 }
1032}
1033
1034fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
1035 if vec.contains(&"..".to_string()) {
1036 vec.extend(default.iter().map(ToString::to_string));
1037 }
1038}
1039
1040impl Conf {
1041 pub fn read(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> &'static Conf {
1042 static CONF: OnceLock<Conf> = OnceLock::new();
1043 CONF.get_or_init(|| Conf::read_inner(sess, path))
1044 }
1045
1046 fn read_inner(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
1047 match path {
1048 Ok((_, warnings)) => {
1049 for warning in warnings {
1050 sess.dcx().warn(warning.clone());
1051 }
1052 },
1053 Err(error) => {
1054 sess.dcx()
1055 .err(format!("error finding Clippy's configuration file: {error}"));
1056 },
1057 }
1058
1059 let TryConf {
1060 mut conf,
1061 value_spans: _,
1062 errors,
1063 warnings,
1064 } = match path {
1065 Ok((Some(path), _)) => match sess.source_map().load_file(path) {
1066 Ok(file) => deserialize(&file),
1067 Err(error) => {
1068 sess.dcx().err(format!("failed to read `{}`: {error}", path.display()));
1069 TryConf::default()
1070 },
1071 },
1072 _ => TryConf::default(),
1073 };
1074
1075 conf.msrv.read_cargo(sess);
1076
1077 for error in errors {
1079 let mut diag = sess.dcx().struct_span_err(
1080 error.span,
1081 format!("error reading Clippy's configuration file: {}", error.message),
1082 );
1083
1084 if let Some(sugg) = error.suggestion {
1085 diag.span_suggestion(error.span, sugg.message, sugg.suggestion, Applicability::MaybeIncorrect);
1086 }
1087
1088 diag.emit();
1089 }
1090
1091 for warning in warnings {
1092 sess.dcx().span_warn(
1093 warning.span,
1094 format!("error reading Clippy's configuration file: {}", warning.message),
1095 );
1096 }
1097
1098 conf
1099 }
1100}
1101
1102const SEPARATOR_WIDTH: usize = 4;
1103
1104#[derive(Debug)]
1105struct FieldError {
1106 error: String,
1107 suggestion: Option<Suggestion>,
1108}
1109
1110#[derive(Debug)]
1111struct Suggestion {
1112 message: &'static str,
1113 suggestion: &'static str,
1114}
1115
1116impl std::error::Error for FieldError {}
1117
1118impl Display for FieldError {
1119 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1120 f.pad(&self.error)
1121 }
1122}
1123
1124impl serde::de::Error for FieldError {
1125 fn custom<T: Display>(msg: T) -> Self {
1126 Self {
1127 error: msg.to_string(),
1128 suggestion: None,
1129 }
1130 }
1131
1132 fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
1133 use fmt::Write;
1136
1137 let metadata = get_configuration_metadata();
1138 let deprecated = metadata
1139 .iter()
1140 .filter_map(|conf| {
1141 if conf.deprecation_reason.is_some() {
1142 Some(conf.name.as_str())
1143 } else {
1144 None
1145 }
1146 })
1147 .collect::<Vec<_>>();
1148
1149 let mut expected = expected
1150 .iter()
1151 .copied()
1152 .filter(|name| !deprecated.contains(name))
1153 .collect::<Vec<_>>();
1154 expected.sort_unstable();
1155
1156 let (rows, column_widths) = calculate_dimensions(&expected);
1157
1158 let mut msg = format!("unknown field `{field}`, expected one of");
1159 for row in 0..rows {
1160 writeln!(msg).unwrap();
1161 for (column, column_width) in column_widths.iter().copied().enumerate() {
1162 let index = column * rows + row;
1163 let field = expected.get(index).copied().unwrap_or_default();
1164 write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap();
1165 }
1166 }
1167
1168 let suggestion = suggest_candidate(field, expected).map(|suggestion| Suggestion {
1169 message: "perhaps you meant",
1170 suggestion,
1171 });
1172
1173 Self { error: msg, suggestion }
1174 }
1175}
1176
1177fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
1178 let columns = env::var("CLIPPY_TERMINAL_WIDTH")
1179 .ok()
1180 .and_then(|s| <usize as FromStr>::from_str(&s).ok())
1181 .map_or(1, |terminal_width| {
1182 let max_field_width = fields.iter().map(|field| field.len()).max().unwrap();
1183 cmp::max(1, terminal_width / (SEPARATOR_WIDTH + max_field_width))
1184 });
1185
1186 let rows = fields.len().div_ceil(columns);
1187
1188 let column_widths = (0..columns)
1189 .map(|column| {
1190 if column < columns - 1 {
1191 (0..rows)
1192 .map(|row| {
1193 let index = column * rows + row;
1194 let field = fields.get(index).copied().unwrap_or_default();
1195 field.len()
1196 })
1197 .max()
1198 .unwrap()
1199 } else {
1200 0
1202 }
1203 })
1204 .collect::<Vec<_>>();
1205
1206 (rows, column_widths)
1207}
1208
1209fn suggest_candidate<'a, I>(value: &str, candidates: I) -> Option<&'a str>
1212where
1213 I: IntoIterator<Item = &'a str>,
1214{
1215 candidates
1216 .into_iter()
1217 .filter_map(|expected| {
1218 let dist = edit_distance(value, expected, 4)?;
1219 Some((dist, expected))
1220 })
1221 .min_by_key(|&(dist, _)| dist)
1222 .map(|(_, suggestion)| suggestion)
1223}
1224
1225#[cfg(test)]
1226mod tests {
1227 use serde::de::IgnoredAny;
1228 use std::collections::{HashMap, HashSet};
1229 use std::fs;
1230 use walkdir::WalkDir;
1231
1232 #[test]
1233 fn configs_are_tested() {
1234 let mut names: HashSet<String> = crate::get_configuration_metadata()
1235 .into_iter()
1236 .filter_map(|meta| {
1237 if meta.deprecation_reason.is_none() {
1238 Some(meta.name.replace('_', "-"))
1239 } else {
1240 None
1241 }
1242 })
1243 .collect();
1244
1245 let toml_files = WalkDir::new("../tests")
1246 .into_iter()
1247 .map(Result::unwrap)
1248 .filter(|entry| entry.file_name() == "clippy.toml");
1249
1250 for entry in toml_files {
1251 let file = fs::read_to_string(entry.path()).unwrap();
1252 #[expect(clippy::zero_sized_map_values)]
1253 if let Ok(map) = toml::from_str::<HashMap<String, IgnoredAny>>(&file) {
1254 for name in map.keys() {
1255 names.remove(name.as_str());
1256 }
1257 }
1258 }
1259
1260 assert!(
1261 names.is_empty(),
1262 "Configuration variable lacks test: {names:?}\nAdd a test to `tests/ui-toml`"
1263 );
1264 }
1265}