Skip to main content

cargo/diagnostics/rules/
missing_lints_inheritance.rs

1use std::path::Path;
2
3use cargo_util_schemas::manifest::TomlToolLints;
4use cargo_util_terminal::report::Group;
5use cargo_util_terminal::report::Level;
6use cargo_util_terminal::report::Origin;
7use cargo_util_terminal::report::Patch;
8use cargo_util_terminal::report::Snippet;
9use tracing::instrument;
10
11use super::SUSPICIOUS;
12use crate::CargoResult;
13use crate::GlobalContext;
14use crate::core::Package;
15use crate::core::Workspace;
16use crate::diagnostics::DiagnosticStats;
17use crate::diagnostics::Lint;
18use crate::diagnostics::LintLevel;
19use crate::diagnostics::rel_cwd_manifest_path;
20
21pub static LINT: &Lint = &Lint {
22    name: "missing_lints_inheritance",
23    desc: "missing `[lints]` to inherit `[workspace.lints]`",
24    primary_group: &SUSPICIOUS,
25    msrv: Some(super::CARGO_LINTS_MSRV),
26    feature_gate: None,
27    docs: Some(
28        r#"
29### What it does
30
31Checks for packages without a `lints` table while `workspace.lints` is present.
32
33### Why it is bad
34
35Many people mistakenly think that `workspace.lints` is implicitly inherited when it is not.
36
37### Drawbacks
38
39### Example
40
41```toml
42[workspace.lints.cargo]
43```
44
45Should be written as:
46
47```toml
48[workspace.lints.cargo]
49
50[lints]
51workspace = true
52```
53"#,
54    ),
55};
56
57#[instrument(skip_all)]
58pub fn missing_lints_inheritance(
59    ws: &Workspace<'_>,
60    pkg: &Package,
61    manifest_path: &Path,
62    cargo_lints: &TomlToolLints,
63    stats: &mut DiagnosticStats,
64    gctx: &GlobalContext,
65) -> CargoResult<()> {
66    let (lint_level, source) = LINT.level(
67        cargo_lints,
68        pkg.rust_version(),
69        pkg.manifest().unstable_features(),
70    );
71
72    if lint_level == LintLevel::Allow {
73        return Ok(());
74    }
75
76    let root = ws.root_maybe();
77    // `normalized_toml` normally isn't guaranteed to include inheritance information except
78    // `workspace.lints` is used outside of inheritance for workspace-level lints.
79    let ws_lints = root
80        .normalized_toml()
81        .workspace
82        .as_ref()
83        .map(|ws| ws.lints.is_some())
84        .unwrap_or(false);
85    if !ws_lints {
86        return Ok(());
87    }
88    if pkg.manifest().normalized_toml().lints.is_some() {
89        return Ok(());
90    }
91
92    let manifest = pkg.manifest();
93    let contents = manifest.contents();
94    let level = lint_level.to_diagnostic_level();
95    let emitted_source = LINT.emitted_source(lint_level, source);
96    let manifest_path = rel_cwd_manifest_path(manifest_path, gctx);
97
98    let mut primary = Group::with_title(level.primary_title(LINT.desc));
99    primary = primary.element(Origin::path(&manifest_path));
100    primary = primary.element(Level::NOTE.message(emitted_source));
101    let mut report = vec![primary];
102    if let Some(contents) = contents {
103        let span = contents.len()..contents.len();
104        let mut help =
105            Group::with_title(Level::HELP.secondary_title("to inherit `workspace.lints, add:"));
106        help = help.element(
107            Snippet::source(contents)
108                .path(&manifest_path)
109                .patch(Patch::new(span.clone(), "\n[lints]\nworkspace = true")),
110        );
111        report.push(help);
112        let mut help = Group::with_title(
113            Level::HELP.secondary_title("to clarify your intent to not inherit, add:"),
114        );
115        help = help.element(
116            Snippet::source(contents)
117                .path(&manifest_path)
118                .patch(Patch::new(span, "\n[lints]")),
119        );
120        report.push(help);
121    }
122
123    stats.record_lint(lint_level);
124    gctx.shell().print_report(&report, lint_level.force())?;
125
126    Ok(())
127}