Skip to content
Merged
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
10 changes: 10 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ dotnet-coverage collect "dotnet test SysML2.NET.sln --no-build" -f xml -o covera

Test framework: **NUnit**. Test classes use `[TestFixture]` and `[Test]` attributes.

## Reading `DEVELOPMENT_STANDARDS.md` is MANDATORY

**`DEVELOPMENT_STANDARDS.md` at the repo root governs how code is written in this solution, and it applies to EVERY piece of code you write or modify — production, tests, generator, scripts.** It is the org-wide Starion Group engineering convention document: C# style, LINQ usage, test conventions, exception and validation boundaries, XML documentation, and repo hygiene.

Read it before writing code in a session where you have not already done so. Do not author code from memory of "typical C#" conventions, or by copying the shape of a neighbouring file — parts of this repo predate the document and do not comply, so imitating them reproduces the deviation.

**Precedence, per its own §0:** a configured `.editorconfig` entry, Roslyn/StyleCop analyzer, or linter rule always WINS over the document's prose where they cover the same concern. The written rules are the fallback for what tooling does not enforce. So before treating one of its rules as binding, check `.editorconfig`, `Directory.Build.props` / `Directory.Build.targets` and any `.globalconfig` — and never flag a diff as a violation of the document when it is actually compliant with the repo's own tool configuration.

Where this `CLAUDE.md` and `DEVELOPMENT_STANDARDS.md` overlap, this file is the project-specific override and wins; the standards document is explicit that project-specific overrides belong in the repo's own `CLAUDE.md`. `TESTING.md` remains the binding authority for NUnit fixtures specifically.

## Reading `TESTING.md` is MANDATORY

**Before you write or modify a single line in any `*.Tests/` project, you MUST `Read` `TESTING.md` at the repo root — in full, in the current session.** It is the authoritative, binding specification for every NUnit fixture in this solution.
Expand Down
695 changes: 695 additions & 0 deletions DEVELOPMENT_STANDARDS.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@
}
else
{
var handCodedRuleName = groupElement.TextualNotationRule?.RuleName ?? "Unknown";

Check warning on line 237 in SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs

View workflow job for this annotation

GitHub Actions / Build

Define a constant instead of using this literal 'Unknown' 6 times.
EmitHandCodedFallback(writer, handCodedRuleName, ruleGenerationContext);
}
}
Expand Down Expand Up @@ -268,7 +268,7 @@

