Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions scarb/src/bin/scarb/commands/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::<ManifestSemanticError>())
{
Expand All @@ -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::<ManifestParseError>())
Expand All @@ -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::<ManifestErrorWithSource>())
Expand All @@ -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;
};
Expand All @@ -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,
Expand Down
43 changes: 43 additions & 0 deletions scarb/src/core/manifest/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand All @@ -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")]
Expand Down
41 changes: 39 additions & 2 deletions scarb/src/core/manifest/diagnostic_kinds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions scarb/src/ops/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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![],
Expand Down
2 changes: 2 additions & 0 deletions scarb/tests/manifest_warnings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down Expand Up @@ -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");
}
Expand Down
6 changes: 4 additions & 2 deletions scarb/tests/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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());
}

Expand Down Expand Up @@ -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());
}

Expand Down Expand Up @@ -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());
}

Expand Down
Loading