diff --git a/CHANGELOG.md b/CHANGELOG.md index d370d5e..3df3306 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixed + +- `check --format json` names the rule of the context-stage findings and of four more semantic findings, where `rule` was null (follow-up to #10). The context stage sets `Diagnostic::code` for its rule-named findings, names the owning-type rules of an `actor`, `stakeholder`, `require`/`assume`, `verify`, `entry`/`do`/`exit` or `render` member in the wrong body (`validate…MembershipOwningType`) and the name clash (`validateNamespaceDistinguishibility`); the semantic stage names `validateMetadataFeatureMetaclass`, `validateMultiplicityRangeBoundResultTypes`, `validateConnectorBinarySpecialization` and the two invocation-argument rules. The messages of the newly named context findings start with the rule, as the others do; severities do not change. + ## v0.11.3 ### Added diff --git a/crates/sysmlv2-cli/tests/cli.rs b/crates/sysmlv2-cli/tests/cli.rs index ade6353..c9ad400 100644 --- a/crates/sysmlv2-cli/tests/cli.rs +++ b/crates/sysmlv2-cli/tests/cli.rs @@ -3443,6 +3443,73 @@ fn check_format_json_names_the_rule_a_semantic_finding_reports() { let _ = fs::remove_dir_all(&dir); } +#[test] +fn check_format_json_names_the_rule_of_every_stage() { + let dir = std::env::temp_dir().join(format!( + "sysmlv2-cli-test-check-json-rules-{}", + std::process::id() + )); + fs::create_dir_all(&dir).unwrap(); + let model = dir.join("model.sysml"); + fs::write( + &model, + "package P { + requirement def R { subject a; subject b; } + calc def C { actor x; return r; } + part def D { part y; part y; } + part def K; + part k { @K; } + attribute n = 1.5; + part w[n]; + calc def F { in x; return r; } + attribute z = F(x = 1, x = 2); +} +", + ) + .unwrap(); + + let out = sysmlv2(&["check", "--format", "json", model.to_str().unwrap()]); + assert!(!out.status.success()); + let doc: serde_json::Value = serde_json::from_str(&stdout(&out)).expect("stdout is JSON"); + let rules: Vec<(&str, &str, &str)> = doc["findings"] + .as_array() + .unwrap() + .iter() + .map(|f| { + ( + f["stage"].as_str().unwrap(), + f["severity"].as_str().unwrap(), + f["rule"].as_str().unwrap_or("null"), + ) + }) + .collect(); + assert_eq!( + rules, + [ + ( + "context", + "error", + "validateRequirementDefinitionOnlyOneSubject" + ), + ("context", "error", "validateActorMembershipOwningType"), + ("context", "error", "validateNamespaceDistinguishibility"), + ("semantic", "error", "validateMetadataFeatureMetaclass"), + ( + "semantic", + "error", + "validateMultiplicityRangeBoundResultTypes" + ), + ( + "semantic", + "warn", + "validateInvocationExpressionNoDuplicateParameterRedefinition" + ), + ], + "{doc}" + ); + let _ = fs::remove_dir_all(&dir); +} + #[test] fn lint_format_json_locates_findings_past_a_parse_failure() { let dir = std::env::temp_dir().join(format!( diff --git a/crates/sysmlv2-model/src/check.rs b/crates/sysmlv2-model/src/check.rs index da39904..b01bdd7 100644 --- a/crates/sysmlv2-model/src/check.rs +++ b/crates/sysmlv2-model/src/check.rs @@ -64,6 +64,7 @@ pub const IMPLEMENTED_NORMATIVE_SEMANTIC_RULES: &[&str] = &[ "validateCaseDefinitionOnlyOneObjective", "validateCaseUsageOnlyOneObjective", "validateClassSpecialization", + "validateConnectorBinarySpecialization", "validateConnectorRelatedFeatures", "validateConstructorExpressionNoDuplicateFeatureRedefinition", "validateControlNodeIncomingSuccessions", @@ -97,11 +98,15 @@ pub const IMPLEMENTED_NORMATIVE_SEMANTIC_RULES: &[&str] = &[ "validateIncludeUseCaseUsageReference", "validateInstantiationExpressionInstantiatedType", "validateInvocationExpressionInstantiatedType", + "validateInvocationExpressionNoDuplicateParameterRedefinition", + "validateInvocationExpressionParameterRedefinition", "validateJoinNodeOutgoingSuccessions", "validateMergeNodeIncomingSuccessions", "validateMergeNodeOutgoingSuccessions", "validateMetadataFeatureAnnotatedElement", "validateMetadataFeatureBody", + "validateMetadataFeatureMetaclass", + "validateMultiplicityRangeBoundResultTypes", "validateNamespaceDistinguishibility", "validateOccurrenceUsageIndividualDefinition", "validateOccurrenceUsageIndividualUsage", diff --git a/crates/sysmlv2-model/src/check/invocations.rs b/crates/sysmlv2-model/src/check/invocations.rs index 5378025..30311b0 100644 --- a/crates/sysmlv2-model/src/check/invocations.rs +++ b/crates/sysmlv2-model/src/check/invocations.rs @@ -71,10 +71,14 @@ pub(super) fn validate(r: &mut ResolvedModel, model: &Model) -> Vec<(usize, Diag Some(name) => name.to_display_string(), None => { let Some(name) = params.get(positional) else { - out.push((unit, Diagnostic::warning(span, format!( - "invocation of `{}` supplies {} arguments for {} parameter(s)", - qn.to_display_string(), args.len(), params.len() - )))); + out.push((unit, super::rule_warning( + span, + "validateInvocationExpressionParameterRedefinition", + format!( + "invocation of `{}` supplies {} arguments for {} parameter(s)", + qn.to_display_string(), args.len(), params.len() + ), + ))); invalid = true; break; }; @@ -83,20 +87,26 @@ pub(super) fn validate(r: &mut ResolvedModel, model: &Model) -> Vec<(usize, Diag } }; let message = if !params.contains(&name) { - Some(format!( - "invocation of `{}` names unknown parameter `{name}`", - qn.to_display_string() + Some(( + "validateInvocationExpressionParameterRedefinition", + format!( + "invocation of `{}` names unknown parameter `{name}`", + qn.to_display_string() + ), )) } else if !bound.insert(name.clone()) { - Some(format!( - "invocation of `{}` binds parameter `{name}` more than once", - qn.to_display_string() + Some(( + "validateInvocationExpressionNoDuplicateParameterRedefinition", + format!( + "invocation of `{}` binds parameter `{name}` more than once", + qn.to_display_string() + ), )) } else { None }; - if let Some(message) = message { - out.push((unit, Diagnostic::warning(arg.value.span, message))); + if let Some((rule, message)) = message { + out.push((unit, super::rule_warning(arg.value.span, rule, message))); invalid = true; } } diff --git a/crates/sysmlv2-model/src/check/multiplicities.rs b/crates/sysmlv2-model/src/check/multiplicities.rs index 1d5d8e9..afa19fb 100644 --- a/crates/sysmlv2-model/src/check/multiplicities.rs +++ b/crates/sysmlv2-model/src/check/multiplicities.rs @@ -89,8 +89,9 @@ pub(super) fn validate(r: &mut ResolvedModel, model: &Model) -> Vec<(usize, Diag if invalid { out.push(( r.b.unit_of_elem(owner), - Diagnostic::error( + super::rule_error( span, + "validateMultiplicityRangeBoundResultTypes", "multiplicity bound must be a Natural number (or `*` for the upper bound)", ), )); diff --git a/crates/sysmlv2-model/src/check/specialization.rs b/crates/sysmlv2-model/src/check/specialization.rs index 5342709..e81d43a 100644 --- a/crates/sysmlv2-model/src/check/specialization.rs +++ b/crates/sysmlv2-model/src/check/specialization.rs @@ -128,8 +128,9 @@ pub(super) fn validate(r: &mut ResolvedModel, model: &Model) -> Vec<(usize, Diag if !matches!(target_ty, "MetadataDefinition" | "Metaclass") { out.push(( unit, - Diagnostic::error( + super::rule_error( qn.span, + "validateMetadataFeatureMetaclass", format!( "metadata must be typed by a metadata definition or \ metaclass; `{}` is a {}", @@ -398,8 +399,9 @@ pub(super) fn validate(r: &mut ResolvedModel, model: &Model) -> Vec<(usize, Diag let ends = end_count(&r.b, *owner); out.push(( r.b.unit_of_elem(*owner), - Diagnostic::warning( + super::rule_warning( qn.span, + "validateConnectorBinarySpecialization", format!( "connector with {ends} ends specializes a binary connector (a binary connector cannot have more than two ends)" ), diff --git a/crates/sysmlv2-parser/tests/fixtures/opensysml/baseline-library.json b/crates/sysmlv2-parser/tests/fixtures/opensysml/baseline-library.json index b0e256d..453c3fd 100644 --- a/crates/sysmlv2-parser/tests/fixtures/opensysml/baseline-library.json +++ b/crates/sysmlv2-parser/tests/fixtures/opensysml/baseline-library.json @@ -94,7 +94,7 @@ "diagnostics": [ { "end": 356, - "message": "a `require` constraint member is not allowed in a definition or usage body", + "message": "validateRequirementConstraintMembershipOwningType: a `require` constraint member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 329 @@ -185,7 +185,7 @@ "diagnostics": [ { "end": 231, - "message": "an `entry` action member is not allowed in a definition or usage body", + "message": "validateStateSubactionMembershipOwningType: an `entry` action member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 223 @@ -204,7 +204,7 @@ "diagnostics": [ { "end": 219, - "message": "a `do` action member is not allowed in a definition or usage body", + "message": "validateStateSubactionMembershipOwningType: a `do` action member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 214 @@ -931,7 +931,7 @@ "diagnostics": [ { "end": 313, - "message": "an `actor` member is not allowed in a definition or usage body", + "message": "validateActorMembershipOwningType: an `actor` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 289 @@ -967,7 +967,7 @@ "diagnostics": [ { "end": 333, - "message": "a `stakeholder` member is not allowed in a definition or usage body", + "message": "validateStakeholderMembershipOwningType: a `stakeholder` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 306 @@ -979,7 +979,7 @@ "diagnostics": [ { "end": 268, - "message": "an `entry` action member is not allowed in a definition or usage body", + "message": "validateStateSubactionMembershipOwningType: an `entry` action member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 250 @@ -991,7 +991,7 @@ "diagnostics": [ { "end": 312, - "message": "a `render` member is not allowed in a definition or usage body", + "message": "validateViewRenderingMembershipOwningType: a `render` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 285 @@ -1725,7 +1725,7 @@ "diagnostics": [ { "end": 335, - "message": "connector with 3 ends specializes a binary connector (a binary connector cannot have more than two ends)", + "message": "connector with 3 ends specializes a binary connector (a binary connector cannot have more than two ends) [validateConnectorBinarySpecialization]", "severity": "Warning", "stage": "semantics", "start": 318 @@ -1797,7 +1797,7 @@ "diagnostics": [ { "end": 353, - "message": "invocation of `F` supplies 2 arguments for 1 parameter(s)", + "message": "invocation of `F` supplies 2 arguments for 1 parameter(s) [validateInvocationExpressionParameterRedefinition]", "severity": "Warning", "stage": "semantics", "start": 346 @@ -1809,7 +1809,7 @@ "diagnostics": [ { "end": 351, - "message": "invocation of `F` binds parameter `x` more than once", + "message": "invocation of `F` binds parameter `x` more than once [validateInvocationExpressionNoDuplicateParameterRedefinition]", "severity": "Warning", "stage": "semantics", "start": 350 @@ -1893,7 +1893,7 @@ "diagnostics": [ { "end": 404, - "message": "metadata must be typed by a metadata definition or metaclass; `M` is a Class", + "message": "metadata must be typed by a metadata definition or metaclass; `M` is a Class [validateMetadataFeatureMetaclass]", "severity": "Error", "stage": "semantics", "start": 403 @@ -2585,7 +2585,7 @@ "diagnostics": [ { "end": 203, - "message": "metadata must be typed by a metadata definition or metaclass; `PD` is a PartDefinition", + "message": "metadata must be typed by a metadata definition or metaclass; `PD` is a PartDefinition [validateMetadataFeatureMetaclass]", "severity": "Error", "stage": "semantics", "start": 201 @@ -3636,7 +3636,7 @@ "diagnostics": [ { "end": 213, - "message": "an `actor` member is not allowed in a definition or usage body", + "message": "validateActorMembershipOwningType: an `actor` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 201 @@ -4026,7 +4026,7 @@ "diagnostics": [ { "end": 216, - "message": "connector with 3 ends specializes a binary connector (a binary connector cannot have more than two ends)", + "message": "connector with 3 ends specializes a binary connector (a binary connector cannot have more than two ends) [validateConnectorBinarySpecialization]", "severity": "Warning", "stage": "semantics", "start": 199 @@ -4826,7 +4826,7 @@ "diagnostics": [ { "end": 231, - "message": "invocation of `F` binds parameter `x` more than once", + "message": "invocation of `F` binds parameter `x` more than once [validateInvocationExpressionNoDuplicateParameterRedefinition]", "severity": "Warning", "stage": "semantics", "start": 230 @@ -4845,7 +4845,7 @@ }, { "end": 276, - "message": "invocation of `F` names unknown parameter `y`", + "message": "invocation of `F` names unknown parameter `y` [validateInvocationExpressionParameterRedefinition]", "severity": "Warning", "stage": "semantics", "start": 275 @@ -4929,7 +4929,7 @@ "diagnostics": [ { "end": 154, - "message": "metadata must be typed by a metadata definition or metaclass; `NotAMetaclass` is a Class", + "message": "metadata must be typed by a metadata definition or metaclass; `NotAMetaclass` is a Class [validateMetadataFeatureMetaclass]", "severity": "Error", "stage": "semantics", "start": 141 @@ -4953,7 +4953,7 @@ "diagnostics": [ { "end": 185, - "message": "metadata must be typed by a metadata definition or metaclass; `NotMetadata` is a PartDefinition", + "message": "metadata must be typed by a metadata definition or metaclass; `NotMetadata` is a PartDefinition [validateMetadataFeatureMetaclass]", "severity": "Error", "stage": "semantics", "start": 174 @@ -4965,7 +4965,7 @@ "diagnostics": [ { "end": 132, - "message": "multiplicity bound must be a Natural number (or `*` for the upper bound)", + "message": "multiplicity bound must be a Natural number (or `*` for the upper bound) [validateMultiplicityRangeBoundResultTypes]", "severity": "Error", "stage": "semantics", "start": 127 @@ -4977,7 +4977,7 @@ "diagnostics": [ { "end": 135, - "message": "the name `A` is already used by an earlier member of the same namespace whose metaclass is not distinguishable from this member", + "message": "validateNamespaceDistinguishibility: the name `A` is already used by an earlier member of the same namespace whose metaclass is not distinguishable from this member", "severity": "Error", "stage": "structure", "start": 134 @@ -5248,7 +5248,7 @@ "diagnostics": [ { "end": 202, - "message": "a `require` constraint member is not allowed in a definition or usage body", + "message": "validateRequirementConstraintMembershipOwningType: a `require` constraint member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 177 @@ -5404,7 +5404,7 @@ "diagnostics": [ { "end": 220, - "message": "a `stakeholder` member is not allowed in a definition or usage body", + "message": "validateStakeholderMembershipOwningType: a `stakeholder` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 202 @@ -5440,7 +5440,7 @@ "diagnostics": [ { "end": 208, - "message": "an `entry` action member is not allowed in a definition or usage body", + "message": "validateStateSubactionMembershipOwningType: an `entry` action member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 190 @@ -5790,7 +5790,7 @@ "diagnostics": [ { "end": 200, - "message": "a `render` member is not allowed in a definition or usage body", + "message": "validateViewRenderingMembershipOwningType: a `render` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 185 diff --git a/crates/sysmlv2-parser/tests/fixtures/opensysml/baseline.json b/crates/sysmlv2-parser/tests/fixtures/opensysml/baseline.json index e5e723b..c00b3d5 100644 --- a/crates/sysmlv2-parser/tests/fixtures/opensysml/baseline.json +++ b/crates/sysmlv2-parser/tests/fixtures/opensysml/baseline.json @@ -94,7 +94,7 @@ "diagnostics": [ { "end": 356, - "message": "a `require` constraint member is not allowed in a definition or usage body", + "message": "validateRequirementConstraintMembershipOwningType: a `require` constraint member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 329 @@ -185,7 +185,7 @@ "diagnostics": [ { "end": 231, - "message": "an `entry` action member is not allowed in a definition or usage body", + "message": "validateStateSubactionMembershipOwningType: an `entry` action member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 223 @@ -204,7 +204,7 @@ "diagnostics": [ { "end": 219, - "message": "a `do` action member is not allowed in a definition or usage body", + "message": "validateStateSubactionMembershipOwningType: a `do` action member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 214 @@ -931,7 +931,7 @@ "diagnostics": [ { "end": 313, - "message": "an `actor` member is not allowed in a definition or usage body", + "message": "validateActorMembershipOwningType: an `actor` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 289 @@ -967,7 +967,7 @@ "diagnostics": [ { "end": 333, - "message": "a `stakeholder` member is not allowed in a definition or usage body", + "message": "validateStakeholderMembershipOwningType: a `stakeholder` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 306 @@ -979,7 +979,7 @@ "diagnostics": [ { "end": 268, - "message": "an `entry` action member is not allowed in a definition or usage body", + "message": "validateStateSubactionMembershipOwningType: an `entry` action member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 250 @@ -991,7 +991,7 @@ "diagnostics": [ { "end": 312, - "message": "a `render` member is not allowed in a definition or usage body", + "message": "validateViewRenderingMembershipOwningType: a `render` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 285 @@ -1825,7 +1825,7 @@ "diagnostics": [ { "end": 353, - "message": "invocation of `F` supplies 2 arguments for 1 parameter(s)", + "message": "invocation of `F` supplies 2 arguments for 1 parameter(s) [validateInvocationExpressionParameterRedefinition]", "severity": "Warning", "stage": "semantics", "start": 346 @@ -1837,7 +1837,7 @@ "diagnostics": [ { "end": 351, - "message": "invocation of `F` binds parameter `x` more than once", + "message": "invocation of `F` binds parameter `x` more than once [validateInvocationExpressionNoDuplicateParameterRedefinition]", "severity": "Warning", "stage": "semantics", "start": 350 @@ -1928,7 +1928,7 @@ "diagnostics": [ { "end": 404, - "message": "metadata must be typed by a metadata definition or metaclass; `M` is a Class", + "message": "metadata must be typed by a metadata definition or metaclass; `M` is a Class [validateMetadataFeatureMetaclass]", "severity": "Error", "stage": "semantics", "start": 403 @@ -2627,7 +2627,7 @@ "diagnostics": [ { "end": 203, - "message": "metadata must be typed by a metadata definition or metaclass; `PD` is a PartDefinition", + "message": "metadata must be typed by a metadata definition or metaclass; `PD` is a PartDefinition [validateMetadataFeatureMetaclass]", "severity": "Error", "stage": "semantics", "start": 201 @@ -3811,7 +3811,7 @@ "diagnostics": [ { "end": 213, - "message": "an `actor` member is not allowed in a definition or usage body", + "message": "validateActorMembershipOwningType: an `actor` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 201 @@ -5225,7 +5225,7 @@ "diagnostics": [ { "end": 231, - "message": "invocation of `F` binds parameter `x` more than once", + "message": "invocation of `F` binds parameter `x` more than once [validateInvocationExpressionNoDuplicateParameterRedefinition]", "severity": "Warning", "stage": "semantics", "start": 230 @@ -5265,7 +5265,7 @@ }, { "end": 276, - "message": "invocation of `F` names unknown parameter `y`", + "message": "invocation of `F` names unknown parameter `y` [validateInvocationExpressionParameterRedefinition]", "severity": "Warning", "stage": "semantics", "start": 275 @@ -5377,7 +5377,7 @@ "diagnostics": [ { "end": 154, - "message": "metadata must be typed by a metadata definition or metaclass; `NotAMetaclass` is a Class", + "message": "metadata must be typed by a metadata definition or metaclass; `NotAMetaclass` is a Class [validateMetadataFeatureMetaclass]", "severity": "Error", "stage": "semantics", "start": 141 @@ -5401,7 +5401,7 @@ "diagnostics": [ { "end": 185, - "message": "metadata must be typed by a metadata definition or metaclass; `NotMetadata` is a PartDefinition", + "message": "metadata must be typed by a metadata definition or metaclass; `NotMetadata` is a PartDefinition [validateMetadataFeatureMetaclass]", "severity": "Error", "stage": "semantics", "start": 174 @@ -5413,7 +5413,7 @@ "diagnostics": [ { "end": 132, - "message": "multiplicity bound must be a Natural number (or `*` for the upper bound)", + "message": "multiplicity bound must be a Natural number (or `*` for the upper bound) [validateMultiplicityRangeBoundResultTypes]", "severity": "Error", "stage": "semantics", "start": 127 @@ -5425,7 +5425,7 @@ "diagnostics": [ { "end": 135, - "message": "the name `A` is already used by an earlier member of the same namespace whose metaclass is not distinguishable from this member", + "message": "validateNamespaceDistinguishibility: the name `A` is already used by an earlier member of the same namespace whose metaclass is not distinguishable from this member", "severity": "Error", "stage": "structure", "start": 134 @@ -5710,7 +5710,7 @@ "diagnostics": [ { "end": 202, - "message": "a `require` constraint member is not allowed in a definition or usage body", + "message": "validateRequirementConstraintMembershipOwningType: a `require` constraint member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 177 @@ -5866,7 +5866,7 @@ "diagnostics": [ { "end": 220, - "message": "a `stakeholder` member is not allowed in a definition or usage body", + "message": "validateStakeholderMembershipOwningType: a `stakeholder` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 202 @@ -5902,7 +5902,7 @@ "diagnostics": [ { "end": 208, - "message": "an `entry` action member is not allowed in a definition or usage body", + "message": "validateStateSubactionMembershipOwningType: an `entry` action member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 190 @@ -6273,7 +6273,7 @@ "diagnostics": [ { "end": 200, - "message": "a `render` member is not allowed in a definition or usage body", + "message": "validateViewRenderingMembershipOwningType: a `render` member is not allowed in a definition or usage body", "severity": "Error", "stage": "structure", "start": 185 diff --git a/crates/sysmlv2-syntax/src/check.rs b/crates/sysmlv2-syntax/src/check.rs index 5f8d6e4..2ff5dce 100644 --- a/crates/sysmlv2-syntax/src/check.rs +++ b/crates/sysmlv2-syntax/src/check.rs @@ -25,6 +25,7 @@ use std::collections::HashMap; /// name against the pinned 2025-02 KerML/SysML metamodel and ratchets the /// total rule inventory, so coverage cannot drift silently. pub const IMPLEMENTED_NORMATIVE_VALIDATION_RULES: &[&str] = &[ + "validateActorMembershipOwningType", "validateCaseDefinitionOnlyOneObjective", "validateCaseDefinitionOnlyOneSubject", "validateCaseDefinitionSubjectParameterPosition", @@ -33,12 +34,18 @@ pub const IMPLEMENTED_NORMATIVE_VALIDATION_RULES: &[&str] = &[ "validateCaseUsageSubjectParameterPosition", "validateFlowDefinitionFlowEnds", "validateImportTopLevelVisibility", + "validateNamespaceDistinguishibility", "validateObjectiveMembershipOwningType", + "validateRequirementConstraintMembershipOwningType", "validateRequirementDefinitionOnlyOneSubject", "validateRequirementDefinitionSubjectParameterPosition", "validateRequirementUsageOnlyOneSubject", "validateRequirementUsageSubjectParameterPosition", + "validateRequirementVerificationMembershipOwningType", + "validateStakeholderMembershipOwningType", + "validateStateSubactionMembershipOwningType", "validateSubjectMembershipOwningType", + "validateViewRenderingMembershipOwningType", ]; /// Validate one parsed source unit; returns all body-context diagnostics. @@ -54,9 +61,10 @@ pub fn validate(unit: &SourceUnit) -> Vec { if matches!(member.kind, MemberKind::Import(_)) && member.visibility != Some(Visibility::Private) { - v.error( + v.rule_error( member, - "validateImportTopLevelVisibility: a top-level import must be private".into(), + "validateImportTopLevelVisibility", + "a top-level import must be private", ); } } @@ -679,6 +687,12 @@ impl Validator { self.diags.push(Diagnostic::error(m.span, message)); } + /// An error that a normative rule names, with the rule as its code. + fn rule_error(&mut self, m: &Member, rule: &'static str, message: &str) { + self.diags + .push(Diagnostic::error(m.span, format!("{rule}: {message}")).with_code(rule)); + } + fn walk_members(&mut self, members: &[Member], ctx: Ctx, owner_is_variation: bool) { self.check_duplicate_names(members); let mut target_succession_run = false; @@ -715,33 +729,31 @@ impl Validator { } } - fn check_one_subject(&mut self, members: &[Member], rule: &str) { + fn check_one_subject(&mut self, members: &[Member], rule: &'static str) { let mut subjects = members .iter() .filter(|m| matches!(m.kind, MemberKind::Subject(_))); let _ = subjects.next(); for duplicate in subjects { - self.error( + self.rule_error( duplicate, - format!("{rule}: a requirement or case may own at most one subject"), + rule, + "a requirement or case may own at most one subject", ); } } - fn check_one_objective(&mut self, members: &[Member], rule: &str) { + fn check_one_objective(&mut self, members: &[Member], rule: &'static str) { let mut objectives = members .iter() .filter(|m| matches!(m.kind, MemberKind::Objective(_))); let _ = objectives.next(); for duplicate in objectives { - self.error( - duplicate, - format!("{rule}: a case may own at most one objective"), - ); + self.rule_error(duplicate, rule, "a case may own at most one objective"); } } - fn check_subject_position(&mut self, members: &[Member], rule: &str) { + fn check_subject_position(&mut self, members: &[Member], rule: &'static str) { let Some((subject_index, subject)) = members .iter() .enumerate() @@ -758,9 +770,10 @@ impl Validator { _ => false, }); if first_input != Some(subject_index) { - self.error( + self.rule_error( subject, - format!("{rule}: the subject parameter must be the first input"), + rule, + "the subject parameter must be the first input", ); } } @@ -794,11 +807,10 @@ impl Validator { let _ = ends.next(); let _ = ends.next(); for excess in ends { - self.error( + self.rule_error( excess, - "validateFlowDefinitionFlowEnds: a flow definition may not \ - have more than two flow ends" - .into(), + "validateFlowDefinitionFlowEnds", + "a flow definition may not have more than two flow ends", ); } } @@ -857,15 +869,18 @@ impl Validator { .copied() .any(|p| !metaclasses_are_distinguishable(p, metaclass)) { - self.diags.push(Diagnostic::error( - name.span, - format!( - "the name `{}` is already used by an earlier member \ - of the same namespace whose metaclass is not \ - distinguishable from this member", - name.value - ), - )); + self.diags.push( + Diagnostic::error( + name.span, + format!( + "validateNamespaceDistinguishibility: the name `{}` is \ + already used by an earlier member of the same namespace \ + whose metaclass is not distinguishable from this member", + name.value + ), + ) + .with_code("validateNamespaceDistinguishibility"), + ); } prior.push(metaclass); } @@ -969,19 +984,31 @@ impl Validator { }; if !ok { let rule = match m.kind { - MemberKind::Subject(_) => Some("validateSubjectMembershipOwningType: "), - MemberKind::Objective(_) => Some("validateObjectiveMembershipOwningType: "), + MemberKind::Subject(_) => Some("validateSubjectMembershipOwningType"), + MemberKind::Objective(_) => Some("validateObjectiveMembershipOwningType"), + MemberKind::Actor(_) => Some("validateActorMembershipOwningType"), + MemberKind::Stakeholder(_) => Some("validateStakeholderMembershipOwningType"), + MemberKind::RequirementConstraint { .. } => { + Some("validateRequirementConstraintMembershipOwningType") + } + MemberKind::RequirementVerification(_) => { + Some("validateRequirementVerificationMembershipOwningType") + } + MemberKind::StateSubaction { .. } => { + Some("validateStateSubactionMembershipOwningType") + } + MemberKind::Render(_) => Some("validateViewRenderingMembershipOwningType"), _ => None, }; - self.error( - m, - format!( - "{}{} is not allowed in {}", - rule.unwrap_or(""), - member_desc(&m.kind), - ctx.describe() - ), + let message = format!( + "{} is not allowed in {}", + member_desc(&m.kind), + ctx.describe() ); + match rule { + Some(rule) => self.rule_error(m, rule, &message), + None => self.error(m, message), + } } }