diff --git a/scarb/src/bin/scarb/commands/metadata.rs b/scarb/src/bin/scarb/commands/metadata.rs index fa2dd49dd..b3b98d483 100644 --- a/scarb/src/bin/scarb/commands/metadata.rs +++ b/scarb/src/bin/scarb/commands/metadata.rs @@ -5,7 +5,8 @@ use toml::de::Error as TomlParseError; use scarb::core::Config; use scarb::core::errors::{ManifestErrorWithSource, ManifestParseError}; use scarb::core::{ - ManifestDiagnosticMessage, ManifestDiagnosticSpan, ManifestMessageKind, ManifestSemanticError, + ManifestDiagnosticCode, ManifestDiagnosticMessage, ManifestDiagnosticSpan, ManifestMessageKind, + ManifestSemanticError, }; use scarb::ops; use scarb_ui::OutputFormat; @@ -42,7 +43,7 @@ pub fn run(args: MetadataArgs, config: &Config) -> Result<()> { } fn emit_manifest_diagnostic(config: &Config, error: &anyhow::Error) { - let (file, message, span, related) = if let Some(sem) = error + let (file, message, error_code, span, related) = if let Some(sem) = error .chain() .find_map(|c| c.downcast_ref::()) { @@ -59,7 +60,7 @@ fn emit_manifest_diagnostic(config: &Config, error: &anyhow::Error) { }) .unwrap_or_default(); let file = src.map(|src| src.path.to_string()); - (file, sem.to_string(), span, related) + (file, sem.to_string(), sem.code(), span, related) } else if let Some(parse_err) = error .chain() .find_map(|c| c.downcast_ref::()) @@ -80,7 +81,13 @@ fn emit_manifest_diagnostic(config: &Config, error: &anyhow::Error) { start: s.start, end: s.end, }); - (Some(parse_err.path().to_string()), message, span, vec![]) + ( + Some(parse_err.path().to_string()), + message, + ManifestDiagnosticCode::ParseError, + span, + vec![], + ) } else if let Some(src) = error .chain() .find_map(|c| c.downcast_ref::()) @@ -89,7 +96,13 @@ fn emit_manifest_diagnostic(config: &Config, error: &anyhow::Error) { .source() .map(|err| err.to_string()) .unwrap_or_else(|| error.to_string()); - (Some(src.path.to_string()), message, None, vec![]) + ( + Some(src.path.to_string()), + message, + ManifestDiagnosticCode::Other, + None, + vec![], + ) } else { return; }; @@ -99,6 +112,7 @@ fn emit_manifest_diagnostic(config: &Config, error: &anyhow::Error) { .force_print(MachineMessage(ManifestDiagnosticMessage { kind: ManifestMessageKind::ManifestDiagnostic, message, + error_code, file, span, related, diff --git a/scarb/src/core/manifest/diagnostic.rs b/scarb/src/core/manifest/diagnostic.rs index d9ccef0ef..aecaf45a3 100644 --- a/scarb/src/core/manifest/diagnostic.rs +++ b/scarb/src/core/manifest/diagnostic.rs @@ -11,6 +11,7 @@ use toml_edit::{Document, Item, Table}; pub struct ManifestDiagnosticMessage { pub kind: ManifestMessageKind, pub message: String, + pub error_code: ManifestDiagnosticCode, #[serde(skip_serializing_if = "Option::is_none")] pub file: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -25,6 +26,48 @@ pub enum ManifestMessageKind { ManifestDiagnostic, } +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +pub enum ManifestDiagnosticCode { + #[serde(rename = "SE0001")] + ParseError, + #[serde(rename = "SE0002")] + UnknownField, + #[serde(rename = "SE0003")] + ProfileNameInvalid, + #[serde(rename = "SE0004")] + ProfileInheritanceInvalid, + #[serde(rename = "SE0005")] + CairoInliningStrategyConflict, + #[serde(rename = "SE0006")] + DependencyWorkspaceNotFound, + #[serde(rename = "SE0007")] + DependencyGitRefWithoutGit, + #[serde(rename = "SE0008")] + DependencyGitReferenceAmbiguous, + #[serde(rename = "SE0009")] + DependencySourceMissing, + #[serde(rename = "SE0010")] + DependencyGitPathAmbiguous, + #[serde(rename = "SE0011")] + DependencyGitRegistryAmbiguous, + #[serde(rename = "SE0012")] + PatchNotInWorkspaceRoot, + #[serde(rename = "SE0013")] + PatchSourceConflict, + #[serde(rename = "SE0014")] + PatchSourceInvalidUrl, + #[serde(rename = "SE0015")] + ReadmePathInvalid, + #[serde(rename = "SE0016")] + LicensePathInvalid, + #[serde(rename = "SE0017")] + DuplicateDefaultTargetDefinition, + #[serde(rename = "SE0018")] + DuplicateNamedTargetDefinition, + #[serde(rename = "SE0019")] + Other, +} + #[derive(Debug, Clone, Serialize)] pub struct ManifestDiagnosticData { #[serde(skip_serializing_if = "Option::is_none")] diff --git a/scarb/src/core/manifest/diagnostic_kinds.rs b/scarb/src/core/manifest/diagnostic_kinds.rs index b5f06e271..d4c99cf6b 100644 --- a/scarb/src/core/manifest/diagnostic_kinds.rs +++ b/scarb/src/core/manifest/diagnostic_kinds.rs @@ -8,8 +8,8 @@ use url::ParseError as UrlParseError; use super::ManifestDiagnosticData; use super::diagnostic::resolve_anchor_in_doc; use super::{ - ManifestDependencyTable, ManifestDiagnosticAnchor, ManifestRelatedAnchor, - ManifestRelatedLocation, + ManifestDependencyTable, ManifestDiagnosticAnchor, ManifestDiagnosticCode, + ManifestRelatedAnchor, ManifestRelatedLocation, }; /// Typed manifest validation errors that carry semantic anchors for diagnostic span resolution. @@ -54,6 +54,43 @@ pub enum ManifestSemanticError { } impl ManifestSemanticError { + pub fn code(&self) -> ManifestDiagnosticCode { + match self { + Self::ProfileNameInvalid(_) => ManifestDiagnosticCode::ProfileNameInvalid, + Self::ProfileInheritanceInvalid(_) => ManifestDiagnosticCode::ProfileInheritanceInvalid, + Self::CairoInliningStrategyConflict(_) => { + ManifestDiagnosticCode::CairoInliningStrategyConflict + } + Self::DependencyWorkspaceNotFound(_) => { + ManifestDiagnosticCode::DependencyWorkspaceNotFound + } + Self::DependencyGitRefWithoutGit(_) => { + ManifestDiagnosticCode::DependencyGitRefWithoutGit + } + Self::DependencyGitReferenceAmbiguous(_) => { + ManifestDiagnosticCode::DependencyGitReferenceAmbiguous + } + Self::DependencySourceMissing(_) => ManifestDiagnosticCode::DependencySourceMissing, + Self::DependencyGitPathAmbiguous(_) => { + ManifestDiagnosticCode::DependencyGitPathAmbiguous + } + Self::DependencyGitRegistryAmbiguous(_) => { + ManifestDiagnosticCode::DependencyGitRegistryAmbiguous + } + Self::PatchNotInWorkspaceRoot(_) => ManifestDiagnosticCode::PatchNotInWorkspaceRoot, + Self::PatchSourceConflict(_) => ManifestDiagnosticCode::PatchSourceConflict, + Self::PatchSourceInvalidUrl(_) => ManifestDiagnosticCode::PatchSourceInvalidUrl, + Self::ReadmePathInvalid(_) => ManifestDiagnosticCode::ReadmePathInvalid, + Self::LicensePathInvalid(_) => ManifestDiagnosticCode::LicensePathInvalid, + Self::DuplicateDefaultTargetDefinition(_) => { + ManifestDiagnosticCode::DuplicateDefaultTargetDefinition + } + Self::DuplicateNamedTargetDefinition(_) => { + ManifestDiagnosticCode::DuplicateNamedTargetDefinition + } + } + } + /// Resolves this error's anchor(s) to byte spans using the parsed manifest root table. pub fn resolve(&self, root: &Table) -> ManifestDiagnosticData { let span = self diff --git a/scarb/src/ops/workspace.rs b/scarb/src/ops/workspace.rs index 645c447cc..8f252b27b 100644 --- a/scarb/src/ops/workspace.rs +++ b/scarb/src/ops/workspace.rs @@ -14,8 +14,8 @@ use crate::core::TomlManifest; use crate::core::config::Config; use crate::core::errors::{ManifestErrorWithSource, ManifestParseError}; use crate::core::manifest::{ - ManifestDiagnosticAnchor, ManifestDiagnosticMessage, ManifestDiagnosticSpan, - ManifestMessageKind, resolve_manifest_anchor, + ManifestDiagnosticAnchor, ManifestDiagnosticCode, ManifestDiagnosticMessage, + ManifestDiagnosticSpan, ManifestMessageKind, resolve_manifest_anchor, }; use crate::core::package::Package; use crate::core::source::SourceId; @@ -215,6 +215,7 @@ fn warn_unknown_manifest_fields(path: &Utf8Path, source: &str, config: &Config) .force_print(MachineMessage(ManifestDiagnosticMessage { kind: ManifestMessageKind::ManifestDiagnostic, message: format!("unknown manifest field `{path_str}`"), + error_code: ManifestDiagnosticCode::UnknownField, file: Some(path.to_string()), span, related: vec![], diff --git a/scarb/tests/manifest_warnings.rs b/scarb/tests/manifest_warnings.rs index 305b0a4f4..974209f20 100644 --- a/scarb/tests/manifest_warnings.rs +++ b/scarb/tests/manifest_warnings.rs @@ -275,6 +275,7 @@ fn json_mode_emits_manifest_diagnostic_for_unknown_top_level_section() { "unexpected message: {}", diag["message"] ); + assert_eq!(diag["error_code"].as_str().unwrap(), "SE0002"); assert!(diag["file"].is_string(), "expected file field"); assert!(diag["span"].is_object(), "expected span field"); assert!( @@ -317,6 +318,7 @@ fn json_mode_emits_manifest_diagnostic_for_unknown_package_field() { "unexpected message: {}", diag["message"] ); + assert_eq!(diag["error_code"].as_str().unwrap(), "SE0002"); assert!(diag["file"].is_string(), "expected file field"); assert!(diag["span"].is_object(), "expected span field"); } diff --git a/scarb/tests/metadata.rs b/scarb/tests/metadata.rs index f5980b3ae..e2f64b72f 100644 --- a/scarb/tests/metadata.rs +++ b/scarb/tests/metadata.rs @@ -149,6 +149,7 @@ fn emits_manifest_diagnostic_ndjson_for_invalid_manifest_in_json_mode() { invalid type: integer `1`, expected a string "#} ); + assert_eq!(diagnostic["error_code"].as_str().unwrap(), "SE0001"); assert!(diagnostic["span"].is_object()); assert!( diagnostic.get("severity").is_none(), @@ -195,6 +196,7 @@ fn attributes_manifest_diagnostic_to_workspace_member_manifest() { diagnostic["file"].as_str().unwrap(), expected_path.to_str().unwrap() ); + assert_eq!(diagnostic["error_code"].as_str().unwrap(), "SE0001"); assert!(diagnostic["span"].is_object()); } @@ -239,7 +241,7 @@ fn emits_manifest_diagnostic_for_semantic_manifest_error_with_span() { diagnostic["message"].as_str().unwrap(), "profile name `test` is not allowed" ); - assert!(diagnostic.get("code").is_none()); + assert_eq!(diagnostic["error_code"].as_str().unwrap(), "SE0003"); assert!(diagnostic["span"].is_object()); } @@ -285,7 +287,7 @@ fn emits_manifest_diagnostic_for_semantic_manifest_error_without_span() { diagnostic["message"].as_str().unwrap(), "error inheriting `hello` from workspace root manifest's `workspace.scripts.hello`" ); - assert!(diagnostic.get("code").is_none()); + assert_eq!(diagnostic["error_code"].as_str().unwrap(), "SE0019"); assert!(diagnostic.get("span").is_none()); }