| use std::path::Path; | |
| use cargo_util_schemas::manifest; | |
| use cargo_util_terminal::report::AnnotationKind; | |
| use cargo_util_terminal::report::Group; | |
| use cargo_util_terminal::report::Level; | |
| use cargo_util_terminal::report::Snippet; | |
| use tracing::instrument; | |
| use super::find_lint_or_group; | |
| use crate::CargoResult; | |
| use crate::GlobalContext; | |
| use crate::diagnostics::ManifestFor; | |
| use crate::diagnostics::ScopedDiagnosticStats; | |
| use crate::diagnostics::get_key_value_span; | |
| use crate::diagnostics::workspace_rel_path; | |
| use crate::workspace::Feature; | |
| use crate::workspace::MaybePackage; | |
| use crate::workspace::Workspace; | |
| #[instrument(skip_all)] | |
| pub(crate) fn diagnose_manifest( | |
| ws: &Workspace<'_>, | |
| manifest: ManifestFor<'_>, | |
| manifest_path: &Path, | |
| pkg_stats: &mut ScopedDiagnosticStats<'_>, | |
| gctx: &GlobalContext, | |
| ) -> CargoResult<()> { | |
| let normalized_toml = match &manifest { | |
| ManifestFor::Package(pkg) => pkg.manifest().normalized_toml(), | |
| ManifestFor::Workspace { | |
| maybe_pkg: MaybePackage::Virtual(vm), | |
| .. | |
| } => vm.normalized_toml(), | |
| ManifestFor::Workspace { | |
| maybe_pkg: MaybePackage::Package(_), | |
| .. | |
| } => { | |
| // For real manifests, lint as a package, rather than a workspace | |
| return Ok(()); | |
| } | |
| }; | |
| let ws_lints = normalized_toml | |
| .workspace | |
| .as_ref() | |
| .and_then(|ws| ws.lints.as_ref()) | |
| .and_then(|lints| lints.get("cargo")); | |
| let pkg_lints = normalized_toml | |
| .lints | |
| .as_ref() | |
| .map(|lints| &lints.lints) | |
| .and_then(|lints| lints.get("cargo")); | |
| if let Some(cargo_lints) = ws_lints { | |
| diagnose_manifest_inner(ws, &manifest, manifest_path, cargo_lints, pkg_stats, gctx)?; | |
| } | |
| if let Some(cargo_lints) = pkg_lints { | |
| diagnose_manifest_inner(ws, &manifest, manifest_path, cargo_lints, pkg_stats, gctx)?; | |
| } | |
| Ok(()) | |
| } | |
| fn diagnose_manifest_inner( | |
| ws: &Workspace<'_>, | |
| manifest: &ManifestFor<'_>, | |
| manifest_path: &Path, | |
| cargo_lints: &manifest::TomlToolLints, | |
| pkg_stats: &mut ScopedDiagnosticStats<'_>, | |
| gctx: &GlobalContext, | |
| ) -> CargoResult<()> { | |
| let manifest_path = workspace_rel_path(ws, manifest_path); | |
| for lint_name in cargo_lints.keys().map(|name| name) { | |
| let Some((name, default_level, feature_gate)) = find_lint_or_group(lint_name) else { | |
| continue; | |
| }; | |
| let (_, source, _) = | |
| crate::diagnostics::lint::level_priority(name, *default_level, cargo_lints); | |
| // Only run analysis on user-specified lints | |
| if !source.is_user_specified() { | |
| continue; | |
| } | |
| // Only run this on lints that are gated by a feature | |
| if let Some(feature_gate) = feature_gate | |
| && !manifest.unstable_features().is_enabled(feature_gate) | |
| { | |
| report_feature_not_enabled( | |
| name, | |
| feature_gate, | |
| &manifest, | |
| &manifest_path, | |
| pkg_stats, | |
| gctx, | |
| )?; | |
| } | |
| } | |
| Ok(()) | |
| } | |
| fn report_feature_not_enabled( | |
| lint_name: &str, | |
| feature_gate: &Feature, | |
| manifest: &ManifestFor<'_>, | |
| manifest_path: &str, | |
| pkg_stats: &mut ScopedDiagnosticStats<'_>, | |
| gctx: &GlobalContext, | |
| ) -> CargoResult<()> { | |
| let dash_feature_name = feature_gate.name().replace("_", "-"); | |
| let mut error = Group::with_title( | |
| Level::ERROR.primary_title(format!("use of unstable lint `{lint_name}`")), | |
| ); | |
| if let Some(document) = manifest.document() | |
| && let Some(contents) = manifest.contents() | |
| { | |
| let key_path = match manifest { | |
| ManifestFor::Package(_) => &["lints", "cargo", lint_name][..], | |
| ManifestFor::Workspace { .. } => &["workspace", "lints", "cargo", lint_name][..], | |
| }; | |
| let Some(span) = get_key_value_span(document, key_path) else { | |
| // This lint is handled by either package or workspace lint. | |
| return Ok(()); | |
| }; | |
| error = error.element(Snippet::source(contents).path(manifest_path).annotation( | |
| AnnotationKind::Primary.span(span.key).label(format!( | |
| "this is behind `{dash_feature_name}`, which is not enabled" | |
| )), | |
| )) | |
| } | |
| let report = [error.element(Level::HELP.message(format!( | |
| "consider adding `cargo-features = [\"{dash_feature_name}\"]` to the top of the manifest" | |
| )))]; | |
| pkg_stats.record_error(); | |
| gctx.shell().print_report(&report, true)?; | |
| Ok(()) | |
| } |