if (!ruleGenerationContext.IsNextElementNewLineTerminal())
{
writer.WriteSafeString("stringBuilder.Append(' ');");

Check warning on line 271 in SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs

View workflow job for this annotation

GitHub Actions / Build

Define a constant instead of using this literal 'stringBuilder.Append(' ');' 5 times.
}
}
else
Expand Down Expand Up @@ -463,6 +463,15 @@
{
writer.WriteSafeString($"SharedTextualNotationBuilder.AppendName(stringBuilder, poco.{targetPropertyName});");
}
else if (assignmentElement.Value is NonTerminalElement { Name: "STRING_VALUE" })
{
// STRING_VALUE carries its own quotes — '"' ( STRING_CHARACTER |
// ESCAPE_SEQUENCE )* '"' — and the model holds the DECODED string, so the
// writer owns re-encoding it. See AppendStringValue. Keyed on the TERMINAL
// rather than the property type: a String-typed test would also quote
// declaredName and every other string the grammar writes bare.
writer.WriteSafeString($"SharedTextualNotationBuilder.AppendStringValue(stringBuilder, poco.{targetPropertyName});");
}
else if (string.Equals(targetPropertyName, "Operator", StringComparison.Ordinal))
{
// Operator tokens need a trailing space before the next operand, matching
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package '14a-Language Extensions' {
private import 'User Defined Extensions'::*;
package 'User Defined Extensions' {
enum def ClassificationLevel {
enum uncl;
enum conf;
enum secret;
}
metadata def Classified {
:>> annotatedElement : SysML::Systems::PartUsage;
attribute classificationLevel: ClassificationLevel[1];
}
}
part part_X {
@ Classified {
ref :>> classificationLevel = ClassificationLevel::conf;
}
}
part part_Y {
@ Classified {
ref :>> classificationLevel = ClassificationLevel::conf;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package '14b-Language-Extensions' {
package LibraryModel {
part def ECU;
}
package UserModel {
package Definitions {
private import LibraryModel::*;
part def VehicleControlUnit :> ECU;
part def EngineControlUnit :> ECU;
part def Vehicle;
part def Engine;
part def CanBus;
port def BusIF;
}
package Usages {
private import Definitions::*;
part vehicle1: Vehicle {
part vehicleControlUnit: VehicleControlUnit {
port busIF: ~BusIF;
}
connect vehicleControlUnit.busIF to canBus.vehicleControlIF;
part canBus: CanBus {
port vehicleControlIF: BusIF;
port engineControlIF: BusIF;
port sensorIF: BusIF;
}
connect engine.engineControlUnit.busIF to canBus.engineControlIF;
part engine: Engine {
part engineControlUnit: EngineControlUnit {
port busIF: ~BusIF;
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package '14c-Language-Extensions' {
private import ScalarValues::*;
library package FMEALibrary {
abstract occurrence def Situation;
abstract occurrence situations: Situation[*] nonunique;
occurrence def Cause :> Situation {
attribute occurs[0..1] : Real;
}
abstract occurrence causes: Cause[*] nonunique;
occurrence def FailureMode :> Situation {
attribute detected[0..1] : Real;
}
abstract occurrence failureModes: FailureMode[*] nonunique;
occurrence def Effect :> Situation {
attribute severity[0..1] : String;
}
abstract occurrence effects: Effect[*] nonunique;
item def FMEAItem :> Situation {
attribute RPN: Real[0..1];
occurrence :>> causes;
occurrence :>> failureModes;
occurrence :>> effects;
}
abstract item fmeaItems: FMEAItem[*] nonunique;
connection def Causation :> Occurrences::HappensBefore {
end[*] ref cause: Situation;
end[*] ref effect: Situation;
}
abstract connection causations: Causation[*] nonunique;
requirement def FMEARequirement;
abstract requirement fmeaRequirements: FMEARequirement[*] nonunique;
requirement def RequirementWithSIL :> FMEARequirement {
attribute sil: SIL;
}
enum def SIL {
enum A;
enum B;
enum C;
}
connection def Violation {
end[*] ref sit: Situation;
end[*] ref req: FMEARequirement;
}
abstract connection violations: Violation[*] nonunique;
abstract connection def ControllingMeasure {
end[*] ref sit: Situation;
end[*] ref req: FMEARequirement;
}
connection def Prevention :> ControllingMeasure;
abstract connection preventions: Prevention[*] nonunique;
connection def Mitigation :> ControllingMeasure;
abstract connection mitigations: Mitigation[*] nonunique;
}
library package FMEAMetadata {
private import Metaobjects::SemanticMetadata;
private import FMEALibrary::*;
enum def Status {
enum Approved;
enum NotApproved;
}
metadata def StatusHolder {
ref status: Status;
}
metadata def <situation> SituationMetadata :> SemanticMetadata {
:>> baseType default = situations meta SysML::Systems::Usage;
}
metadata def <cause> CauseMetadata :> situation {
:>> baseType = causes meta SysML::Systems::Usage;
}
metadata def <failure> FailureModeMetadata :> situation {
:>> baseType = failureModes meta SysML::Systems::Usage;
}
metadata def <effect> EffectMetadata :> situation {
:>> baseType = effects meta SysML::Systems::Usage;
}
metadata def <fmea> FMEAItemMetadata :> situation {
:> annotatedElement : SysML::Systems::ItemDefinition;
:> annotatedElement : SysML::Systems::ItemUsage;
:>> baseType = fmeaItems meta SysML::Systems::Usage;
}
metadata def <causation> CausationMetadata :> SemanticMetadata {
:>> annotatedElement : SysML::Systems::ConnectionUsage;
:>> baseType = causations meta SysML::Systems::Usage;
}
metadata def <fmeaspec> FMEARequirementMetadata :> SemanticMetadata {
:>> annotatedElement : SysML::Systems::RequirementUsage;
:>> baseType = fmeaRequirements meta SysML::Systems::Usage;
}
metadata def <violation> ViolationMetadata :> SemanticMetadata {
:>> annotatedElement : SysML::Systems::ConnectionUsage;
:>> baseType = violations meta SysML::Systems::Usage;
}
abstract metadata def ControllingMeasureMetadata :> SemanticMetadata {
:>> annotatedElement : SysML::Systems::ConnectionUsage;
}
metadata def <prevention> PreventionMetadata :> ControllingMeasureMetadata {
:>> baseType = preventions meta SysML::Systems::Usage;
}
metadata def <mitigation> MitigationMetadata :> ControllingMeasureMetadata {
:>> baseType = mitigations meta SysML::Systems::Usage;
}
}
package FMEAUserModel {
private import FMEALibrary::*;
private import FMEAMetadata::*;
#fmeaspec requirement req1 {
doc
/* Meter designed according to ISO00124 */
}
#fmeaspec requirement req2 {
doc
/* Device working for 1 week without the need to replace batteries */
}
#fmeaspec requirement req3: RequirementWithSIL {
@ StatusHolder {
ref :>> status = Status::Approved;
}
doc
/* Alarm when battery has sank */
:>> sil = SIL::A;
}
#fmea item def 'Glucose FMEA Item' {
#prevention connect 'battery depleted' to req1;
#cause occurrence 'battery depleted' {
:>> Cause::occurs = 0.005;
}
#causation connect 'battery depleted' to 'battery cannot be charged';
#failure occurrence 'battery cannot be charged' {
:>> FailureMode::detected = 0.013;
}
#causation connect 'battery cannot be charged' to 'glucose level undetected';
#effect occurrence 'glucose level undetected';
#causation connect 'glucose level undetected' to 'therapy delay';
#effect occurrence 'therapy delay' {
:>> Effect::severity = "High";
}
}
#violation connect 'Glucose Meter in Use' to req2;
#mitigation connect 'Glucose Meter in Use' to req3;
#fmea item 'Glucose Meter in Use': 'Glucose FMEA Item' {
part 'glucose meter' {
event 'glucose level undetected'[*];
part battery {
event 'battery depleted'[*];
event 'battery cannot be charged'[*];
}
part pump;
part reservoir;
}
part patient {
event 'therapy delay'[*];
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ public void OneTimeTearDown()
[TestCase("13-Model Containment", "13b-Safety and Security Features Element Group-1.sysmlx")]
[TestCase("13-Model Containment", "13b-Safety and Security Features Element Group-2.sysmlx")]
[TestCase("13-Model Containment", "13b-Safety and Security Features Element Group.sysmlx")]
[TestCase("14-Language Extensions", "14a-Language Extensions.sysmlx")]
[TestCase("14-Language Extensions", "14b-Language Extensions.sysmlx")]
[TestCase("14-Language Extensions", "14c-Language Extensions.sysmlx")]
public async Task VerifyValidationTextualNotationXmi(string folderName, string fileName)
{
var loggerFactory = LoggerFactory.Create(builder =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public static void BuildComment(SysML2.NET.Core.POCO.Root.Annotations.IComment p
if (!string.IsNullOrWhiteSpace(poco.Locale))
{
stringBuilder.Append("locale ");
stringBuilder.Append(poco.Locale);
SharedTextualNotationBuilder.AppendStringValue(stringBuilder, poco.Locale);
stringBuilder.Append(' ');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public static void BuildDocumentation(SysML2.NET.Core.POCO.Root.Annotations.IDoc
if (!string.IsNullOrWhiteSpace(poco.Locale))
{
stringBuilder.Append("locale ");
stringBuilder.Append(poco.Locale);
SharedTextualNotationBuilder.AppendStringValue(stringBuilder, poco.Locale);
stringBuilder.Append(' ');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static partial class LiteralStringTextualNotationBuilder
/// <param name="stringBuilder">The <see cref="IndentedStringBuilder" /> that accumulates the entire textual notation with indentation</param>
public static void BuildLiteralString(SysML2.NET.Core.POCO.Kernel.Expressions.ILiteralString poco, TextualNotationWriterContext writerContext, IndentedStringBuilder stringBuilder)
{
stringBuilder.Append(poco.Value);
SharedTextualNotationBuilder.AppendStringValue(stringBuilder, poco.Value);

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static void BuildTextualRepresentation(SysML2.NET.Core.POCO.Root.Annotati
}

stringBuilder.Append("language ");
stringBuilder.Append(poco.Language);
SharedTextualNotationBuilder.AppendStringValue(stringBuilder, poco.Language);
SharedTextualNotationBuilder.AppendRegularComment(stringBuilder, poco.Body, surroundWithBlankLines: false);

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,42 @@ internal static void AppendRealValue(IndentedStringBuilder stringBuilder, double
}
}

/// <summary>
/// Appends a string in the form the <c>STRING_VALUE</c> terminal accepts.
/// <para><c>STRING_VALUE = '"' ( STRING_CHARACTER | ESCAPE_SEQUENCE )* '"'</c></para>
/// </summary>
/// <param name="stringBuilder">The <see cref="IndentedStringBuilder" /> that contains the entire textual notation</param>
/// <param name="value">The decoded string to emit; may be <see langword="null" /></param>
/// <remarks>
/// The quotes are part of the TERMINAL, unlike <c>UNRESTRICTED_NAME</c> where the specification says
/// the surrounding quotes are not part of the represented name. The model holds the DECODED string,
/// so the writer owns re-encoding it: emitting the raw value turned <c>"High"</c> into <c>High</c>,
/// which re-parses as a <c>FeatureReferenceExpression</c> — a different metaclass that then fails
/// name resolution.
/// <para><c>STRING_CHARACTER</c> is any printable character other than backslash or <c>"</c>, so
/// those two are escaped, along with the non-printables KerML Table 4 gives escapes for. Backslash is
/// replaced FIRST so the backslashes introduced by the later replacements are not re-escaped.</para>
/// </remarks>
internal static void AppendStringValue(IndentedStringBuilder stringBuilder, string value)
{
stringBuilder.Append('"');

if (!string.IsNullOrEmpty(value))
{
stringBuilder.Append(value
.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\b", "\\b")
.Replace("\f", "\\f")
.Replace("\t", "\\t")
.Replace("\r\n", "\\n")
.Replace("\n", "\\n")
.Replace("\r", "\\n"));
}

stringBuilder.Append('"');
}

/// <summary>
/// Builds the Textual Notation string for the shared two-ended connector declaration template
/// used by <c>BindingConnectorDeclaration</c> (with <c>'of'</c>/<c>'='</c>) and
Expand Down
Loading