Skip to main content

cargo/diagnostics/rules/
missing_lints_inheritance.rs

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