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 COMPLEXITY,
138 CORRECTNESS,
139 NURSERY,
140 PEDANTIC,
141 PERF,
142 RESTRICTION,
143 STYLE,
144 SUSPICIOUS,
145 TEST_DUMMY_UNSTABLE,
146];
147
148const COMPLEXITY: LintGroup = LintGroup {
149 name: "complexity",
150 desc: "code that does something simple but in a complex way",
151 default_level: LintLevel::Warn,
152 feature_gate: None,
153 hidden: false,
154};
155
156const CORRECTNESS: LintGroup = LintGroup {
157 name: "correctness",
158 desc: "code that is outright wrong or useless",
159 default_level: LintLevel::Deny,
160 feature_gate: None,
161 hidden: false,
162};
163
164const NURSERY: LintGroup = LintGroup {
165 name: "nursery",
166 desc: "new lints that are still under development",
167 default_level: LintLevel::Allow,
168 feature_gate: None,
169 hidden: false,
170};
171
172const PEDANTIC: LintGroup = LintGroup {
173 name: "pedantic",
174 desc: "lints which are rather strict or have occasional false positives",
175 default_level: LintLevel::Allow,
176 feature_gate: None,
177 hidden: false,
178};
179
180const PERF: LintGroup = LintGroup {
181 name: "perf",
182 desc: "code that can be written to run faster",
183 default_level: LintLevel::Warn,
184 feature_gate: None,
185 hidden: false,
186};
187
188const RESTRICTION: LintGroup = LintGroup {
189 name: "restriction",
190 desc: "lints which prevent the use of Cargo features",
191 default_level: LintLevel::Allow,
192 feature_gate: None,
193 hidden: false,
194};
195
196const STYLE: LintGroup = LintGroup {
197 name: "style",
198 desc: "code that should be written in a more idiomatic way",
199 default_level: LintLevel::Warn,
200 feature_gate: None,
201 hidden: false,
202};
203
204const SUSPICIOUS: LintGroup = LintGroup {
205 name: "suspicious",
206 desc: "code that is most likely wrong or useless",
207 default_level: LintLevel::Warn,
208 feature_gate: None,
209 hidden: false,
210};
211
212const TEST_DUMMY_UNSTABLE: LintGroup = LintGroup {
214 name: "test_dummy_unstable",
215 desc: "test_dummy_unstable is meant to only be used in tests",
216 default_level: LintLevel::Allow,
217 feature_gate: Some(crate::core::Feature::test_dummy_unstable()),
218 hidden: true,
219};
220
221fn find_lint_or_group<'a>(
222 name: &str,
223) -> Option<(&'static str, &LintLevel, &Option<&'static Feature>)> {
224 if let Some(lint) = LINTS.iter().find(|l| l.name == name) {
225 Some((
226 lint.name,
227 &lint.primary_group.default_level,
228 &lint.feature_gate,
229 ))
230 } else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == name) {
231 Some((group.name, &group.default_level, &group.feature_gate))
232 } else {
233 None
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use itertools::Itertools;
240 use snapbox::ToDebug;
241 use std::collections::HashSet;
242
243 use super::*;
244
245 #[test]
246 fn ensure_lint_groups_do_not_default_to_forbid() {
247 let forbid_groups = LINT_GROUPS
248 .iter()
249 .filter(|g| matches!(g.default_level, LintLevel::Forbid))
250 .collect::<Vec<_>>();
251
252 assert!(
253 forbid_groups.is_empty(),
254 "\n`LintGroup`s should never default to `forbid`, but the following do:\n\
255 {}\n",
256 forbid_groups.iter().map(|g| g.name).join("\n")
257 );
258 }
259
260 #[test]
261 fn ensure_sorted_lints() {
262 let location = std::panic::Location::caller();
264 println!("\nTo fix this test, sort `LINTS` in {}\n", location.file(),);
265
266 let actual = LINTS
267 .iter()
268 .map(|l| l.name.to_uppercase())
269 .collect::<Vec<_>>();
270
271 let mut expected = actual.clone();
272 expected.sort();
273 snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
274 }
275
276 #[test]
277 fn ensure_sorted_lint_groups() {
278 let location = std::panic::Location::caller();
280 println!(
281 "\nTo fix this test, sort `LINT_GROUPS` in {}\n",
282 location.file(),
283 );
284 let actual = LINT_GROUPS
285 .iter()
286 .map(|l| l.name.to_uppercase())
287 .collect::<Vec<_>>();
288
289 let mut expected = actual.clone();
290 expected.sort();
291 snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
292 }
293
294 #[test]
295 fn ensure_sorted_parse_pass_rules() {
296 let actual = parse_pass_rule_names(PARSE_PASS_RULES);
297 let mut ordered_parse_pass = PARSE_PASS_RULES.to_vec();
298 ordered_parse_pass.sort_by_key(|rule| {
299 let (lint, scope) = match rule {
300 ParsePassRule::DiagnosticManifest { .. } => {
301 let scope = 0;
302 (None, scope)
303 }
304 ParsePassRule::LintManifest { lint, .. } => {
305 let scope = 0;
306 (Some(lint), scope)
307 }
308 ParsePassRule::DiagnosticWorkspace { .. } => {
309 let scope = 1;
310 (None, scope)
311 }
312 ParsePassRule::LintWorkspace { lint, .. } => {
313 let scope = 1;
314 (Some(lint), scope)
315 }
316 ParsePassRule::DiagnosticPackage { .. } => {
317 let scope = 2;
318 (None, scope)
319 }
320 ParsePassRule::LintPackage { lint, .. } => {
321 let scope = 2;
322 (Some(lint), scope)
323 }
324 };
325 let is_lint = lint.is_some();
326 let level = lint.map(|l| std::cmp::Reverse(l.primary_group.default_level));
327 let name = lint.map(|l| l.name);
328 (is_lint, scope, level, name)
329 });
330 let expected = parse_pass_rule_names(&ordered_parse_pass);
331
332 println!("`PARSE_PASS_RULES` sort order:");
333 snapbox::assert_data_eq!(actual.join("\n"), expected.join("\n"));
334 }
335
336 #[test]
337 fn ensure_parse_passed_in_lints() {
338 let parse_pass_lint_names =
339 HashSet::from_iter(parse_pass_rule_names(PARSE_PASS_RULES).into_iter());
340 let lint_names = LINTS
341 .iter()
342 .map(|l| l.name)
343 .collect::<std::collections::HashSet<_>>();
344 let diff = parse_pass_lint_names
345 .difference(&lint_names)
346 .sorted()
347 .collect::<Vec<_>>();
348 let mut need_added = String::new();
349 for name in &diff {
350 need_added.push_str(&format!("{name}\n"));
351 }
352 assert!(
353 diff.is_empty(),
354 "\n`LINTS` did not contain all `Lint`s found in `PARSE_PASS_RULES`\n\
355 Please add the following to `LINTS`:\n\
356 {need_added}",
357 );
358 }
359
360 fn parse_pass_rule_names(rules: &[ParsePassRule<'_>]) -> Vec<&'static str> {
361 rules
362 .iter()
363 .filter_map(|rule| match rule {
364 ParsePassRule::DiagnosticManifest { .. }
365 | ParsePassRule::DiagnosticWorkspace { .. }
366 | ParsePassRule::DiagnosticPackage { .. } => None,
367 ParsePassRule::LintManifest { lint, .. }
368 | ParsePassRule::LintWorkspace { lint, .. }
369 | ParsePassRule::LintPackage { lint, .. } => Some(lint.name),
370 })
371 .collect()
372 }
373
374 #[test]
375 fn ensure_updated_lints() {
376 let dir = snapbox::utils::current_dir!();
377 let mut expected = HashSet::new();
378 for entry in std::fs::read_dir(&dir).unwrap() {
379 let entry = entry.unwrap();
380 let path = entry.path();
381 if path.ends_with("mod.rs") {
382 continue;
383 }
384 let content = std::fs::read_to_string(&path).unwrap();
385 if !content.contains("LINT") {
386 continue;
388 }
389 let lint_name = path.file_stem().unwrap().to_string_lossy();
390 assert!(expected.insert(lint_name.into()), "duplicate lint found");
391 }
392
393 let actual = LINTS
394 .iter()
395 .map(|l| l.name.to_string())
396 .collect::<HashSet<_>>();
397 let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
398
399 let mut need_added = String::new();
400 for name in &diff {
401 need_added.push_str(&format!("{name}\n"));
402 }
403 assert!(
404 diff.is_empty(),
405 "\n`LINTS` did not contain all `Lint`s found in {}\n\
406 Please add the following to `LINTS`:\n\
407 {need_added}",
408 dir.display(),
409 );
410 }
411
412 #[test]
413 fn ensure_updated_lint_groups() {
414 let path = snapbox::utils::current_rs!();
415 let expected = std::fs::read_to_string(&path).unwrap();
416 let expected = expected
417 .lines()
418 .filter_map(|l| {
419 if l.ends_with(": LintGroup = LintGroup {") {
420 Some(
421 l.chars()
422 .skip(6)
423 .take_while(|c| *c != ':')
424 .collect::<String>(),
425 )
426 } else {
427 None
428 }
429 })
430 .collect::<HashSet<_>>();
431 let actual = LINT_GROUPS
432 .iter()
433 .map(|l| l.name.to_uppercase())
434 .collect::<HashSet<_>>();
435 let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
436
437 let mut need_added = String::new();
438 for name in &diff {
439 need_added.push_str(&format!("{}\n", name));
440 }
441 assert!(
442 diff.is_empty(),
443 "\n`LINT_GROUPS` did not contain all `LintGroup`s found in {}\n\
444 Please add the following to `LINT_GROUPS`:\n\
445 {}",
446 path.display(),
447 need_added
448 );
449 }
450}