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}