1use crate::ClippyConfiguration;
2use crate::types::{
3 DisallowedPath, DisallowedPathWithoutReplacement, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour,
4 Rename, SourceItemOrdering, SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings,
5 SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
6 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 "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript",
37 "WebAssembly",
38 "NaN", "NaNs",
39 "OAuth", "GraphQL",
40 "OCaml",
41 "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry",
42 "OpenType",
43 "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport",
44 "WebP", "OpenExr", "YCbCr", "sRGB",
45 "TensorFlow",
46 "TrueType",
47 "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD",
48 "TeX", "LaTeX", "BibTeX", "BibLaTeX",
49 "MinGW",
50 "CamelCase",
51];
52const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
53const DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z", "w", "n"];
54const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"];
55const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] =
56 &["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"];
57const DEFAULT_MODULE_ITEM_ORDERING_GROUPS: &[(&str, &[SourceItemOrderingModuleItemKind])] = {
58 #[allow(clippy::enum_glob_use)] use SourceItemOrderingModuleItemKind::*;
60 &[
61 ("modules", &[ExternCrate, Mod, ForeignMod]),
62 ("use", &[Use]),
63 ("macros", &[Macro]),
64 ("global_asm", &[GlobalAsm]),
65 ("UPPER_SNAKE_CASE", &[Static, Const]),
66 ("PascalCase", &[TyAlias, Enum, Struct, Union, Trait, TraitAlias, Impl]),
67 ("lower_snake_case", &[Fn]),
68 ]
69};
70const DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER: &[SourceItemOrderingTraitAssocItemKind] = {
71 #[allow(clippy::enum_glob_use)] use SourceItemOrderingTraitAssocItemKind::*;
73 &[Const, Type, Fn]
74};
75const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
76 #[allow(clippy::enum_glob_use)] use SourceItemOrderingCategory::*;
78 &[Enum, Impl, Module, Struct, Trait]
79};
80
81#[derive(Default)]
83struct TryConf {
84 conf: Conf,
85 value_spans: HashMap<String, Range<usize>>,
86 errors: Vec<ConfError>,
87 warnings: Vec<ConfError>,
88}
89
90impl TryConf {
91 fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
92 Self {
93 conf: Conf::default(),
94 value_spans: HashMap::default(),
95 errors: vec![ConfError::from_toml(file, error)],
96 warnings: vec![],
97 }
98 }
99}
100
101#[derive(Debug)]
102struct ConfError {
103 message: String,
104 suggestion: Option<Suggestion>,
105 span: Span,
106}
107
108impl ConfError {
109 fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
110 let span = error.span().unwrap_or(0..file.source_len.0 as usize);
111 Self::spanned(file, error.message(), None, span)
112 }
113
114 fn spanned(
115 file: &SourceFile,
116 message: impl Into<String>,
117 suggestion: Option<Suggestion>,
118 span: Range<usize>,
119 ) -> Self {
120 Self {
121 message: message.into(),
122 suggestion,
123 span: span_from_toml_range(file, span),
124 }
125 }
126}
127
128pub fn sanitize_explanation(raw_docs: &str) -> String {
130 let mut explanation = String::with_capacity(128);
132 let mut in_code = false;
133 for line in raw_docs.lines() {
134 let line = line.strip_prefix(' ').unwrap_or(line);
135
136 if let Some(lang) = line.strip_prefix("```") {
137 let tag = lang.split_once(',').map_or(lang, |(left, _)| left);
138 if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") {
139 explanation += "```rust\n";
140 } else {
141 explanation += line;
142 explanation.push('\n');
143 }
144 in_code = !in_code;
145 } else if !(in_code && line.starts_with("# ")) {
146 explanation += line;
147 explanation.push('\n');
148 }
149 }
150
151 explanation
152}
153
154macro_rules! wrap_option {
155 () => {
156 None
157 };
158 ($x:literal) => {
159 Some($x)
160 };
161}
162
163macro_rules! default_text {
164 ($value:expr) => {{
165 let mut text = String::new();
166 $value.serialize(toml::ser::ValueSerializer::new(&mut text)).unwrap();
167 text
168 }};
169 ($value:expr, $override:expr) => {
170 $override.to_string()
171 };
172}
173
174macro_rules! deserialize {
175 ($map:expr, $ty:ty, $errors:expr, $file:expr) => {{
176 let raw_value = $map.next_value::<toml::Spanned<toml::Value>>()?;
177 let value_span = raw_value.span();
178 let value = match <$ty>::deserialize(raw_value.into_inner()) {
179 Err(e) => {
180 $errors.push(ConfError::spanned(
181 $file,
182 e.to_string().replace('\n', " ").trim(),
183 None,
184 value_span,
185 ));
186 continue;
187 },
188 Ok(value) => value,
189 };
190 (value, value_span)
191 }};
192
193 ($map:expr, $ty:ty, $errors:expr, $file:expr, $replacements_allowed:expr) => {{
194 let array = $map.next_value::<Vec<toml::Spanned<toml::Value>>>()?;
195 let mut disallowed_paths_span = Range {
196 start: usize::MAX,
197 end: usize::MIN,
198 };
199 let mut disallowed_paths = Vec::new();
200 for raw_value in array {
201 let value_span = raw_value.span();
202 let mut disallowed_path = match DisallowedPath::<$replacements_allowed>::deserialize(raw_value.into_inner())
203 {
204 Err(e) => {
205 $errors.push(ConfError::spanned(
206 $file,
207 e.to_string().replace('\n', " ").trim(),
208 None,
209 value_span,
210 ));
211 continue;
212 },
213 Ok(disallowed_path) => disallowed_path,
214 };
215 disallowed_paths_span = union(&disallowed_paths_span, &value_span);
216 disallowed_path.set_span(span_from_toml_range($file, value_span));
217 disallowed_paths.push(disallowed_path);
218 }
219 (disallowed_paths, disallowed_paths_span)
220 }};
221}
222
223macro_rules! define_Conf {
224 ($(
225 $(#[doc = $doc:literal])+
226 $(#[conf_deprecated($dep:literal, $new_conf:ident)])?
227 $(#[default_text = $default_text:expr])?
228 $(#[disallowed_paths_allow_replacements = $replacements_allowed:expr])?
229 $(#[lints($($for_lints:ident),* $(,)?)])?
230 $name:ident: $ty:ty = $default:expr,
231 )*) => {
232 pub struct Conf {
234 $($(#[cfg_attr(doc, doc = $doc)])+ pub $name: $ty,)*
235 }
236
237 mod defaults {
238 use super::*;
239 $(pub fn $name() -> $ty { $default })*
240 }
241
242 impl Default for Conf {
243 fn default() -> Self {
244 Self { $($name: defaults::$name(),)* }
245 }
246 }
247
248 #[derive(Deserialize)]
249 #[serde(field_identifier, rename_all = "kebab-case")]
250 #[allow(non_camel_case_types)]
251 enum Field { $($name,)* third_party, }
252
253 struct ConfVisitor<'a>(&'a SourceFile);
254
255 impl<'de> Visitor<'de> for ConfVisitor<'_> {
256 type Value = TryConf;
257
258 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
259 formatter.write_str("Conf")
260 }
261
262 fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
263 let mut value_spans = HashMap::new();
264 let mut errors = Vec::new();
265 let mut warnings = Vec::new();
266
267 $(let mut $name = None;)*
269
270 while let Some(name) = map.next_key::<toml::Spanned<String>>()? {
272 let field = match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
273 Err(e) => {
274 let e: FieldError = e;
275 errors.push(ConfError::spanned(self.0, e.error, e.suggestion, name.span()));
276 continue;
277 }
278 Ok(field) => field
279 };
280
281 match field {
282 $(Field::$name => {
283 $(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
285 let (value, value_span) =
286 deserialize!(map, $ty, errors, self.0 $(, $replacements_allowed)?);
287 if $name.is_some() {
289 errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
290 continue;
291 }
292 $name = Some(value);
293 value_spans.insert(name.get_ref().as_str().to_string(), value_span);
294 $(match $new_conf {
297 Some(_) => errors.push(ConfError::spanned(self.0, concat!(
298 "duplicate field `", stringify!($new_conf),
299 "` (provided as `", stringify!($name), "`)"
300 ), None, name.span())),
301 None => $new_conf = $name.clone(),
302 })?
303 })*
304 Field::third_party => drop(map.next_value::<IgnoredAny>())
306 }
307 }
308 let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
309 Ok(TryConf { conf, value_spans, errors, warnings })
310 }
311 }
312
313 pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
314 vec![$(
315 ClippyConfiguration {
316 name: stringify!($name).replace('_', "-"),
317 default: default_text!(defaults::$name() $(, $default_text)?),
318 lints: &[$($(stringify!($for_lints)),*)?],
319 doc: concat!($($doc, '\n',)*),
320 deprecation_reason: wrap_option!($($dep)?)
321 },
322 )*]
323 }
324 };
325}
326
327fn union(x: &Range<usize>, y: &Range<usize>) -> Range<usize> {
328 Range {
329 start: cmp::min(x.start, y.start),
330 end: cmp::max(x.end, y.end),
331 }
332}
333
334fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
335 Span::new(
336 file.start_pos + BytePos::from_usize(span.start),
337 file.start_pos + BytePos::from_usize(span.end),
338 SyntaxContext::root(),
339 None,
340 )
341}
342
343define_Conf! {
344 #[lints(absolute_paths)]
346 absolute_paths_allowed_crates: Vec<String> = Vec::new(),
347 #[lints(absolute_paths)]
350 absolute_paths_max_segments: u64 = 2,
351 #[lints(undocumented_unsafe_blocks)]
353 accept_comment_above_attributes: bool = true,
354 #[lints(undocumented_unsafe_blocks)]
356 accept_comment_above_statement: bool = true,
357 #[lints(modulo_arithmetic)]
359 allow_comparison_to_zero: bool = true,
360 #[lints(dbg_macro)]
362 allow_dbg_in_tests: bool = false,
363 #[lints(module_name_repetitions)]
365 allow_exact_repetitions: bool = true,
366 #[lints(expect_used)]
368 allow_expect_in_consts: bool = true,
369 #[lints(expect_used)]
371 allow_expect_in_tests: bool = false,
372 #[lints(indexing_slicing)]
374 allow_indexing_slicing_in_tests: bool = false,
375 #[lints(uninlined_format_args)]
377 allow_mixed_uninlined_format_args: bool = true,
378 #[lints(needless_raw_string_hashes)]
380 allow_one_hash_in_raw_strings: bool = false,
381 #[lints(panic)]
383 allow_panic_in_tests: bool = false,
384 #[lints(print_stderr, print_stdout)]
386 allow_print_in_tests: bool = false,
387 #[lints(module_inception)]
389 allow_private_module_inception: bool = false,
390 #[lints(renamed_function_params)]
404 allow_renamed_params_for: Vec<String> =
405 DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect(),
406 #[lints(unwrap_used)]
408 allow_unwrap_in_consts: bool = true,
409 #[lints(unwrap_used)]
411 allow_unwrap_in_tests: bool = false,
412 #[lints(useless_vec)]
414 allow_useless_vec_in_tests: bool = false,
415 #[lints(path_ends_with_ext)]
417 allowed_dotfiles: Vec<String> = Vec::default(),
418 #[lints(multiple_crate_versions)]
420 allowed_duplicate_crates: Vec<String> = Vec::new(),
421 #[lints(min_ident_chars)]
425 allowed_idents_below_min_chars: Vec<String> =
426 DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect(),
427 #[lints(module_name_repetitions)]
445 allowed_prefixes: Vec<String> = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect(),
446 #[lints(disallowed_script_idents)]
448 allowed_scripts: Vec<String> = vec!["Latin".to_string()],
449 #[lints(wildcard_imports)]
463 allowed_wildcard_imports: Vec<String> = Vec::new(),
464 #[lints(arithmetic_side_effects)]
479 arithmetic_side_effects_allowed: Vec<String> = <_>::default(),
480 #[lints(arithmetic_side_effects)]
495 arithmetic_side_effects_allowed_binary: Vec<(String, String)> = <_>::default(),
496 #[lints(arithmetic_side_effects)]
504 arithmetic_side_effects_allowed_unary: Vec<String> = <_>::default(),
505 #[lints(large_const_arrays, large_stack_arrays)]
507 array_size_threshold: u64 = 16 * 1024,
508 #[lints(
510 box_collection,
511 enum_variant_names,
512 large_types_passed_by_value,
513 linkedlist,
514 needless_pass_by_ref_mut,
515 option_option,
516 owned_cow,
517 rc_buffer,
518 rc_mutex,
519 redundant_allocation,
520 ref_option,
521 single_call_fn,
522 trivially_copy_pass_by_ref,
523 unnecessary_box_returns,
524 unnecessary_wraps,
525 unused_self,
526 upper_case_acronyms,
527 vec_box,
528 wrong_self_convention,
529 )]
530 avoid_breaking_exported_api: bool = true,
531 #[disallowed_paths_allow_replacements = false]
533 #[lints(await_holding_invalid_type)]
534 await_holding_invalid_types: Vec<DisallowedPathWithoutReplacement> = Vec::new(),
535 #[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)]
539 blacklisted_names: Vec<String> = Vec::new(),
540 #[lints(cargo_common_metadata)]
542 cargo_ignore_publish: bool = false,
543 #[lints(incompatible_msrv)]
545 check_incompatible_msrv_in_tests: bool = false,
546 #[lints(inconsistent_struct_constructor)]
565 check_inconsistent_struct_field_initializers: bool = false,
566 #[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
568 check_private_items: bool = false,
569 #[lints(cognitive_complexity)]
571 cognitive_complexity_threshold: u64 = 25,
572 #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
576 cyclomatic_complexity_threshold: u64 = 25,
577 #[disallowed_paths_allow_replacements = true]
586 #[lints(disallowed_macros)]
587 disallowed_macros: Vec<DisallowedPath> = Vec::new(),
588 #[disallowed_paths_allow_replacements = true]
597 #[lints(disallowed_methods)]
598 disallowed_methods: Vec<DisallowedPath> = Vec::new(),
599 #[lints(disallowed_names)]
603 disallowed_names: Vec<String> = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(),
604 #[disallowed_paths_allow_replacements = true]
613 #[lints(disallowed_types)]
614 disallowed_types: Vec<DisallowedPath> = Vec::new(),
615 #[lints(doc_markdown)]
621 doc_valid_idents: Vec<String> = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect(),
622 #[lints(non_send_fields_in_send_ty)]
624 enable_raw_pointer_heuristic_for_send: bool = true,
625 #[lints(explicit_iter_loop)]
643 enforce_iter_loop_reborrow: bool = false,
644 #[lints(missing_enforced_import_renames)]
646 enforced_import_renames: Vec<Rename> = Vec::new(),
647 #[lints(enum_variant_names)]
649 enum_variant_name_threshold: u64 = 3,
650 #[lints(large_enum_variant)]
652 enum_variant_size_threshold: u64 = 200,
653 #[lints(excessive_nesting)]
655 excessive_nesting_threshold: u64 = 0,
656 #[lints(large_futures)]
658 future_size_threshold: u64 = 16 * 1024,
659 #[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)]
661 ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()]),
662 #[lints(result_large_err)]
664 large_error_threshold: u64 = 128,
665 #[lints(collapsible_else_if, collapsible_if)]
668 lint_commented_code: bool = false,
669 #[conf_deprecated("Please use `check-inconsistent-struct-field-initializers` instead", check_inconsistent_struct_field_initializers)]
674 lint_inconsistent_struct_field_initializers: bool = false,
675 #[lints(decimal_literal_representation)]
677 literal_representation_threshold: u64 = 16384,
678 #[lints(manual_let_else)]
681 matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes,
682 #[lints(fn_params_excessive_bools)]
684 max_fn_params_bools: u64 = 3,
685 #[lints(large_include_file)]
687 max_include_file_size: u64 = 1_000_000,
688 #[lints(struct_excessive_bools)]
690 max_struct_bools: u64 = 3,
691 #[lints(index_refutable_slice)]
695 max_suggested_slice_pattern_length: u64 = 3,
696 #[lints(type_repetition_in_bounds)]
698 max_trait_bounds: u64 = 3,
699 #[lints(min_ident_chars)]
701 min_ident_chars_threshold: u64 = 1,
702 #[lints(missing_docs_in_private_items)]
704 missing_docs_allow_unused: bool = false,
705 #[lints(missing_docs_in_private_items)]
708 missing_docs_in_crate_items: bool = false,
709 #[lints(arbitrary_source_item_ordering)]
711 module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(),
712 #[lints(arbitrary_source_item_ordering)]
717 module_items_ordered_within_groupings: SourceItemOrderingWithinModuleItemGroupings =
718 SourceItemOrderingWithinModuleItemGroupings::None,
719 #[default_text = "current version"]
721 #[lints(
722 allow_attributes,
723 allow_attributes_without_reason,
724 almost_complete_range,
725 approx_constant,
726 assigning_clones,
727 borrow_as_ptr,
728 cast_abs_to_unsigned,
729 checked_conversions,
730 cloned_instead_of_copied,
731 collapsible_match,
732 collapsible_str_replace,
733 deprecated_cfg_attr,
734 derivable_impls,
735 err_expect,
736 filter_map_next,
737 from_over_into,
738 if_then_some_else_none,
739 index_refutable_slice,
740 io_other_error,
741 iter_kv_map,
742 legacy_numeric_constants,
743 lines_filter_map_ok,
744 manual_abs_diff,
745 manual_bits,
746 manual_c_str_literals,
747 manual_clamp,
748 manual_div_ceil,
749 manual_flatten,
750 manual_hash_one,
751 manual_is_ascii_check,
752 manual_is_power_of_two,
753 manual_let_else,
754 manual_midpoint,
755 manual_non_exhaustive,
756 manual_option_as_slice,
757 manual_pattern_char_comparison,
758 manual_range_contains,
759 manual_rem_euclid,
760 manual_repeat_n,
761 manual_retain,
762 manual_slice_fill,
763 manual_slice_size_calculation,
764 manual_split_once,
765 manual_str_repeat,
766 manual_strip,
767 manual_try_fold,
768 map_clone,
769 map_unwrap_or,
770 map_with_unused_argument_over_ranges,
771 match_like_matches_macro,
772 mem_replace_option_with_some,
773 mem_replace_with_default,
774 missing_const_for_fn,
775 needless_borrow,
776 non_std_lazy_statics,
777 option_as_ref_deref,
778 option_map_unwrap_or,
779 ptr_as_ptr,
780 question_mark,
781 redundant_field_names,
782 redundant_static_lifetimes,
783 repeat_vec_with_capacity,
784 same_item_push,
785 seek_from_current,
786 seek_rewind,
787 to_digit_is_some,
788 transmute_ptr_to_ref,
789 tuple_array_conversions,
790 type_repetition_in_bounds,
791 unchecked_duration_subtraction,
792 uninlined_format_args,
793 unnecessary_lazy_evaluations,
794 unnested_or_patterns,
795 unused_trait_names,
796 use_self,
797 )]
798 msrv: Msrv = Msrv::default(),
799 #[lints(large_types_passed_by_value)]
801 pass_by_value_size_limit: u64 = 256,
802 #[lints(pub_underscore_fields)]
805 pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported,
806 #[lints(semicolon_inside_block)]
808 semicolon_inside_block_ignore_singleline: bool = false,
809 #[lints(semicolon_outside_block)]
811 semicolon_outside_block_ignore_multiline: bool = false,
812 #[lints(many_single_char_names)]
814 single_char_binding_names_threshold: u64 = 4,
815 #[lints(arbitrary_source_item_ordering)]
817 source_item_ordering: SourceItemOrdering = DEFAULT_SOURCE_ITEM_ORDERING.into(),
818 #[lints(large_stack_frames)]
820 stack_size_threshold: u64 = 512_000,
821 #[lints(nonstandard_macro_braces)]
827 standard_macro_braces: Vec<MacroMatcher> = Vec::new(),
828 #[lints(struct_field_names)]
830 struct_field_name_threshold: u64 = 3,
831 #[lints(indexing_slicing)]
837 suppress_restriction_lint_in_const: bool = false,
838 #[lints(boxed_local, useless_vec)]
840 too_large_for_stack: u64 = 200,
841 #[lints(too_many_arguments)]
843 too_many_arguments_threshold: u64 = 7,
844 #[lints(too_many_lines)]
846 too_many_lines_threshold: u64 = 100,
847 #[lints(arbitrary_source_item_ordering)]
849 trait_assoc_item_kinds_order: SourceItemOrderingTraitAssocItemKinds = DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER.into(),
850 #[default_text = "target_pointer_width"]
853 #[lints(trivially_copy_pass_by_ref)]
854 trivial_copy_size_limit: Option<u64> = None,
855 #[lints(type_complexity)]
857 type_complexity_threshold: u64 = 250,
858 #[lints(unnecessary_box_returns)]
860 unnecessary_box_size: u64 = 128,
861 #[lints(unreadable_literal)]
863 unreadable_literal_lint_fractions: bool = true,
864 #[lints(upper_case_acronyms)]
866 upper_case_acronyms_aggressive: bool = false,
867 #[lints(vec_box)]
869 vec_box_size_threshold: u64 = 4096,
870 #[lints(verbose_bit_mask)]
872 verbose_bit_mask_threshold: u64 = 1,
873 #[lints(wildcard_imports)]
876 warn_on_all_wildcard_imports: bool = false,
877 #[lints(macro_metavars_in_unsafe)]
879 warn_unsafe_macro_metavars_in_private_macros: bool = false,
880}
881
882pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
888 const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
890
891 let mut current = env::var_os("CLIPPY_CONF_DIR")
894 .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
895 .map_or_else(|| PathBuf::from("."), PathBuf::from)
896 .canonicalize()?;
897
898 let mut found_config: Option<PathBuf> = None;
899 let mut warnings = vec![];
900
901 loop {
902 for config_file_name in &CONFIG_FILE_NAMES {
903 if let Ok(config_file) = current.join(config_file_name).canonicalize() {
904 match fs::metadata(&config_file) {
905 Err(e) if e.kind() == io::ErrorKind::NotFound => {},
906 Err(e) => return Err(e),
907 Ok(md) if md.is_dir() => {},
908 Ok(_) => {
909 if let Some(ref found_config) = found_config {
911 warnings.push(format!(
912 "using config file `{}`, `{}` will be ignored",
913 found_config.display(),
914 config_file.display()
915 ));
916 } else {
917 found_config = Some(config_file);
918 }
919 },
920 }
921 }
922 }
923
924 if found_config.is_some() {
925 return Ok((found_config, warnings));
926 }
927
928 if !current.pop() {
930 return Ok((None, warnings));
931 }
932 }
933}
934
935fn deserialize(file: &SourceFile) -> TryConf {
936 match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(file)) {
937 Ok(mut conf) => {
938 extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
939 extend_vec_if_indicator_present(&mut conf.conf.allowed_prefixes, DEFAULT_ALLOWED_PREFIXES);
940 extend_vec_if_indicator_present(
941 &mut conf.conf.allow_renamed_params_for,
942 DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS,
943 );
944
945 if let SourceItemOrderingWithinModuleItemGroupings::Custom(groupings) =
948 &conf.conf.module_items_ordered_within_groupings
949 {
950 for grouping in groupings {
951 if !conf.conf.module_item_order_groupings.is_grouping(grouping) {
952 let names = conf.conf.module_item_order_groupings.grouping_names();
956 let suggestion = suggest_candidate(grouping, names.iter().map(String::as_str))
957 .map(|s| format!(" perhaps you meant `{s}`?"))
958 .unwrap_or_default();
959 let names = names.iter().map(|s| format!("`{s}`")).join(", ");
960 let message = format!(
961 "unknown ordering group: `{grouping}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}"
962 );
963
964 let span = conf
965 .value_spans
966 .get("module_item_order_groupings")
967 .cloned()
968 .unwrap_or_default();
969 conf.errors.push(ConfError::spanned(file, message, None, span));
970 }
971 }
972 }
973
974 if conf.conf.allowed_idents_below_min_chars.iter().any(|e| e == "..") {
976 conf.conf
977 .allowed_idents_below_min_chars
978 .extend(DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string));
979 }
980 if conf.conf.doc_valid_idents.iter().any(|e| e == "..") {
981 conf.conf
982 .doc_valid_idents
983 .extend(DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string));
984 }
985
986 conf
987 },
988 Err(e) => TryConf::from_toml_error(file, &e),
989 }
990}
991
992fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
993 if vec.contains(&"..".to_string()) {
994 vec.extend(default.iter().map(ToString::to_string));
995 }
996}
997
998impl Conf {
999 pub fn read(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> &'static Conf {
1000 static CONF: OnceLock<Conf> = OnceLock::new();
1001 CONF.get_or_init(|| Conf::read_inner(sess, path))
1002 }
1003
1004 fn read_inner(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
1005 match path {
1006 Ok((_, warnings)) => {
1007 for warning in warnings {
1008 sess.dcx().warn(warning.clone());
1009 }
1010 },
1011 Err(error) => {
1012 sess.dcx()
1013 .err(format!("error finding Clippy's configuration file: {error}"));
1014 },
1015 }
1016
1017 let TryConf {
1018 mut conf,
1019 value_spans: _,
1020 errors,
1021 warnings,
1022 } = match path {
1023 Ok((Some(path), _)) => match sess.source_map().load_file(path) {
1024 Ok(file) => deserialize(&file),
1025 Err(error) => {
1026 sess.dcx().err(format!("failed to read `{}`: {error}", path.display()));
1027 TryConf::default()
1028 },
1029 },
1030 _ => TryConf::default(),
1031 };
1032
1033 conf.msrv.read_cargo(sess);
1034
1035 for error in errors {
1037 let mut diag = sess.dcx().struct_span_err(
1038 error.span,
1039 format!("error reading Clippy's configuration file: {}", error.message),
1040 );
1041
1042 if let Some(sugg) = error.suggestion {
1043 diag.span_suggestion(error.span, sugg.message, sugg.suggestion, Applicability::MaybeIncorrect);
1044 }
1045
1046 diag.emit();
1047 }
1048
1049 for warning in warnings {
1050 sess.dcx().span_warn(
1051 warning.span,
1052 format!("error reading Clippy's configuration file: {}", warning.message),
1053 );
1054 }
1055
1056 conf
1057 }
1058}
1059
1060const SEPARATOR_WIDTH: usize = 4;
1061
1062#[derive(Debug)]
1063struct FieldError {
1064 error: String,
1065 suggestion: Option<Suggestion>,
1066}
1067
1068#[derive(Debug)]
1069struct Suggestion {
1070 message: &'static str,
1071 suggestion: &'static str,
1072}
1073
1074impl std::error::Error for FieldError {}
1075
1076impl Display for FieldError {
1077 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1078 f.pad(&self.error)
1079 }
1080}
1081
1082impl serde::de::Error for FieldError {
1083 fn custom<T: Display>(msg: T) -> Self {
1084 Self {
1085 error: msg.to_string(),
1086 suggestion: None,
1087 }
1088 }
1089
1090 fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
1091 use fmt::Write;
1094
1095 let metadata = get_configuration_metadata();
1096 let deprecated = metadata
1097 .iter()
1098 .filter_map(|conf| {
1099 if conf.deprecation_reason.is_some() {
1100 Some(conf.name.as_str())
1101 } else {
1102 None
1103 }
1104 })
1105 .collect::<Vec<_>>();
1106
1107 let mut expected = expected
1108 .iter()
1109 .copied()
1110 .filter(|name| !deprecated.contains(name))
1111 .collect::<Vec<_>>();
1112 expected.sort_unstable();
1113
1114 let (rows, column_widths) = calculate_dimensions(&expected);
1115
1116 let mut msg = format!("unknown field `{field}`, expected one of");
1117 for row in 0..rows {
1118 writeln!(msg).unwrap();
1119 for (column, column_width) in column_widths.iter().copied().enumerate() {
1120 let index = column * rows + row;
1121 let field = expected.get(index).copied().unwrap_or_default();
1122 write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap();
1123 }
1124 }
1125
1126 let suggestion = suggest_candidate(field, expected).map(|suggestion| Suggestion {
1127 message: "perhaps you meant",
1128 suggestion,
1129 });
1130
1131 Self { error: msg, suggestion }
1132 }
1133}
1134
1135fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
1136 let columns = env::var("CLIPPY_TERMINAL_WIDTH")
1137 .ok()
1138 .and_then(|s| <usize as FromStr>::from_str(&s).ok())
1139 .map_or(1, |terminal_width| {
1140 let max_field_width = fields.iter().map(|field| field.len()).max().unwrap();
1141 cmp::max(1, terminal_width / (SEPARATOR_WIDTH + max_field_width))
1142 });
1143
1144 let rows = fields.len().div_ceil(columns);
1145
1146 let column_widths = (0..columns)
1147 .map(|column| {
1148 if column < columns - 1 {
1149 (0..rows)
1150 .map(|row| {
1151 let index = column * rows + row;
1152 let field = fields.get(index).copied().unwrap_or_default();
1153 field.len()
1154 })
1155 .max()
1156 .unwrap()
1157 } else {
1158 0
1160 }
1161 })
1162 .collect::<Vec<_>>();
1163
1164 (rows, column_widths)
1165}
1166
1167fn suggest_candidate<'a, I>(value: &str, candidates: I) -> Option<&'a str>
1170where
1171 I: IntoIterator<Item = &'a str>,
1172{
1173 candidates
1174 .into_iter()
1175 .filter_map(|expected| {
1176 let dist = edit_distance(value, expected, 4)?;
1177 Some((dist, expected))
1178 })
1179 .min_by_key(|&(dist, _)| dist)
1180 .map(|(_, suggestion)| suggestion)
1181}
1182
1183#[cfg(test)]
1184mod tests {
1185 use serde::de::IgnoredAny;
1186 use std::collections::{HashMap, HashSet};
1187 use std::fs;
1188 use walkdir::WalkDir;
1189
1190 #[test]
1191 fn configs_are_tested() {
1192 let mut names: HashSet<String> = crate::get_configuration_metadata()
1193 .into_iter()
1194 .filter_map(|meta| {
1195 if meta.deprecation_reason.is_none() {
1196 Some(meta.name.replace('_', "-"))
1197 } else {
1198 None
1199 }
1200 })
1201 .collect();
1202
1203 let toml_files = WalkDir::new("../tests")
1204 .into_iter()
1205 .map(Result::unwrap)
1206 .filter(|entry| entry.file_name() == "clippy.toml");
1207
1208 for entry in toml_files {
1209 let file = fs::read_to_string(entry.path()).unwrap();
1210 #[allow(clippy::zero_sized_map_values)]
1211 if let Ok(map) = toml::from_str::<HashMap<String, IgnoredAny>>(&file) {
1212 for name in map.keys() {
1213 names.remove(name.as_str());
1214 }
1215 }
1216 }
1217
1218 assert!(
1219 names.is_empty(),
1220 "Configuration variable lacks test: {names:?}\nAdd a test to `tests/ui-toml`"
1221 );
1222 }
1223}