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}