1use crate::ClippyConfiguration;
2use crate::types::{
3 DisallowedPath, DisallowedPathWithoutReplacement, DisallowedProfile, InherentImplLintScope, MacroMatcher,
4 MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering, SourceItemOrderingCategory,
5 SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind,
6 SourceItemOrderingTraitAssocItemKinds, SourceItemOrderingWithinModuleItemGroupings,
7};
8use clippy_utils::msrvs::Msrv;
9use itertools::Itertools;
10use rustc_data_structures::fx::FxHashMap;
11use rustc_errors::Applicability;
12use rustc_session::Session;
13use rustc_span::edit_distance::edit_distance;
14use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
15use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
16use serde::{Deserialize, Deserializer, Serialize};
17use std::collections::HashMap;
18use std::fmt::{Debug, Display, Formatter};
19use std::ops::Range;
20use std::path::PathBuf;
21use std::str::FromStr;
22use std::sync::OnceLock;
23use std::{cmp, env, fmt, fs, io};
24
25#[rustfmt::skip]
26const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
27 "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
28 "MHz", "GHz", "THz",
29 "AccessKit",
30 "CoAP", "CoreFoundation", "CoreGraphics", "CoreText",
31 "DevOps",
32 "Direct2D", "Direct3D", "DirectWrite", "DirectX",
33 "ECMAScript",
34 "GPLv2", "GPLv3",
35 "GitHub", "GitLab",
36 "IPv4", "IPv6",
37 "InfiniBand", "RoCE",
38 "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript",
39 "PowerPC", "PowerShell", "WebAssembly",
40 "NaN", "NaNs",
41 "OAuth", "GraphQL",
42 "SQLite", "MySQL", "PostgreSQL", "MariaDB", "MongoDB",
43 "OCaml",
44 "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry",
45 "OpenType",
46 "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport",
47 "WebP", "OpenExr", "YCbCr", "sRGB",
48 "TensorFlow",
49 "TrueType",
50 "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS",
51 "TeX", "LaTeX", "BibTeX", "BibLaTeX",
52 "MinGW",
53 "CamelCase",
54];
55const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
56const DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z", "w", "n"];
57const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"];
58const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] =
59 &["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"];
60const DEFAULT_MODULE_ITEM_ORDERING_GROUPS: &[(&str, &[SourceItemOrderingModuleItemKind])] = {
61 #[allow(clippy::enum_glob_use)] use SourceItemOrderingModuleItemKind::*;
63 &[
64 ("modules", &[ExternCrate, Mod, ForeignMod]),
65 ("use", &[Use]),
66 ("macros", &[Macro]),
67 ("global_asm", &[GlobalAsm]),
68 ("UPPER_SNAKE_CASE", &[Static, Const]),
69 ("PascalCase", &[TyAlias, Enum, Struct, Union, Trait, TraitAlias, Impl]),
70 ("lower_snake_case", &[Fn]),
71 ]
72};
73const DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER: &[SourceItemOrderingTraitAssocItemKind] = {
74 #[allow(clippy::enum_glob_use)] use SourceItemOrderingTraitAssocItemKind::*;
76 &[Const, Type, Fn]
77};
78const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
79 #[allow(clippy::enum_glob_use)] use SourceItemOrderingCategory::*;
81 &[Enum, Impl, Module, Struct, Trait]
82};
83
84#[derive(Default)]
86struct TryConf {
87 conf: Conf,
88 value_spans: HashMap<String, Range<usize>>,
89 errors: Vec<ConfError>,
90 warnings: Vec<ConfError>,
91}
92
93impl TryConf {
94 fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
95 Self {
96 conf: Conf::default(),
97 value_spans: HashMap::default(),
98 errors: vec![ConfError::from_toml(file, error)],
99 warnings: vec![],
100 }
101 }
102}
103
104#[derive(Debug)]
105struct ConfError {
106 message: String,
107 suggestion: Option<Suggestion>,
108 span: Span,
109}
110
111impl ConfError {
112 fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
113 let span = error.span().unwrap_or(0..file.normalized_source_len.0 as usize);
114 Self::spanned(file, error.message(), None, span)
115 }
116
117 fn spanned(
118 file: &SourceFile,
119 message: impl Into<String>,
120 suggestion: Option<Suggestion>,
121 span: Range<usize>,
122 ) -> Self {
123 Self {
124 message: message.into(),
125 suggestion,
126 span: span_from_toml_range(file, span),
127 }
128 }
129}
130
131pub fn sanitize_explanation(raw_docs: &str) -> String {
133 let mut explanation = String::with_capacity(128);
135 let mut in_code = false;
136 for line in raw_docs.lines() {
137 let line = line.strip_prefix(' ').unwrap_or(line);
138
139 if let Some(lang) = line.strip_prefix("```") {
140 let tag = lang.split_once(',').map_or(lang, |(left, _)| left);
141 if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") {
142 explanation += "```rust\n";
143 } else {
144 explanation += line;
145 explanation.push('\n');
146 }
147 in_code = !in_code;
148 } else if !(in_code && line.starts_with("# ")) {
149 explanation += line;
150 explanation.push('\n');
151 }
152 }
153
154 explanation
155}
156
157macro_rules! wrap_option {
158 () => {
159 None
160 };
161 ($x:literal) => {
162 Some($x)
163 };
164}
165
166macro_rules! default_text {
167 ($value:expr) => {{
168 let mut text = String::new();
169 $value.serialize(toml::ser::ValueSerializer::new(&mut text)).unwrap();
170 text
171 }};
172 ($value:expr, $override:expr) => {
173 $override.to_string()
174 };
175}
176
177macro_rules! deserialize {
178 ($map:expr, $ty:ty, $errors:expr, $file:expr) => {{
179 let raw_value = $map.next_value::<toml::Spanned<toml::Value>>()?;
180 let value_span = raw_value.span();
181 let value = match <$ty>::deserialize(raw_value.into_inner()) {
182 Err(e) => {
183 $errors.push(ConfError::spanned(
184 $file,
185 e.to_string().replace('\n', " ").trim(),
186 None,
187 value_span,
188 ));
189 continue;
190 },
191 Ok(value) => value,
192 };
193 (value, value_span)
194 }};
195
196 ($map:expr, $ty:ty, $errors:expr, $file:expr, $replacements_allowed:expr) => {{
197 let array = $map.next_value::<Vec<toml::Spanned<toml::Value>>>()?;
198 let mut disallowed_paths_span = Range {
199 start: usize::MAX,
200 end: usize::MIN,
201 };
202 let mut disallowed_paths = Vec::new();
203 for raw_value in array {
204 let value_span = raw_value.span();
205 let mut disallowed_path = match DisallowedPath::<$replacements_allowed>::deserialize(raw_value.into_inner())
206 {
207 Err(e) => {
208 $errors.push(ConfError::spanned(
209 $file,
210 e.to_string().replace('\n', " ").trim(),
211 None,
212 value_span,
213 ));
214 continue;
215 },
216 Ok(disallowed_path) => disallowed_path,
217 };
218 disallowed_paths_span = union(&disallowed_paths_span, &value_span);
219 disallowed_path.set_span(span_from_toml_range($file, value_span));
220 disallowed_paths.push(disallowed_path);
221 }
222 (disallowed_paths, disallowed_paths_span)
223 }};
224}
225
226macro_rules! parse_conf_value {
227 (
228 $map:expr,
229 $ty:ty,
230 $errors:expr,
231 $file:expr,
232 $field_span:expr,
233 profiles @[$($profiles:expr)?],
234 disallowed @[$($disallowed:expr)?]
235 ) => {
236 parse_conf_value_impl!(
237 $map,
238 $ty,
239 $errors,
240 $file,
241 $field_span,
242 ($($profiles)?),
243 ($($disallowed)?)
244 )
245 };
246}
247
248macro_rules! parse_conf_value_impl {
249 ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ()) => {{
250 let _ = &$field_span;
251 deserialize!($map, $ty, $errors, $file)
252 }};
253 ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, ($profiles:expr), ()) => {{
254 let raw_value = $map.next_value::<toml::Value>()?;
255 let value_span = $field_span.clone();
256 let toml::Value::Table(table) = raw_value else {
257 $errors.push(ConfError::spanned(
258 $file,
259 "expected table with named profiles",
260 None,
261 value_span.clone(),
262 ));
263 continue;
264 };
265
266 let map = parse_profiles(table, $file, value_span.clone(), &mut $errors);
267
268 (map, value_span)
269 }};
270 ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ($disallowed:expr)) => {{
271 let _ = &$field_span;
272 deserialize!($map, $ty, $errors, $file, $disallowed)
273 }};
274 (
275 $map:expr,
276 $ty:ty,
277 $errors:expr,
278 $file:expr,
279 $field_span:expr,
280 ($profiles:expr),
281 ($disallowed:expr)
282 ) => {
283 compile_error!("field cannot specify both profiles and disallowed-paths attributes")
284 };
285}
286
287macro_rules! define_Conf {
288 ($(
289 $(#[doc = $doc:literal])+
290 $(#[conf_deprecated($dep:literal, $new_conf:ident)])?
291 $(#[default_text = $default_text:expr])?
292 $(#[disallowed_paths_allow_replacements = $replacements_allowed:expr])?
293 $(#[profiles = $profiles:expr])?
294 $(#[lints($($for_lints:ident),* $(,)?)])?
295 $name:ident: $ty:ty = $default:expr,
296 )*) => {
297 pub struct Conf {
299 $($(#[cfg_attr(doc, doc = $doc)])+ pub $name: $ty,)*
300 }
301
302 mod defaults {
303 use super::*;
304 $(pub fn $name() -> $ty { $default })*
305 }
306
307 impl Default for Conf {
308 fn default() -> Self {
309 Self { $($name: defaults::$name(),)* }
310 }
311 }
312
313 #[derive(Deserialize)]
314 #[serde(field_identifier, rename_all = "kebab-case")]
315 #[expect(non_camel_case_types)]
316 enum Field { $($name,)* third_party, }
317
318 struct ConfVisitor<'a>(&'a SourceFile);
319
320 impl<'de> Visitor<'de> for ConfVisitor<'_> {
321 type Value = TryConf;
322
323 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
324 formatter.write_str("Conf")
325 }
326
327 fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
328 let mut value_spans = HashMap::new();
329 let mut errors = Vec::new();
330 let mut warnings = Vec::new();
331
332 $(let mut $name = None;)*
334
335 while let Some(name) = map.next_key::<toml::Spanned<String>>()? {
337 let field = match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
338 Err(e) => {
339 let e: FieldError = e;
340 errors.push(ConfError::spanned(self.0, e.error, e.suggestion, name.span()));
341 continue;
342 }
343 Ok(field) => field
344 };
345
346 match field {
347 $(Field::$name => {
348 let field_span = name.span();
349 $(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
351 let (value, value_span) = parse_conf_value!(
352 map,
353 $ty,
354 errors,
355 self.0,
356 field_span,
357 profiles @[$($profiles)?],
360 disallowed @[$($replacements_allowed)?]
361 );
362 if $name.is_some() {
364 errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
365 continue;
366 }
367 $name = Some(value);
368 value_spans.insert(name.get_ref().as_str().to_string(), value_span);
369 $(match $new_conf {
372 Some(_) => errors.push(ConfError::spanned(self.0, concat!(
373 "duplicate field `", stringify!($new_conf),
374 "` (provided as `", stringify!($name), "`)"
375 ), None, name.span())),
376 None => $new_conf = $name.clone(),
377 })?
378 })*
379 Field::third_party => drop(map.next_value::<IgnoredAny>())
381 }
382 }
383 let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
384 Ok(TryConf { conf, value_spans, errors, warnings })
385 }
386 }
387
388 pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
389 vec![$(
390 ClippyConfiguration {
391 name: stringify!($name).replace('_', "-"),
392 default: default_text!(defaults::$name() $(, $default_text)?),
393 lints: &[$($(stringify!($for_lints)),*)?],
394 doc: concat!($($doc, '\n',)*),
395 deprecation_reason: wrap_option!($($dep)?)
396 },
397 )*]
398 }
399 };
400}
401
402fn union(x: &Range<usize>, y: &Range<usize>) -> Range<usize> {
403 Range {
404 start: cmp::min(x.start, y.start),
405 end: cmp::max(x.end, y.end),
406 }
407}
408
409fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
410 Span::new(
411 file.start_pos + BytePos::from_usize(span.start),
412 file.start_pos + BytePos::from_usize(span.end),
413 SyntaxContext::root(),
414 None,
415 )
416}
417
418fn parse_profiles(
419 table: toml::value::Table,
420 file: &SourceFile,
421 value_span: Range<usize>,
422 errors: &mut Vec<ConfError>,
423) -> FxHashMap<String, DisallowedProfile> {
424 let mut profiles = FxHashMap::default();
425 let config_span = span_from_toml_range(file, value_span.clone());
426
427 for (profile_name, profile_value) in table {
428 let toml::Value::Table(mut profile_table) = profile_value else {
429 errors.push(ConfError::spanned(
430 file,
431 format!("invalid profile `{profile_name}`: expected table"),
432 None,
433 value_span.clone(),
434 ));
435 continue;
436 };
437
438 let disallowed_methods = match profile_table
439 .remove("disallowed-methods")
440 .or_else(|| profile_table.remove("disallowed_methods"))
441 {
442 Some(value) => parse_profile_list(
443 file,
444 &profile_name,
445 "disallowed-methods",
446 value,
447 value_span.clone(),
448 config_span,
449 errors,
450 ),
451 None => Vec::new(),
452 };
453
454 let disallowed_types = match profile_table
455 .remove("disallowed-types")
456 .or_else(|| profile_table.remove("disallowed_types"))
457 {
458 Some(value) => parse_profile_list(
459 file,
460 &profile_name,
461 "disallowed-types",
462 value,
463 value_span.clone(),
464 config_span,
465 errors,
466 ),
467 None => Vec::new(),
468 };
469
470 if !profile_table.is_empty() {
471 let keys = profile_table.keys().map(String::as_str).collect::<Vec<_>>().join(", ");
472 errors.push(ConfError::spanned(
473 file,
474 format!("profile `{profile_name}` has unknown keys: {keys}"),
475 None,
476 value_span.clone(),
477 ));
478 }
479
480 profiles.insert(
481 profile_name,
482 DisallowedProfile {
483 disallowed_methods,
484 disallowed_types,
485 },
486 );
487 }
488
489 profiles
490}
491
492fn parse_profile_list(
493 file: &SourceFile,
494 profile_name: &str,
495 key_name: &str,
496 value: toml::Value,
497 value_span: Range<usize>,
498 config_span: Span,
499 errors: &mut Vec<ConfError>,
500) -> Vec<DisallowedPath> {
501 let toml::Value::Array(entries) = value else {
502 errors.push(ConfError::spanned(
503 file,
504 format!("profile `{profile_name}`: `{key_name}` must be an array"),
505 None,
506 value_span,
507 ));
508 return Vec::new();
509 };
510
511 let mut disallowed = Vec::with_capacity(entries.len());
512 for entry in entries {
513 match DisallowedPath::deserialize(entry.clone()) {
514 Ok(mut path) => {
515 path.set_span(config_span);
516 disallowed.push(path);
517 },
518 Err(err) => errors.push(ConfError::spanned(
519 file,
520 format!(
521 "profile `{profile_name}`: {}",
522 err.to_string().replace('\n', " ").trim()
523 ),
524 None,
525 value_span.clone(),
526 )),
527 }
528 }
529
530 disallowed
531}
532
533define_Conf! {
534 #[lints(absolute_paths)]
536 absolute_paths_allowed_crates: Vec<String> = Vec::new(),
537 #[lints(absolute_paths)]
540 absolute_paths_max_segments: u64 = 2,
541 #[lints(undocumented_unsafe_blocks)]
543 accept_comment_above_attributes: bool = true,
544 #[lints(undocumented_unsafe_blocks)]
546 accept_comment_above_statement: bool = true,
547 #[lints(modulo_arithmetic)]
549 allow_comparison_to_zero: bool = true,
550 #[lints(dbg_macro)]
552 allow_dbg_in_tests: bool = false,
553 #[lints(module_name_repetitions)]
555 allow_exact_repetitions: bool = true,
556 #[lints(expect_used)]
558 allow_expect_in_consts: bool = true,
559 #[lints(expect_used)]
561 allow_expect_in_tests: bool = false,
562 #[lints(indexing_slicing)]
564 allow_indexing_slicing_in_tests: bool = false,
565 #[lints(large_stack_frames)]
567 allow_large_stack_frames_in_tests: bool = true,
568 #[lints(uninlined_format_args)]
570 allow_mixed_uninlined_format_args: bool = true,
571 #[lints(needless_raw_string_hashes)]
573 allow_one_hash_in_raw_strings: bool = false,
574 #[lints(panic)]
576 allow_panic_in_tests: bool = false,
577 #[lints(print_stderr, print_stdout)]
579 allow_print_in_tests: bool = false,
580 #[lints(module_inception)]
582 allow_private_module_inception: bool = false,
583 #[lints(renamed_function_params)]
597 allow_renamed_params_for: Vec<String> =
598 DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect(),
599 #[lints(unwrap_used)]
601 allow_unwrap_in_consts: bool = true,
602 #[lints(unwrap_used)]
604 allow_unwrap_in_tests: bool = false,
605 #[lints(expect_used, unwrap_used)]
613 allow_unwrap_types: Vec<String> = Vec::new(),
614 #[lints(useless_vec)]
616 allow_useless_vec_in_tests: bool = false,
617 #[lints(path_ends_with_ext)]
619 allowed_dotfiles: Vec<String> = Vec::default(),
620 #[lints(multiple_crate_versions)]
622 allowed_duplicate_crates: Vec<String> = Vec::new(),
623 #[lints(min_ident_chars)]
627 allowed_idents_below_min_chars: Vec<String> =
628 DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect(),
629 #[lints(module_name_repetitions)]
647 allowed_prefixes: Vec<String> = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect(),
648 #[lints(disallowed_script_idents)]
650 allowed_scripts: Vec<String> = vec!["Latin".to_string()],
651 #[lints(wildcard_imports)]
665 allowed_wildcard_imports: Vec<String> = Vec::new(),
666 #[lints(arithmetic_side_effects)]
681 arithmetic_side_effects_allowed: Vec<String> = <_>::default(),
682 #[lints(arithmetic_side_effects)]
697 arithmetic_side_effects_allowed_binary: Vec<(String, String)> = <_>::default(),
698 #[lints(arithmetic_side_effects)]
706 arithmetic_side_effects_allowed_unary: Vec<String> = <_>::default(),
707 #[lints(large_const_arrays, large_stack_arrays)]
709 array_size_threshold: u64 = 16 * 1024,
710 #[lints(
712 box_collection,
713 enum_variant_names,
714 large_types_passed_by_value,
715 linkedlist,
716 needless_pass_by_ref_mut,
717 option_option,
718 owned_cow,
719 rc_buffer,
720 rc_mutex,
721 redundant_allocation,
722 ref_option,
723 single_call_fn,
724 trivially_copy_pass_by_ref,
725 unnecessary_box_returns,
726 unnecessary_wraps,
727 unused_self,
728 upper_case_acronyms,
729 vec_box,
730 wrong_self_convention,
731 )]
732 avoid_breaking_exported_api: bool = true,
733 #[disallowed_paths_allow_replacements = false]
735 #[lints(await_holding_invalid_type)]
736 await_holding_invalid_types: Vec<DisallowedPathWithoutReplacement> = Vec::new(),
737 #[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)]
741 blacklisted_names: Vec<String> = Vec::new(),
742 #[lints(cargo_common_metadata)]
744 cargo_ignore_publish: bool = false,
745 #[lints(incompatible_msrv)]
747 check_incompatible_msrv_in_tests: bool = false,
748 #[lints(inconsistent_struct_constructor)]
767 check_inconsistent_struct_field_initializers: bool = false,
768 #[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
770 check_private_items: bool = false,
771 #[lints(cognitive_complexity)]
773 cognitive_complexity_threshold: u64 = 25,
774 #[lints(excessive_precision)]
776 const_literal_digits_threshold: usize = 30,
777 #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
781 cyclomatic_complexity_threshold: u64 = 25,
782 #[disallowed_paths_allow_replacements = true]
791 #[lints(disallowed_fields)]
792 disallowed_fields: Vec<DisallowedPath> = Vec::new(),
793 #[disallowed_paths_allow_replacements = true]
802 #[lints(disallowed_macros)]
803 disallowed_macros: Vec<DisallowedPath> = Vec::new(),
804 #[disallowed_paths_allow_replacements = true]
813 #[lints(disallowed_methods)]
814 disallowed_methods: Vec<DisallowedPath> = Vec::new(),
815 #[lints(disallowed_names)]
819 disallowed_names: Vec<String> = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(),
820 #[disallowed_paths_allow_replacements = true]
829 #[lints(disallowed_types)]
830 disallowed_types: Vec<DisallowedPath> = Vec::new(),
831 #[lints(doc_markdown)]
837 doc_valid_idents: Vec<String> = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect(),
838 #[lints(non_send_fields_in_send_ty)]
840 enable_raw_pointer_heuristic_for_send: bool = true,
841 #[lints(explicit_iter_loop)]
859 enforce_iter_loop_reborrow: bool = false,
860 #[lints(missing_enforced_import_renames)]
862 enforced_import_renames: Vec<Rename> = Vec::new(),
863 #[lints(enum_variant_names)]
865 enum_variant_name_threshold: u64 = 3,
866 #[lints(large_enum_variant)]
868 enum_variant_size_threshold: u64 = 200,
869 #[lints(excessive_nesting)]
871 excessive_nesting_threshold: u64 = 0,
872 #[lints(large_futures)]
874 future_size_threshold: u64 = 16 * 1024,
875 #[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)]
877 ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()]),
878 #[lints(multiple_inherent_impl)]
880 inherent_impl_lint_scope: InherentImplLintScope = InherentImplLintScope::Crate,
881 #[lints(result_large_err)]
884 large_error_ignored: Vec<String> = Vec::default(),
885 #[lints(result_large_err)]
887 large_error_threshold: u64 = 128,
888 #[lints(collapsible_else_if, collapsible_if)]
891 lint_commented_code: bool = false,
892 #[conf_deprecated("Please use `check-inconsistent-struct-field-initializers` instead", check_inconsistent_struct_field_initializers)]
897 lint_inconsistent_struct_field_initializers: bool = false,
898 #[lints(decimal_literal_representation)]
900 literal_representation_threshold: u64 = 16384,
901 #[lints(manual_let_else)]
904 matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes,
905 #[lints(fn_params_excessive_bools)]
908 max_fn_params_bools: u64 = 3,
909 #[lints(large_include_file)]
911 max_include_file_size: u64 = 1_000_000,
912 #[lints(struct_excessive_bools)]
914 max_struct_bools: u64 = 3,
915 #[lints(index_refutable_slice)]
919 max_suggested_slice_pattern_length: u64 = 3,
920 #[lints(type_repetition_in_bounds)]
922 max_trait_bounds: u64 = 3,
923 #[lints(min_ident_chars)]
925 min_ident_chars_threshold: u64 = 1,
926 #[lints(missing_docs_in_private_items)]
928 missing_docs_allow_unused: bool = false,
929 #[lints(missing_docs_in_private_items)]
932 missing_docs_in_crate_items: bool = false,
933 #[lints(arbitrary_source_item_ordering)]
935 module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(),
936 #[lints(arbitrary_source_item_ordering)]
941 module_items_ordered_within_groupings: SourceItemOrderingWithinModuleItemGroupings =
942 SourceItemOrderingWithinModuleItemGroupings::None,
943 #[default_text = "current version"]
945 #[lints(
946 allow_attributes,
947 allow_attributes_without_reason,
948 almost_complete_range,
949 approx_constant,
950 assigning_clones,
951 borrow_as_ptr,
952 cast_abs_to_unsigned,
953 checked_conversions,
954 cloned_instead_of_copied,
955 collapsible_match,
956 collapsible_str_replace,
957 deprecated_cfg_attr,
958 derivable_impls,
959 err_expect,
960 filter_map_next,
961 from_over_into,
962 if_then_some_else_none,
963 index_refutable_slice,
964 inefficient_to_string,
965 io_other_error,
966 iter_kv_map,
967 legacy_numeric_constants,
968 len_zero,
969 lines_filter_map_ok,
970 manual_abs_diff,
971 manual_bits,
972 manual_c_str_literals,
973 manual_clamp,
974 manual_div_ceil,
975 manual_flatten,
976 manual_hash_one,
977 manual_is_ascii_check,
978 manual_is_power_of_two,
979 manual_isolate_lowest_one,
980 manual_let_else,
981 manual_midpoint,
982 manual_non_exhaustive,
983 manual_noop_waker,
984 manual_option_as_slice,
985 manual_pattern_char_comparison,
986 manual_range_contains,
987 manual_rem_euclid,
988 manual_repeat_n,
989 manual_retain,
990 manual_slice_fill,
991 manual_slice_size_calculation,
992 manual_split_once,
993 manual_str_repeat,
994 manual_strip,
995 manual_take,
996 manual_try_fold,
997 map_clone,
998 map_unwrap_or,
999 map_with_unused_argument_over_ranges,
1000 match_like_matches_macro,
1001 mem_replace_option_with_some,
1002 mem_replace_with_default,
1003 missing_const_for_fn,
1004 needless_borrow,
1005 non_std_lazy_statics,
1006 option_as_ref_deref,
1007 or_fun_call,
1008 ptr_as_ptr,
1009 question_mark,
1010 redundant_field_names,
1011 redundant_static_lifetimes,
1012 repeat_vec_with_capacity,
1013 same_item_push,
1014 seek_from_current,
1015 to_digit_is_some,
1016 transmute_ptr_to_ref,
1017 tuple_array_conversions,
1018 type_repetition_in_bounds,
1019 unchecked_time_subtraction,
1020 uninlined_format_args,
1021 unnecessary_lazy_evaluations,
1022 unnecessary_unwrap,
1023 unnested_or_patterns,
1024 unused_trait_names,
1025 use_self,
1026 zero_ptr,
1027 )]
1028 msrv: Msrv = Msrv::default(),
1029 #[lints(large_types_passed_by_value)]
1031 pass_by_value_size_limit: u64 = 256,
1032 #[profiles = true]
1045 #[lints(disallowed_methods, disallowed_types)]
1046 profiles: FxHashMap<String, DisallowedProfile> = FxHashMap::default(),
1047 #[lints(pub_underscore_fields)]
1050 pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported,
1051 #[lints(use_self)]
1053 recursive_self_in_type_definitions: bool = true,
1054 #[lints(semicolon_inside_block)]
1056 semicolon_inside_block_ignore_singleline: bool = false,
1057 #[lints(semicolon_outside_block)]
1059 semicolon_outside_block_ignore_multiline: bool = false,
1060 #[lints(many_single_char_names)]
1062 single_char_binding_names_threshold: u64 = 4,
1063 #[lints(arbitrary_source_item_ordering)]
1065 source_item_ordering: SourceItemOrdering = DEFAULT_SOURCE_ITEM_ORDERING.into(),
1066 #[lints(large_stack_frames)]
1068 stack_size_threshold: u64 = 512_000,
1069 #[lints(nonstandard_macro_braces)]
1075 standard_macro_braces: Vec<MacroMatcher> = Vec::new(),
1076 #[lints(struct_field_names)]
1078 struct_field_name_threshold: u64 = 3,
1079 #[lints(indexing_slicing)]
1085 suppress_restriction_lint_in_const: bool = false,
1086 #[lints(boxed_local, useless_vec)]
1088 too_large_for_stack: u64 = 200,
1089 #[lints(too_many_arguments)]
1091 too_many_arguments_threshold: u64 = 7,
1092 #[lints(too_many_lines)]
1094 too_many_lines_threshold: u64 = 100,
1095 #[lints(arbitrary_source_item_ordering)]
1097 trait_assoc_item_kinds_order: SourceItemOrderingTraitAssocItemKinds = DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER.into(),
1098 #[default_text = "target_pointer_width"]
1101 #[lints(trivially_copy_pass_by_ref)]
1102 trivial_copy_size_limit: Option<u64> = None,
1103 #[lints(type_complexity)]
1105 type_complexity_threshold: u64 = 250,
1106 #[lints(unnecessary_box_returns)]
1108 unnecessary_box_size: u64 = 128,
1109 #[lints(unreadable_literal)]
1111 unreadable_literal_lint_fractions: bool = true,
1112 #[lints(upper_case_acronyms)]
1114 upper_case_acronyms_aggressive: bool = false,
1115 #[lints(vec_box)]
1117 vec_box_size_threshold: u64 = 4096,
1118 #[lints(verbose_bit_mask)]
1120 verbose_bit_mask_threshold: u64 = 1,
1121 #[lints(wildcard_imports)]
1124 warn_on_all_wildcard_imports: bool = false,
1125 #[lints(macro_metavars_in_unsafe)]
1127 warn_unsafe_macro_metavars_in_private_macros: bool = false,
1128}
1129
1130pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
1136 const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
1138
1139 let mut current = env::var_os("CLIPPY_CONF_DIR")
1142 .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
1143 .map_or_else(|| PathBuf::from("."), PathBuf::from)
1144 .canonicalize()?;
1145
1146 let mut found_config: Option<PathBuf> = None;
1147 let mut warnings = vec![];
1148
1149 loop {
1150 for config_file_name in &CONFIG_FILE_NAMES {
1151 if let Ok(config_file) = current.join(config_file_name).canonicalize() {
1152 match fs::metadata(&config_file) {
1153 Err(e) if e.kind() == io::ErrorKind::NotFound => {},
1154 Err(e) => return Err(e),
1155 Ok(md) if md.is_dir() => {},
1156 Ok(_) => {
1157 if let Some(ref found_config) = found_config {
1159 warnings.push(format!(
1160 "using config file `{}`, `{}` will be ignored",
1161 found_config.display(),
1162 config_file.display()
1163 ));
1164 } else {
1165 found_config = Some(config_file);
1166 }
1167 },
1168 }
1169 }
1170 }
1171
1172 if found_config.is_some() {
1173 return Ok((found_config, warnings));
1174 }
1175
1176 if !current.pop() {
1178 return Ok((None, warnings));
1179 }
1180 }
1181}
1182
1183fn deserialize(file: &SourceFile) -> TryConf {
1184 match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(file)) {
1185 Ok(mut conf) => {
1186 extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
1187 extend_vec_if_indicator_present(&mut conf.conf.allowed_prefixes, DEFAULT_ALLOWED_PREFIXES);
1188 extend_vec_if_indicator_present(
1189 &mut conf.conf.allow_renamed_params_for,
1190 DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS,
1191 );
1192
1193 if let SourceItemOrderingWithinModuleItemGroupings::Custom(groupings) =
1196 &conf.conf.module_items_ordered_within_groupings
1197 {
1198 for grouping in groupings {
1199 if !conf.conf.module_item_order_groupings.is_grouping(grouping) {
1200 let names = conf.conf.module_item_order_groupings.grouping_names();
1204 let suggestion = suggest_candidate(grouping, names.iter().map(String::as_str))
1205 .map(|s| format!(" perhaps you meant `{s}`?"))
1206 .unwrap_or_default();
1207 let names = names.iter().map(|s| format!("`{s}`")).join(", ");
1208 let message = format!(
1209 "unknown ordering group: `{grouping}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}"
1210 );
1211
1212 let span = conf
1213 .value_spans
1214 .get("module_item_order_groupings")
1215 .cloned()
1216 .unwrap_or_default();
1217 conf.errors.push(ConfError::spanned(file, message, None, span));
1218 }
1219 }
1220 }
1221
1222 if conf.conf.allowed_idents_below_min_chars.iter().any(|e| e == "..") {
1224 conf.conf
1225 .allowed_idents_below_min_chars
1226 .extend(DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string));
1227 }
1228 if conf.conf.doc_valid_idents.iter().any(|e| e == "..") {
1229 conf.conf
1230 .doc_valid_idents
1231 .extend(DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string));
1232 }
1233
1234 conf
1235 },
1236 Err(e) => TryConf::from_toml_error(file, &e),
1237 }
1238}
1239
1240fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
1241 if vec.contains(&"..".to_string()) {
1242 vec.extend(default.iter().map(ToString::to_string));
1243 }
1244}
1245
1246impl Conf {
1247 pub fn read(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> &'static Conf {
1248 static CONF: OnceLock<Conf> = OnceLock::new();
1249 CONF.get_or_init(|| Conf::read_inner(sess, path))
1250 }
1251
1252 fn read_inner(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
1253 match path {
1254 Ok((_, warnings)) => {
1255 for warning in warnings {
1256 sess.dcx().warn(warning.clone());
1257 }
1258 },
1259 Err(error) => {
1260 sess.dcx()
1261 .err(format!("error finding Clippy's configuration file: {error}"));
1262 },
1263 }
1264
1265 let TryConf {
1266 mut conf,
1267 value_spans: _,
1268 errors,
1269 warnings,
1270 } = match path {
1271 Ok((Some(path), _)) => match sess.source_map().load_file(path) {
1272 Ok(file) => deserialize(&file),
1273 Err(error) => {
1274 sess.dcx().err(format!("failed to read `{}`: {error}", path.display()));
1275 TryConf::default()
1276 },
1277 },
1278 _ => TryConf::default(),
1279 };
1280
1281 conf.msrv.read_cargo(sess);
1282
1283 for error in errors {
1285 let mut diag = sess.dcx().struct_span_err(
1286 error.span,
1287 format!("error reading Clippy's configuration file: {}", error.message),
1288 );
1289
1290 if let Some(sugg) = error.suggestion {
1291 diag.span_suggestion(error.span, sugg.message, sugg.suggestion, Applicability::MaybeIncorrect);
1292 }
1293
1294 diag.emit();
1295 }
1296
1297 for warning in warnings {
1298 sess.dcx().span_warn(
1299 warning.span,
1300 format!("error reading Clippy's configuration file: {}", warning.message),
1301 );
1302 }
1303
1304 conf
1305 }
1306}
1307
1308const SEPARATOR_WIDTH: usize = 4;
1309
1310#[derive(Debug)]
1311struct FieldError {
1312 error: String,
1313 suggestion: Option<Suggestion>,
1314}
1315
1316#[derive(Debug)]
1317struct Suggestion {
1318 message: &'static str,
1319 suggestion: &'static str,
1320}
1321
1322impl std::error::Error for FieldError {}
1323
1324impl Display for FieldError {
1325 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1326 f.pad(&self.error)
1327 }
1328}
1329
1330impl serde::de::Error for FieldError {
1331 fn custom<T: Display>(msg: T) -> Self {
1332 Self {
1333 error: msg.to_string(),
1334 suggestion: None,
1335 }
1336 }
1337
1338 fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
1339 use fmt::Write;
1342
1343 let metadata = get_configuration_metadata();
1344 let deprecated = metadata
1345 .iter()
1346 .filter_map(|conf| {
1347 if conf.deprecation_reason.is_some() {
1348 Some(conf.name.as_str())
1349 } else {
1350 None
1351 }
1352 })
1353 .collect::<Vec<_>>();
1354
1355 let mut expected = expected
1356 .iter()
1357 .copied()
1358 .filter(|name| !deprecated.contains(name))
1359 .collect::<Vec<_>>();
1360 expected.sort_unstable();
1361
1362 let (rows, column_widths) = calculate_dimensions(&expected);
1363
1364 let mut msg = format!("unknown field `{field}`, expected one of");
1365 for row in 0..rows {
1366 writeln!(msg).unwrap();
1367 for (column, column_width) in column_widths.iter().copied().enumerate() {
1368 let index = column * rows + row;
1369 let field = expected.get(index).copied().unwrap_or_default();
1370 write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap();
1371 }
1372 }
1373
1374 let suggestion = suggest_candidate(field, expected).map(|suggestion| Suggestion {
1375 message: "perhaps you meant",
1376 suggestion,
1377 });
1378
1379 Self { error: msg, suggestion }
1380 }
1381}
1382
1383fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
1384 let columns = env::var("CLIPPY_TERMINAL_WIDTH")
1385 .ok()
1386 .and_then(|s| <usize as FromStr>::from_str(&s).ok())
1387 .map_or(1, |terminal_width| {
1388 let max_field_width = fields.iter().map(|field| field.len()).max().unwrap();
1389 cmp::max(1, terminal_width / (SEPARATOR_WIDTH + max_field_width))
1390 });
1391
1392 let rows = fields.len().div_ceil(columns);
1393
1394 let column_widths = (0..columns)
1395 .map(|column| {
1396 if column < columns - 1 {
1397 (0..rows)
1398 .map(|row| {
1399 let index = column * rows + row;
1400 let field = fields.get(index).copied().unwrap_or_default();
1401 field.len()
1402 })
1403 .max()
1404 .unwrap()
1405 } else {
1406 0
1408 }
1409 })
1410 .collect::<Vec<_>>();
1411
1412 (rows, column_widths)
1413}
1414
1415fn suggest_candidate<'a, I>(value: &str, candidates: I) -> Option<&'a str>
1418where
1419 I: IntoIterator<Item = &'a str>,
1420{
1421 candidates
1422 .into_iter()
1423 .filter_map(|expected| {
1424 let dist = edit_distance(value, expected, 4)?;
1425 Some((dist, expected))
1426 })
1427 .min_by_key(|&(dist, _)| dist)
1428 .map(|(_, suggestion)| suggestion)
1429}
1430
1431#[cfg(test)]
1432mod tests {
1433 use serde::de::IgnoredAny;
1434 use std::collections::{HashMap, HashSet};
1435 use std::fs;
1436 use walkdir::WalkDir;
1437
1438 #[test]
1439 fn configs_are_tested() {
1440 let mut names: HashSet<String> = crate::get_configuration_metadata()
1441 .into_iter()
1442 .filter_map(|meta| {
1443 if meta.deprecation_reason.is_none() {
1444 Some(meta.name.replace('_', "-"))
1445 } else {
1446 None
1447 }
1448 })
1449 .collect();
1450
1451 let toml_files = WalkDir::new("../tests")
1452 .into_iter()
1453 .map(Result::unwrap)
1454 .filter(|entry| entry.file_name() == "clippy.toml");
1455
1456 for entry in toml_files {
1457 let file = fs::read_to_string(entry.path()).unwrap();
1458 #[expect(clippy::zero_sized_map_values)]
1459 if let Ok(map) = toml::from_str::<HashMap<String, IgnoredAny>>(&file) {
1460 for name in map.keys() {
1461 names.remove(name.as_str());
1462 }
1463 }
1464 }
1465
1466 assert!(
1467 names.is_empty(),
1468 "Configuration variable lacks test: {names:?}\nAdd a test to `tests/ui-toml`"
1469 );
1470 }
1471}