1mod blanket_hint_mostly_unused;
2mod deferred_parse_diagnostics;
3mod im_a_teapot;
4mod implicit_minimum_version_req;
5mod missing_lints_features;
6mod missing_lints_inheritance;
7mod non_kebab_case_bins;
8mod non_kebab_case_features;
9mod non_kebab_case_packages;
10mod non_snake_case_features;
11mod non_snake_case_packages;
12mod redundant_homepage;
13mod redundant_readme;
14mod text_direction_codepoint_in_comment;
15mod text_direction_codepoint_in_literal;
16mod unknown_lints;
17pub mod unused_dependencies;
18mod unused_workspace_dependencies;
19mod unused_workspace_package_fields;
20
21use super::LintGroup;
22use super::LintLevel;
23use super::passes::ParsePassRule;
24use crate::workspace::Feature;
25
26pub const PARSE_PASS_RULES: &[ParsePassRule<'static>] = &[
27 ParsePassRule::DiagnosticManifest {
28 rule: deferred_parse_diagnostics::diagnose_manifest,
29 },
30 ParsePassRule::DiagnosticManifest {
31 rule: missing_lints_features::diagnose_manifest,
32 },
33 ParsePassRule::LintManifest {
34 rule: text_direction_codepoint_in_comment::lint_manifest,
35 lint: text_direction_codepoint_in_comment::LINT,
36 },
37 ParsePassRule::LintManifest {
38 rule: text_direction_codepoint_in_literal::lint_manifest,
39 lint: text_direction_codepoint_in_literal::LINT,
40 },
41 ParsePassRule::LintManifest {
42 rule: unknown_lints::lint_manifest,
43 lint: unknown_lints::LINT,
44 },
45 ParsePassRule::LintWorkspace {
46 rule: blanket_hint_mostly_unused::lint_workspace,
47 lint: blanket_hint_mostly_unused::LINT,
48 },
49 ParsePassRule::LintWorkspace {
50 rule: unused_workspace_dependencies::lint_workspace,
51 lint: unused_workspace_dependencies::LINT,
52 },
53 ParsePassRule::LintWorkspace {
54 rule: unused_workspace_package_fields::lint_workspace,
55 lint: unused_workspace_package_fields::LINT,
56 },
57 ParsePassRule::LintWorkspace {
58 rule: implicit_minimum_version_req::lint_workspace,
59 lint: implicit_minimum_version_req::LINT,
60 },
61 ParsePassRule::LintPackage {
63 rule: missing_lints_inheritance::lint_package,
64 lint: missing_lints_inheritance::LINT,
65 },
66 ParsePassRule::LintPackage {
67 rule: non_kebab_case_bins::lint_package,
68 lint: non_kebab_case_bins::LINT,
69 },
70 ParsePassRule::LintPackage {
71 rule: redundant_homepage::lint_package,
72 lint: redundant_homepage::LINT,
73 },
74 ParsePassRule::LintPackage {
75 rule: redundant_readme::lint_package,
76 lint: redundant_readme::LINT,
77 },
78 ParsePassRule::LintPackage {
79 rule: unused_dependencies::lint_package,
80 lint: unused_dependencies::LINT,
81 },
82 ParsePassRule::LintPackage {
83 rule: im_a_teapot::lint_package,
84 lint: im_a_teapot::LINT,
85 },
86 ParsePassRule::LintPackage {
88 rule: implicit_minimum_version_req::lint_package,
89 lint: implicit_minimum_version_req::LINT,
90 },
91 ParsePassRule::LintPackage {
92 rule: non_kebab_case_features::lint_package,
93 lint: non_kebab_case_features::LINT,
94 },
95 ParsePassRule::LintPackage {
96 rule: non_kebab_case_packages::lint_package,
97 lint: non_kebab_case_packages::LINT,
98 },
99 ParsePassRule::LintPackage {
100 rule: non_snake_case_features::lint_package,
101 lint: non_snake_case_features::LINT,
102 },
103 ParsePassRule::LintPackage {
104 rule: non_snake_case_packages::lint_package,
105 lint: non_snake_case_packages::LINT,
106 },
107];
108
109pub static LINTS: &[&crate::diagnostics::Lint] = &[
110 blanket_hint_mostly_unused::LINT,
111 implicit_minimum_version_req::LINT,
112 im_a_teapot::LINT,
113 missing_lints_inheritance::LINT,
114 non_kebab_case_bins::LINT,
115 non_kebab_case_features::LINT,
116 non_kebab_case_packages::LINT,
117 non_snake_case_features::LINT,
118 non_snake_case_packages::LINT,
119 redundant_homepage::LINT,
120 redundant_readme::LINT,
121 text_direction_codepoint_in_comment::LINT,
122 text_direction_codepoint_in_literal::LINT,
123 unknown_lints::LINT,
124 unused_dependencies::LINT,
125 unused_workspace_dependencies::LINT,
126 unused_workspace_package_fields::LINT,
127];
128
129static CARGO_LINTS_MSRV: cargo_util_schemas::manifest::RustVersion =
134 cargo_util_schemas::manifest::RustVersion::new(1, 79, 0);
135
136pub static LINT_GROUPS: &[LintGroup] = &[
137 DEFAULT,
138 CORRECTNESS,
139 COMPLEXITY,
140 PERF,
141 STYLE,
142 SUSPICIOUS,
143 NURSERY,
144 PEDANTIC,
145 RESTRICTION,
146 TEST_DUMMY_UNSTABLE,
147];
148
149const DEFAULT: LintGroup = LintGroup {
150 name: "default",
151 desc: "all lints that are on by default (correctness, suspicious, style, complexity, perf)",
152 default_level: LintLevel::Warn,
153 feature_gate: None,
154 hidden: false,
155};
156
157const COMPLEXITY: LintGroup = LintGroup {
158 name: "complexity",
159 desc: "code that does something simple but in a complex way",
160 default_level: LintLevel::Warn,
161 feature_gate: None,
162 hidden: false,
163};
164
165const CORRECTNESS: LintGroup = LintGroup {
166 name: "correctness",
167 desc: "code that is outright wrong or useless",
168 default_level: LintLevel::Deny,
169 feature_gate: None,
170 hidden: false,
171};
172
173const NURSERY: LintGroup = LintGroup {
174 name: "nursery",
175 desc: "new lints that are still under development",
176 default_level: LintLevel::Allow,
177 feature_gate: None,
178 hidden: false,
179};
180
181const PEDANTIC: LintGroup = LintGroup {
182 name: "pedantic",
183 desc: "lints which are rather strict or have occasional false positives",
184 default_level: LintLevel::Allow,
185 feature_gate: None,
186 hidden: false,
187};
188
189const PERF: LintGroup = LintGroup {
190 name: "perf",
191 desc: "code that can be written to run faster",
192 default_level: LintLevel::Warn,
193 feature_gate: None,
194 hidden: false,
195};
196
197const RESTRICTION: LintGroup = LintGroup {
198 name: "restriction",
199 desc: "lints which prevent the use of Cargo features",
200 default_level: LintLevel::Allow,
201 feature_gate: None,
202 hidden: false,
203};
204
205const STYLE: LintGroup = LintGroup {
206 name: "style",
207 desc: "code that should be written in a more idiomatic way",
208 default_level: LintLevel::Warn,
209 feature_gate: None,
210 hidden: false,
211};
212
213const SUSPICIOUS: LintGroup = LintGroup {
214 name: "suspicious",
215 desc: "code that is most likely wrong or useless",
216 default_level: LintLevel::Warn,
217 feature_gate: None,
218 hidden: false,
219};
220
221const TEST_DUMMY_UNSTABLE: LintGroup = LintGroup {
223 name: "test_dummy_unstable",
224 desc: "test_dummy_unstable is meant to only be used in tests",
225 default_level: LintLevel::Allow,
226 feature_gate: Some(crate::workspace::Feature::test_dummy_unstable()),
227 hidden: true,
228};
229
230fn find_lint_or_group<'a>(
231 name: &str,
232) -> Option<(&'static str, &LintLevel, &Option<&'static Feature>)> {
233 if let Some(lint) = LINTS.iter().find(|l| l.name == name) {
234 Some((
235 lint.name,
236 &lint.primary_group.default_level,
237 &lint.feature_gate,
238 ))
239 } else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == name) {
240 Some((group.name, &group.default_level, &group.feature_gate))
241 } else {
242 None
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use crate::util::data_structures::HashSet;
249 use crate::util::data_structures::IndexMap;
250 use itertools::Itertools;
251 use snapbox::ToDebug;
252 use std::cmp::Reverse;
253
254 use super::*;
255
256 #[test]
257 fn ensure_lint_groups_do_not_default_to_forbid() {
258 let forbid_groups = LINT_GROUPS
259 .iter()
260 .filter(|g| matches!(g.default_level, LintLevel::Forbid))
261 .collect::<Vec<_>>();
262
263 assert!(
264 forbid_groups.is_empty(),
265 "\n`LintGroup`s should never default to `forbid`, but the following do:\n\
266 {}\n",
267 forbid_groups.iter().map(|g| g.name).join("\n")
268 );
269 }
270
271 #[test]
272 fn ensure_visible_lint_msrv() {
273 let invalid_msrvs = LINTS
274 .iter()
275 .filter(|l| !matches!(l.primary_group.default_level, LintLevel::Allow))
277 .filter(|l| l.msrv.map(|v| v < CARGO_LINTS_MSRV).unwrap_or(false))
278 .map(|l| l.name)
279 .join(", ");
280 assert!(
281 invalid_msrvs.is_empty(),
282 "{invalid_msrvs} need `msrv` set so users can use `[lints.cargo]` to disable them"
283 );
284 }
285
286 #[test]
287 fn ensure_docs_sections() {
288 let expected_sections_restriction = &[
289 "### What it does",
290 "### Why restrict this?",
291 "### Drawbacks",
292 "### Example",
293 ];
294 let expected_sections = &[
295 "### What it does",
296 "### Why is this bad?",
297 "### Drawbacks",
298 "### Example",
299 ];
300 for lint in LINTS {
301 dbg!(lint.name);
302 let mut sections = IndexMap::default();
303 let mut title = "";
304 let mut body = Vec::new();
305 let Some(docs) = lint.docs else {
306 continue;
307 };
308 for line in docs.trim().lines() {
309 if line.starts_with("#") {
310 if !title.is_empty() || !body.is_empty() {
311 let old = sections.insert(title, body);
312 assert!(old.is_none(), "duplicate title: `{title:?}`");
313 }
314 title = line;
315 body = Vec::new();
316 } else {
317 body.push(line);
318 }
319 }
320 if !title.is_empty() || !body.is_empty() {
321 let old = sections.insert(title, body);
322 assert!(old.is_none(), "duplicate title: `{title:?}`");
323 }
324
325 let mut expected = Vec::new();
326 let expected_sections = match lint.primary_group.name {
327 "restriction" => expected_sections_restriction,
328 _ => expected_sections,
329 };
330 for section in expected_sections {
331 let body = match sections.get(section) {
332 Some(body) => body,
333 None => continue,
334 };
335 expected.push(*section);
336 expected.extend(body.iter().copied());
337 }
338 let expected = expected.join("\n");
339 snapbox::assert_data_eq!(docs.trim(), expected);
340 }
341 }
342
343 #[test]
344 fn ensure_sorted_lints() {
345 let location = std::panic::Location::caller();
347 println!("\nTo fix this test, sort `LINTS` in {}\n", location.file(),);
348
349 let actual = LINTS
350 .iter()
351 .map(|l| l.name.to_uppercase())
352 .collect::<Vec<_>>();
353
354 let mut expected = actual.clone();
355 expected.sort();
356 snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
357 }
358
359 #[test]
360 fn ensure_sorted_lint_groups() {
361 let location = std::panic::Location::caller();
363 println!(
364 "\nTo fix this test, sort `LINT_GROUPS` in {}\n",
365 location.file(),
366 );
367 let actual = LINT_GROUPS
368 .iter()
369 .map(|l| {
370 (
371 l.name != "default",
372 Reverse(l.default_level),
373 l.name.to_uppercase(),
374 )
375 })
376 .collect::<Vec<_>>();
377
378 let mut expected = actual.clone();
379 expected.sort();
380 snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
381 }
382
383 #[test]
384 fn ensure_sorted_parse_pass_rules() {
385 let actual = parse_pass_rule_names(PARSE_PASS_RULES);
386 let mut ordered_parse_pass = PARSE_PASS_RULES.to_vec();
387 ordered_parse_pass.sort_by_key(|rule| {
388 let (lint, scope) = match rule {
389 ParsePassRule::DiagnosticManifest { .. } => {
390 let scope = 0;
391 (None, scope)
392 }
393 ParsePassRule::LintManifest { lint, .. } => {
394 let scope = 0;
395 (Some(lint), scope)
396 }
397 ParsePassRule::DiagnosticWorkspace { .. } => {
398 let scope = 1;
399 (None, scope)
400 }
401 ParsePassRule::LintWorkspace { lint, .. } => {
402 let scope = 1;
403 (Some(lint), scope)
404 }
405 ParsePassRule::DiagnosticPackage { .. } => {
406 let scope = 2;
407 (None, scope)
408 }
409 ParsePassRule::LintPackage { lint, .. } => {
410 let scope = 2;
411 (Some(lint), scope)
412 }
413 };
414 let is_lint = lint.is_some();
415 let level = lint.map(|l| std::cmp::Reverse(l.primary_group.default_level));
416 let name = lint.map(|l| l.name);
417 (is_lint, scope, level, name)
418 });
419 let expected = parse_pass_rule_names(&ordered_parse_pass);
420
421 println!("`PARSE_PASS_RULES` sort order:");
422 snapbox::assert_data_eq!(actual.join("\n"), expected.join("\n"));
423 }
424
425 #[test]
426 fn ensure_parse_passed_in_lints() {
427 let parse_pass_lint_names =
428 HashSet::from_iter(parse_pass_rule_names(PARSE_PASS_RULES).into_iter());
429 let lint_names = LINTS.iter().map(|l| l.name).collect::<HashSet<_>>();
430 let diff = parse_pass_lint_names
431 .difference(&lint_names)
432 .sorted()
433 .collect::<Vec<_>>();
434 let mut need_added = String::new();
435 for name in &diff {
436 need_added.push_str(&format!("{name}\n"));
437 }
438 assert!(
439 diff.is_empty(),
440 "\n`LINTS` did not contain all `Lint`s found in `PARSE_PASS_RULES`\n\
441 Please add the following to `LINTS`:\n\
442 {need_added}",
443 );
444 }
445
446 fn parse_pass_rule_names(rules: &[ParsePassRule<'_>]) -> Vec<&'static str> {
447 rules
448 .iter()
449 .filter_map(|rule| match rule {
450 ParsePassRule::DiagnosticManifest { .. }
451 | ParsePassRule::DiagnosticWorkspace { .. }
452 | ParsePassRule::DiagnosticPackage { .. } => None,
453 ParsePassRule::LintManifest { lint, .. }
454 | ParsePassRule::LintWorkspace { lint, .. }
455 | ParsePassRule::LintPackage { lint, .. } => Some(lint.name),
456 })
457 .collect()
458 }
459
460 #[test]
461 fn ensure_updated_lints() {
462 let dir = snapbox::utils::current_dir!();
463 let mut expected = HashSet::default();
464 for entry in std::fs::read_dir(&dir).unwrap() {
465 let entry = entry.unwrap();
466 let path = entry.path();
467 if path.ends_with("mod.rs") {
468 continue;
469 }
470 let content = std::fs::read_to_string(&path).unwrap();
471 if !content.contains("LINT") {
472 continue;
474 }
475 let lint_name = path.file_stem().unwrap().to_string_lossy();
476 assert!(expected.insert(lint_name.into()), "duplicate lint found");
477 }
478
479 let actual = LINTS
480 .iter()
481 .map(|l| l.name.to_string())
482 .collect::<HashSet<_>>();
483 let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
484
485 let mut need_added = String::new();
486 for name in &diff {
487 need_added.push_str(&format!("{name}\n"));
488 }
489 assert!(
490 diff.is_empty(),
491 "\n`LINTS` did not contain all `Lint`s found in {}\n\
492 Please add the following to `LINTS`:\n\
493 {need_added}",
494 dir.display(),
495 );
496 }
497
498 #[test]
499 fn ensure_updated_lint_groups() {
500 let path = snapbox::utils::current_rs!();
501 let expected = std::fs::read_to_string(&path).unwrap();
502 let expected = expected
503 .lines()
504 .filter_map(|l| {
505 if l.ends_with(": LintGroup = LintGroup {") {
506 Some(
507 l.chars()
508 .skip(6)
509 .take_while(|c| *c != ':')
510 .collect::<String>(),
511 )
512 } else {
513 None
514 }
515 })
516 .collect::<HashSet<_>>();
517 let actual = LINT_GROUPS
518 .iter()
519 .map(|l| l.name.to_uppercase())
520 .collect::<HashSet<_>>();
521 let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
522
523 let mut need_added = String::new();
524 for name in &diff {
525 need_added.push_str(&format!("{}\n", name));
526 }
527 assert!(
528 diff.is_empty(),
529 "\n`LINT_GROUPS` did not contain all `LintGroup`s found in {}\n\
530 Please add the following to `LINT_GROUPS`:\n\
531 {}",
532 path.display(),
533 need_added
534 );
535 }
536}