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::core::MaybePackage;
8use crate::core::Package;
9use crate::core::Workspace;
10use crate::diagnostics::GlobalDiagnosticStats;
11use crate::diagnostics::Lint;
12use crate::diagnostics::LintLevel;
13use crate::diagnostics::LintLevelProduct;
14use crate::diagnostics::ManifestFor;
15use crate::diagnostics::PassOutput;
16use crate::diagnostics::ScopedDiagnosticStats;
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                if workspace.gctx().cli_unstable().cargo_lints {
157                    let manifest: ManifestFor<'_> = pkg.into();
158                    let level = manifest.lint_level(&cargo_lints, lint, workspace.gctx());
159                    if level.level != LintLevel::Allow {
160                        rule(
161                            workspace,
162                            manifest,
163                            &path,
164                            level,
165                            &mut pkg_stats,
166                            workspace.gctx(),
167                        )?;
168                    }
169                }
170            }
171            ParsePassRule::DiagnosticWorkspace { .. } | ParsePassRule::LintWorkspace { .. } => {}
172            ParsePassRule::DiagnosticPackage { rule } => {
173                rule(workspace, pkg, &path, &mut pkg_stats, workspace.gctx())?;
174            }
175            ParsePassRule::LintPackage { rule, lint } => {
176                if workspace.gctx().cli_unstable().cargo_lints {
177                    let level = lint.level(
178                        &cargo_lints,
179                        pkg.rust_version(),
180                        pkg.manifest().unstable_features(),
181                        workspace.gctx(),
182                    );
183
184                    if level.level != LintLevel::Allow {
185                        rule(
186                            workspace,
187                            pkg,
188                            &path,
189                            level,
190                            &mut pkg_stats,
191                            workspace.gctx(),
192                        )?;
193                    }
194                }
195            }
196        }
197    }
198
199    pkg_stats.report_summary("parse", Some(&*pkg.name()), workspace.gctx())?;
200
201    Ok(())
202}
203
204fn emit_parse_ws_diagnostics(
205    workspace: &Workspace<'_>,
206    rules: &[ParsePassRule<'_>],
207    global_stats: &mut GlobalDiagnosticStats,
208) -> CargoResult<()> {
209    let mut pkg_stats = global_stats.scope();
210
211    let cargo_lints = match workspace.root_maybe() {
212        MaybePackage::Package(pkg) => {
213            let toml = pkg.manifest().normalized_toml();
214            if let Some(ws) = &toml.workspace {
215                ws.lints.as_ref()
216            } else {
217                toml.lints.as_ref().map(|l| &l.lints)
218            }
219        }
220        MaybePackage::Virtual(vm) => vm
221            .normalized_toml()
222            .workspace
223            .as_ref()
224            .unwrap()
225            .lints
226            .as_ref(),
227    }
228    .and_then(|t| t.get("cargo"))
229    .cloned()
230    .unwrap_or(manifest::TomlToolLints::default());
231
232    for rule in rules {
233        match rule {
234            ParsePassRule::DiagnosticManifest { rule } => {
235                let manifest = (workspace, workspace.root_maybe()).into();
236                rule(
237                    workspace,
238                    manifest,
239                    workspace.root_manifest(),
240                    &mut pkg_stats,
241                    workspace.gctx(),
242                )?;
243            }
244            ParsePassRule::LintManifest { rule, lint } => {
245                if workspace.gctx().cli_unstable().cargo_lints {
246                    let manifest: ManifestFor<'_> = (workspace, workspace.root_maybe()).into();
247                    let level = manifest.lint_level(&cargo_lints, lint, workspace.gctx());
248                    if level.level != LintLevel::Allow {
249                        rule(
250                            workspace,
251                            manifest,
252                            workspace.root_manifest(),
253                            level,
254                            &mut pkg_stats,
255                            workspace.gctx(),
256                        )?;
257                    }
258                }
259            }
260            ParsePassRule::DiagnosticWorkspace { rule } => {
261                rule(
262                    workspace,
263                    workspace.root_maybe(),
264                    workspace.root_manifest(),
265                    &mut pkg_stats,
266                    workspace.gctx(),
267                )?;
268            }
269            ParsePassRule::LintWorkspace { rule, lint } => {
270                if workspace.gctx().cli_unstable().cargo_lints {
271                    let level = lint.level(
272                        &cargo_lints,
273                        workspace.lowest_rust_version(),
274                        workspace.root_maybe().unstable_features(),
275                        workspace.gctx(),
276                    );
277                    if level.level != LintLevel::Allow {
278                        rule(
279                            workspace,
280                            workspace.root_maybe(),
281                            workspace.root_manifest(),
282                            level,
283                            &mut pkg_stats,
284                            workspace.gctx(),
285                        )?;
286                    }
287                }
288            }
289            ParsePassRule::DiagnosticPackage { .. } | ParsePassRule::LintPackage { .. } => {}
290        }
291    }
292
293    pkg_stats.report_summary("parse", None, workspace.gctx())?;
294    Ok(())
295}