Skip to main content

cargo/diagnostics/
passes.rs

1use std::path::Path;
2
3use cargo_util_schemas::manifest;
4
5use crate::CargoResult;
6use crate::GlobalContext;
7use crate::diagnostics::GlobalDiagnosticStats;
8use crate::diagnostics::Lint;
9use crate::diagnostics::LintLevel;
10use crate::diagnostics::LintLevelProduct;
11use crate::diagnostics::ManifestFor;
12use crate::diagnostics::PassOutput;
13use crate::diagnostics::ScopedDiagnosticStats;
14use crate::workspace::MaybePackage;
15use crate::workspace::Package;
16use crate::workspace::Workspace;
17
18#[derive(Clone)]
19pub enum ParsePassRule<'r> {
20    DiagnosticManifest {
21        rule: FnDiagnosticManifest,
22    },
23    LintManifest {
24        rule: FnLintManifest,
25        lint: &'r Lint,
26    },
27    DiagnosticWorkspace {
28        rule: FnDiagnosticWorkspace,
29    },
30    LintWorkspace {
31        rule: FnLintWorkspace,
32        lint: &'r Lint,
33    },
34    DiagnosticPackage {
35        rule: FnDiagnosticPackage,
36    },
37    LintPackage {
38        rule: FnLintPackage,
39        lint: &'r Lint,
40    },
41}
42
43type FnDiagnosticManifest = fn(
44    &Workspace<'_>,
45    ManifestFor<'_>,
46    &Path,
47    &mut ScopedDiagnosticStats<'_>,
48    &GlobalContext,
49) -> CargoResult<()>;
50
51type FnDiagnosticWorkspace = fn(
52    &Workspace<'_>,
53    &MaybePackage,
54    &Path,
55    &mut ScopedDiagnosticStats<'_>,
56    &GlobalContext,
57) -> CargoResult<()>;
58
59type FnDiagnosticPackage = fn(
60    &Workspace<'_>,
61    &Package,
62    &Path,
63    &mut ScopedDiagnosticStats<'_>,
64    &GlobalContext,
65) -> CargoResult<()>;
66
67type FnLintManifest = fn(
68    &Workspace<'_>,
69    manifest: ManifestFor<'_>,
70    manifest_path: &Path,
71    LintLevelProduct,
72    stats: &mut ScopedDiagnosticStats<'_>,
73    gctx: &GlobalContext,
74) -> CargoResult<()>;
75
76type FnLintWorkspace = fn(
77    &Workspace<'_>,
78    &MaybePackage,
79    &Path,
80    LintLevelProduct,
81    &mut ScopedDiagnosticStats<'_>,
82    &GlobalContext,
83) -> CargoResult<()>;
84
85type FnLintPackage = fn(
86    &Workspace<'_>,
87    &Package,
88    &Path,
89    LintLevelProduct,
90    &mut ScopedDiagnosticStats<'_>,
91    &GlobalContext,
92) -> CargoResult<()>;
93
94pub fn emit_parse_diagnostics(
95    workspace: &Workspace<'_>,
96    rules: &[ParsePassRule<'_>],
97) -> CargoResult<PassOutput> {
98    let mut stats = GlobalDiagnosticStats::new();
99
100    if is_local_workspace(workspace) {
101        emit_parse_ws_diagnostics(workspace, rules, &mut stats)?;
102    }
103
104    for maybe_pkg in workspace.loaded_maybe() {
105        if let MaybePackage::Package(pkg) = maybe_pkg {
106            if is_local_package(pkg) {
107                let path = pkg.manifest_path();
108                emit_parse_pkg_diagnostics(workspace, pkg, &path, rules, &mut stats)?;
109            }
110        }
111    }
112
113    stats.ok()
114}
115
116fn is_local_workspace(workspace: &Workspace<'_>) -> bool {
117    workspace
118        .root_maybe()
119        .as_package()
120        .map(is_local_package)
121        .unwrap_or_else(|| workspace.members().any(is_local_package))
122}
123
124fn is_local_package(pkg: &Package) -> bool {
125    pkg.package_id().source_id().is_path()
126}
127
128fn emit_parse_pkg_diagnostics(
129    workspace: &Workspace<'_>,
130    pkg: &Package,
131    path: &Path,
132    rules: &[ParsePassRule<'_>],
133    global_stats: &mut GlobalDiagnosticStats,
134) -> CargoResult<()> {
135    let mut pkg_stats = global_stats.scope();
136
137    let toml_lints = pkg
138        .manifest()
139        .normalized_toml()
140        .lints
141        .clone()
142        .map(|lints| lints.lints)
143        .unwrap_or(manifest::TomlLints::default());
144    let cargo_lints = toml_lints
145        .get("cargo")
146        .cloned()
147        .unwrap_or(manifest::TomlToolLints::default());
148
149    for rule in rules {
150        match rule {
151            ParsePassRule::DiagnosticManifest { rule } => {
152                let manifest = pkg.into();
153                rule(workspace, manifest, &path, &mut pkg_stats, workspace.gctx())?;
154            }
155            ParsePassRule::LintManifest { rule, lint } => {
156                let manifest: ManifestFor<'_> = pkg.into();
157                let level = manifest.lint_level(&cargo_lints, lint, workspace.gctx());
158                if level.level != LintLevel::Allow {
159                    rule(
160                        workspace,
161                        manifest,
162                        &path,
163                        level,
164                        &mut pkg_stats,
165                        workspace.gctx(),
166                    )?;
167                }
168            }
169            ParsePassRule::DiagnosticWorkspace { .. } | ParsePassRule::LintWorkspace { .. } => {}
170            ParsePassRule::DiagnosticPackage { rule } => {
171                rule(workspace, pkg, &path, &mut pkg_stats, workspace.gctx())?;
172            }
173            ParsePassRule::LintPackage { rule, lint } => {
174                let level = lint.level(
175                    &cargo_lints,
176                    pkg.rust_version(),
177                    pkg.manifest().unstable_features(),
178                    workspace.gctx(),
179                );
180
181                if level.level != LintLevel::Allow {
182                    rule(
183                        workspace,
184                        pkg,
185                        &path,
186                        level,
187                        &mut pkg_stats,
188                        workspace.gctx(),
189                    )?;
190                }
191            }
192        }
193    }
194
195    pkg_stats.report_summary("parse", Some(&*pkg.name()), workspace.gctx())?;
196
197    Ok(())
198}
199
200fn emit_parse_ws_diagnostics(
201    workspace: &Workspace<'_>,
202    rules: &[ParsePassRule<'_>],
203    global_stats: &mut GlobalDiagnosticStats,
204) -> CargoResult<()> {
205    let mut pkg_stats = global_stats.scope();
206
207    let cargo_lints = match workspace.root_maybe() {
208        MaybePackage::Package(pkg) => {
209            let toml = pkg.manifest().normalized_toml();
210            if let Some(ws) = &toml.workspace {
211                ws.lints.as_ref()
212            } else {
213                toml.lints.as_ref().map(|l| &l.lints)
214            }
215        }
216        MaybePackage::Virtual(vm) => vm
217            .normalized_toml()
218            .workspace
219            .as_ref()
220            .unwrap()
221            .lints
222            .as_ref(),
223    }
224    .and_then(|t| t.get("cargo"))
225    .cloned()
226    .unwrap_or(manifest::TomlToolLints::default());
227
228    for rule in rules {
229        match rule {
230            ParsePassRule::DiagnosticManifest { rule } => {
231                let manifest = (workspace, workspace.root_maybe()).into();
232                rule(
233                    workspace,
234                    manifest,
235                    workspace.root_manifest(),
236                    &mut pkg_stats,
237                    workspace.gctx(),
238                )?;
239            }
240            ParsePassRule::LintManifest { rule, lint } => {
241                let manifest: ManifestFor<'_> = (workspace, workspace.root_maybe()).into();
242                let level = manifest.lint_level(&cargo_lints, lint, workspace.gctx());
243                if level.level != LintLevel::Allow {
244                    rule(
245                        workspace,
246                        manifest,
247                        workspace.root_manifest(),
248                        level,
249                        &mut pkg_stats,
250                        workspace.gctx(),
251                    )?;
252                }
253            }
254            ParsePassRule::DiagnosticWorkspace { rule } => {
255                rule(
256                    workspace,
257                    workspace.root_maybe(),
258                    workspace.root_manifest(),
259                    &mut pkg_stats,
260                    workspace.gctx(),
261                )?;
262            }
263            ParsePassRule::LintWorkspace { rule, lint } => {
264                let level = lint.level(
265                    &cargo_lints,
266                    workspace.lowest_rust_version(),
267                    workspace.root_maybe().unstable_features(),
268                    workspace.gctx(),
269                );
270                if level.level != LintLevel::Allow {
271                    rule(
272                        workspace,
273                        workspace.root_maybe(),
274                        workspace.root_manifest(),
275                        level,
276                        &mut pkg_stats,
277                        workspace.gctx(),
278                    )?;
279                }
280            }
281            ParsePassRule::DiagnosticPackage { .. } | ParsePassRule::LintPackage { .. } => {}
282        }
283    }
284
285    pkg_stats.report_summary("parse", None, workspace.gctx())?;
286    Ok(())
287}