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::core::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::core::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 itertools::Itertools;
249 use snapbox::ToDebug;
250 use std::cmp::Reverse;
251 use std::collections::HashSet;
252
253 use super::*;
254
255 #[test]
256 fn ensure_lint_groups_do_not_default_to_forbid() {
257 let forbid_groups = LINT_GROUPS
258 .iter()
259 .filter(|g| matches!(g.default_level, LintLevel::Forbid))
260 .collect::<Vec<_>>();
261
262 assert!(
263 forbid_groups.is_empty(),
264 "\n`LintGroup`s should never default to `forbid`, but the following do:\n\
265 {}\n",
266 forbid_groups.iter().map(|g| g.name).join("\n")
267 );
268 }
269
270 #[test]
271 fn ensure_sorted_lints() {
272 let location = std::panic::Location::caller();
274 println!("\nTo fix this test, sort `LINTS` in {}\n", location.file(),);
275
276 let actual = LINTS
277 .iter()
278 .map(|l| l.name.to_uppercase())
279 .collect::<Vec<_>>();
280
281 let mut expected = actual.clone();
282 expected.sort();
283 snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
284 }
285
286 #[test]
287 fn ensure_sorted_lint_groups() {
288 let location = std::panic::Location::caller();
290 println!(
291 "\nTo fix this test, sort `LINT_GROUPS` in {}\n",
292 location.file(),
293 );
294 let actual = LINT_GROUPS
295 .iter()
296 .map(|l| {
297 (
298 l.name != "default",
299 Reverse(l.default_level),
300 l.name.to_uppercase(),
301 )
302 })
303 .collect::<Vec<_>>();
304
305 let mut expected = actual.clone();
306 expected.sort();
307 snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
308 }
309
310 #[test]
311 fn ensure_sorted_parse_pass_rules() {
312 let actual = parse_pass_rule_names(PARSE_PASS_RULES);
313 let mut ordered_parse_pass = PARSE_PASS_RULES.to_vec();
314 ordered_parse_pass.sort_by_key(|rule| {
315 let (lint, scope) = match rule {
316 ParsePassRule::DiagnosticManifest { .. } => {
317 let scope = 0;
318 (None, scope)
319 }
320 ParsePassRule::LintManifest { lint, .. } => {
321 let scope = 0;
322 (Some(lint), scope)
323 }
324 ParsePassRule::DiagnosticWorkspace { .. } => {
325 let scope = 1;
326 (None, scope)
327 }
328 ParsePassRule::LintWorkspace { lint, .. } => {
329 let scope = 1;
330 (Some(lint), scope)
331 }
332 ParsePassRule::DiagnosticPackage { .. } => {
333 let scope = 2;
334 (None, scope)
335 }
336 ParsePassRule::LintPackage { lint, .. } => {
337 let scope = 2;
338 (Some(lint), scope)
339 }
340 };
341 let is_lint = lint.is_some();
342 let level = lint.map(|l| std::cmp::Reverse(l.primary_group.default_level));
343 let name = lint.map(|l| l.name);
344 (is_lint, scope, level, name)
345 });
346 let expected = parse_pass_rule_names(&ordered_parse_pass);
347
348 println!("`PARSE_PASS_RULES` sort order:");
349 snapbox::assert_data_eq!(actual.join("\n"), expected.join("\n"));
350 }
351
352 #[test]
353 fn ensure_parse_passed_in_lints() {
354 let parse_pass_lint_names =
355 HashSet::from_iter(parse_pass_rule_names(PARSE_PASS_RULES).into_iter());
356 let lint_names = LINTS
357 .iter()
358 .map(|l| l.name)
359 .collect::<std::collections::HashSet<_>>();
360 let diff = parse_pass_lint_names
361 .difference(&lint_names)
362 .sorted()
363 .collect::<Vec<_>>();
364 let mut need_added = String::new();
365 for name in &diff {
366 need_added.push_str(&format!("{name}\n"));
367 }
368 assert!(
369 diff.is_empty(),
370 "\n`LINTS` did not contain all `Lint`s found in `PARSE_PASS_RULES`\n\
371 Please add the following to `LINTS`:\n\
372 {need_added}",
373 );
374 }
375
376 fn parse_pass_rule_names(rules: &[ParsePassRule<'_>]) -> Vec<&'static str> {
377 rules
378 .iter()
379 .filter_map(|rule| match rule {
380 ParsePassRule::DiagnosticManifest { .. }
381 | ParsePassRule::DiagnosticWorkspace { .. }
382 | ParsePassRule::DiagnosticPackage { .. } => None,
383 ParsePassRule::LintManifest { lint, .. }
384 | ParsePassRule::LintWorkspace { lint, .. }
385 | ParsePassRule::LintPackage { lint, .. } => Some(lint.name),
386 })
387 .collect()
388 }
389
390 #[test]
391 fn ensure_updated_lints() {
392 let dir = snapbox::utils::current_dir!();
393 let mut expected = HashSet::new();
394 for entry in std::fs::read_dir(&dir).unwrap() {
395 let entry = entry.unwrap();
396 let path = entry.path();
397 if path.ends_with("mod.rs") {
398 continue;
399 }
400 let content = std::fs::read_to_string(&path).unwrap();
401 if !content.contains("LINT") {
402 continue;
404 }
405 let lint_name = path.file_stem().unwrap().to_string_lossy();
406 assert!(expected.insert(lint_name.into()), "duplicate lint found");
407 }
408
409 let actual = LINTS
410 .iter()
411 .map(|l| l.name.to_string())
412 .collect::<HashSet<_>>();
413 let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
414
415 let mut need_added = String::new();
416 for name in &diff {
417 need_added.push_str(&format!("{name}\n"));
418 }
419 assert!(
420 diff.is_empty(),
421 "\n`LINTS` did not contain all `Lint`s found in {}\n\
422 Please add the following to `LINTS`:\n\
423 {need_added}",
424 dir.display(),
425 );
426 }
427
428 #[test]
429 fn ensure_updated_lint_groups() {
430 let path = snapbox::utils::current_rs!();
431 let expected = std::fs::read_to_string(&path).unwrap();
432 let expected = expected
433 .lines()
434 .filter_map(|l| {
435 if l.ends_with(": LintGroup = LintGroup {") {
436 Some(
437 l.chars()
438 .skip(6)
439 .take_while(|c| *c != ':')
440 .collect::<String>(),
441 )
442 } else {
443 None
444 }
445 })
446 .collect::<HashSet<_>>();
447 let actual = LINT_GROUPS
448 .iter()
449 .map(|l| l.name.to_uppercase())
450 .collect::<HashSet<_>>();
451 let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
452
453 let mut need_added = String::new();
454 for name in &diff {
455 need_added.push_str(&format!("{}\n", name));
456 }
457 assert!(
458 diff.is_empty(),
459 "\n`LINT_GROUPS` did not contain all `LintGroup`s found in {}\n\
460 Please add the following to `LINT_GROUPS`:\n\
461 {}",
462 path.display(),
463 need_added
464 );
465 }
466}