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