Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
c2e61ec
test: red assertion-parity test for V2_6_V2_7 -> single-topic migration
ottobolyos Aug 19, 2026
bb7a7f8
test(dry-generator): consolidate V2_6_V2_7 fixtures into topic files …
ottobolyos Aug 19, 2026
0156e9c
chore(dry-generator): delete deprecated V2_6_V2_7 per-version fixture…
ottobolyos Aug 19, 2026
4273b04
test(dry-generator): permanent guard against V<N_M>/ folder + V<N_M>*…
ottobolyos Aug 19, 2026
350c01d
fix(dry-generator): filter bin/obj from per-version directory guard s…
ottobolyos Aug 19, 2026
62cf097
docs(testing): retire V2_6_V2_7 stale refs post Phase 1 consolidation
ottobolyos Aug 19, 2026
4092538
test(dry-generator): topic-fixture coverage guard for migrated anchor…
ottobolyos Aug 20, 2026
e28db25
test(generator): add ByteIdenticalRegenTests for current-XMI regen
ottobolyos Aug 20, 2026
022ac0b
refactor(generator): merge XmlCutting{Item,ToolLifeCycle} into Shape-…
ottobolyos Aug 20, 2026
a0955ba
refactor(generator): merge Enum{,String}Descriptions into Shape-B tem…
ottobolyos Aug 20, 2026
981576d
fix(generator): emit Pallet measurement interfaces
ottobolyos Aug 20, 2026
1fdc09a
chore(generator): remove stale orphan .g.cs files
ottobolyos Aug 20, 2026
e7d5aef
chore(generator): refresh .g.cs to current generator output
ottobolyos Aug 20, 2026
d60d4ff
test(generator): flip current-XMI byte-identical guard from Explicit …
ottobolyos Aug 20, 2026
884425c
feat(generator): add --previous-xmi flag for delta-driven regen
ottobolyos Aug 20, 2026
1f084f5
test(generator): mutated-XMI cross-verify of delta emission
ottobolyos Aug 20, 2026
e6e2ecb
fix(generator): drop stale ToolingMeasurement.Code inheritance override
ottobolyos Aug 20, 2026
d7f0057
test(generator): skip bin/obj generated files in byte-identity walks
ottobolyos Aug 20, 2026
c9f37a6
chore(tests): adopt Stryker.NET mutation-testing framework
ottobolyos Aug 20, 2026
c6dfc4b
test(generator): CLI failure-path coverage for Program.cs
ottobolyos Aug 20, 2026
863adee
test(generator): delta-mode Compat body + stats-line invariants
ottobolyos Aug 20, 2026
e25f99b
test(generator): relax malformed-XMI test to non-zero exit contract
ottobolyos Aug 20, 2026
9a91e23
fix(generator): delta emitter deletes stale UNCHANGED / REMOVED files
ottobolyos Aug 20, 2026
88dfe0e
fix(tests): drain stdout/stderr concurrently + tighten TopicAnchors f…
ottobolyos Aug 20, 2026
0c7052f
docs(generator): document delta CLI + Stryker gate + generator-tests …
ottobolyos Aug 20, 2026
81724ce
chore(tests): exclude Stryker Regex mutator (upstream bug workaround)
ottobolyos Aug 20, 2026
9bcc109
chore(tests): pin Stryker threshold at 7.75% baseline; issue #242 tra…
ottobolyos Aug 20, 2026
20832a7
test(generator): pin stryker-config.json thresholds + JSONC-parser as…
ottobolyos Aug 20, 2026
838d19e
docs(testing): sync mutation-testing.md + Stryker.NET pin to cycle-2 …
ottobolyos Aug 20, 2026
5b66785
docs(reference): regenerate cli.md for new sysml-import delta flags
ottobolyos Aug 20, 2026
ecde543
docs(testing): repoint out-of-docs code links to GitHub blob URLs
ottobolyos Aug 20, 2026
d4810b7
feat(sysml-import): zero-config PREV_VERSION auto-derive from MTConne…
ottobolyos Aug 21, 2026
33d1bde
test(generator): cover zero-config PREV_VERSION auto-derive + adapt e…
ottobolyos Aug 21, 2026
06ce82e
docs(sysml-import): document --new-xmi / --full-tree / zero-config au…
ottobolyos Aug 21, 2026
5f57e35
test(generator): pin uncovered auto-derive + IsSafeCompatLabel arms
ottobolyos Aug 21, 2026
37ae555
fix(sysml-import): strip C# comments before Max regex; unlie "(auto-d…
ottobolyos Aug 21, 2026
5e4a97c
fix(docs-gen): bound RequireValue detection to current case block
ottobolyos Aug 21, 2026
706cd42
docs(reference): regenerate cli.md for auto-derive + full-tree flag s…
ottobolyos Aug 21, 2026
96bf3a5
docs(sysml-import): rewrite CLI page for zero-config auto-derive default
ottobolyos Aug 21, 2026
5db3ec0
test(sysml-import,docs-gen): extract SourceStripper + pin cycle-4 gaps
ottobolyos Aug 21, 2026
ad35e13
docs(sysml-import): fix AmE regression in cycle-4 rewrite
ottobolyos Aug 21, 2026
699a57d
refactor(sysml-import): drop SourceStripper for line-anchored regex —…
ottobolyos Aug 21, 2026
cd6b2ba
chore(sysml-import): regenerate .g.cs to match DRY-generator output
ottobolyos Aug 24, 2026
8e983a5
fix(sysml-import): emit 'new' only when parent class has same-named m…
ottobolyos Aug 25, 2026
765b0cf
chore(sysml-import): regenerate .g.cs after 'new'-emission fix
ottobolyos Aug 25, 2026
39d3a0b
test(sysml-import): pin JSON-cppagent regen against obsolete-type ref…
ottobolyos Aug 25, 2026
8c99c1f
fix(sysml-import): JSON-cppagent generator skips references to obsole…
ottobolyos Aug 25, 2026
2b612a4
chore(sysml-import): regenerate JSON-cppagent .g.cs to drop obsolete …
ottobolyos Aug 25, 2026
96de4f1
chore(tests): swap sibling references to obsolete types for their non…
ottobolyos Aug 25, 2026
453783e
tests,docs,build,tools(dry-generator): swap BrE tokens to AmE in PR-a…
ottobolyos Aug 31, 2026
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
6 changes: 6 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"commands": [
"reportgenerator"
]
},
"dotnet-stryker": {
"version": "4.16.0",
"commands": [
"stryker"
]
}
}
}
12 changes: 11 additions & 1 deletion MTConnect.NET.sln
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MTConnect.NET-HTTP-Tests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MTConnect.NET-SysML-Tests", "tests\MTConnect.NET-SysML-Tests\MTConnect.NET-SysML-Tests.csproj", "{6CE969D2-A1E8-4BC1-85D8-303701B42F64}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MTConnect.NET-Generator-Tests", "tests\MTConnect.NET-Generator-Tests\MTConnect.NET-Generator-Tests.csproj", "{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -519,6 +521,14 @@ Global
{6CE969D2-A1E8-4BC1-85D8-303701B42F64}.Package|Any CPU.Build.0 = Debug|Any CPU
{6CE969D2-A1E8-4BC1-85D8-303701B42F64}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CE969D2-A1E8-4BC1-85D8-303701B42F64}.Release|Any CPU.Build.0 = Release|Any CPU
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Docker|Any CPU.Build.0 = Debug|Any CPU
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Package|Any CPU.ActiveCfg = Debug|Any CPU
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Package|Any CPU.Build.0 = Debug|Any CPU
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -576,7 +586,7 @@ Global
{17E64F59-0E62-4FCE-BEC4-EABBCF95B9A2} = {BBF53739-168D-4635-8595-083AC0C65E4C}
{AE09D1CA-5572-40BF-B984-74230E8634E1} = {14375E03-6BF8-45E6-B868-D2399368992B}
{3E89B860-A428-470C-8E48-0DDABC4027F0} = {14375E03-6BF8-45E6-B868-D2399368992B}
{6CE969D2-A1E8-4BC1-85D8-303701B42F64} = {14375E03-6BF8-45E6-B868-D2399368992B}
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57} = {14375E03-6BF8-45E6-B868-D2399368992B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC13D3AD-18BF-4695-AB2A-087EF0885B20}
Expand Down
11 changes: 8 additions & 3 deletions build/MTConnect.NET-DocsGen/CliInventory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,15 @@ private static CliInfo CollectDotNetTool(string name, string file, string repoRo
if (headerDescs.TryGetValue(flagName, out var headerDesc)) desc = headerDesc;
desc ??= ExtractDotnetFlagDescription(text, flagName);

// Detect whether the case body calls `RequireValue` — if it
// does, the flag takes a value.
// Detect whether the case body calls `RequireValue` — if it does,
// the flag takes a value. The scan is bounded to the CURRENT case
// block only: it stops at the next `case "…":` label, a `default:`
// label, or a `break;` terminator, so a boolean flag whose case
// body sits above a value-taking case (like `--full-tree` above
// `case "--output": … RequireValue(…)`) does not falsely inherit
// the neighbor's value shape.
bool takesValue = Regex.IsMatch(text,
$@"case\s+""{Regex.Escape(flagName)}""\s*:[\s\S]{{0,200}}?RequireValue");
$@"case\s+""{Regex.Escape(flagName)}""\s*:(?:(?!\s*case\s+""|\s*default\s*:|\bbreak\s*;)[\s\S])*?RequireValue");
flags.Add(new CliFlag(
Name: flagName,
Short: null,
Expand Down
16 changes: 16 additions & 0 deletions build/MTConnect.NET-SysML-Import/CSharp/ClassModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ public class ClassModel : MTConnectClassModel, ITemplateModel
/// </summary>
public new List<PropertyModel> Properties { get; set; } = new();

/// <summary>
/// <c>true</c> when at least one ancestor in the
/// <see cref="MTConnectClassModel.ParentName"/> chain declares a
/// non-empty <see cref="MTConnectClassModel.Rules"/> array.
/// <c>Model.scriban</c> uses this — rather than mere parent
/// presence — to decide whether the generated <c>Rules</c> field
/// needs the <c>new</c> modifier. A class can have a parent
/// without that parent (or any of its ancestors) declaring
/// <c>Rules</c>, in which case emitting <c>new</c> hides nothing
/// and the compiler raises CS0109. Populated by
/// <see cref="CSharpTemplateRenderer"/> after every
/// <see cref="ClassModel"/> has been assembled, so the full
/// ancestor chain is resolvable.
/// </summary>
public bool ParentHasRules { get; set; }


/// <summary>
/// Parameterless constructor used by the import pipeline when it
Expand Down
2 changes: 1 addition & 1 deletion build/MTConnect.NET-SysML-Import/CSharp/EnumModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public string RenderModel()
public string RenderDescriptions()
{
if (Values == null || Values.Count == 0) return null;
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumDescriptions.scriban");
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumOrStringDescriptions.scriban");
return template.Render(this);
}
}
Expand Down
10 changes: 9 additions & 1 deletion build/MTConnect.NET-SysML-Import/CSharp/EnumStringModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ internal class EnumStringModel : MTConnectEnumModel, ITemplateModel

public bool IsPartial { get; set; }

// Consumed by the Shape-B consolidated EnumOrStringDescriptions.scriban
// template: gates the class-doc wording, the Get(...) overload's
// parameter type (string vs. enum-typed), and the Get(...) doc summary.
// EnumModel and ObservationModel do NOT expose this — Scriban resolves
// a missing member as null (falsy), producing the enum-shape emission
// for those two callers.
public bool IsString => true;


public EnumStringModel() { }

Expand Down Expand Up @@ -88,7 +96,7 @@ public string RenderModel()

public string RenderDescriptions()
{
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumStringDescriptions.scriban");
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumOrStringDescriptions.scriban");
return template.Render(this);
}
}
Expand Down
6 changes: 5 additions & 1 deletion build/MTConnect.NET-SysML-Import/CSharp/MeasurementModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ public string RenderModel()
}

/// <inheritdoc />
public string RenderInterface() => null;
public string RenderInterface()
{
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "Pallets.MeasurementInterface.scriban");
return template.Render(this);
}

/// <inheritdoc />
public string RenderDescriptions() => null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public string RenderModel()
/// <inheritdoc />
public string RenderDescriptions()
{
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumDescriptions.scriban");
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumOrStringDescriptions.scriban");
return template.Render(this);
}
}
Expand Down
112 changes: 98 additions & 14 deletions build/MTConnect.NET-SysML-Import/CSharp/TemplateRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,15 @@ public static void Render(MTConnectModel mtconnectModel, string outputPath)
// so its `Code` property hides Measurement.Code and needs `new`.
MarkInheritedProperties(templates, classModels);

// Mark each ClassModel's ParentHasRules flag so Model.scriban
// emits the `new` modifier on the generated Rules[] field only
// when an ancestor actually declares Rules. Parent presence
// alone is not sufficient — e.g. Axis extends AbstractAxis, but
// AbstractAxis has no Rules, so `new` on Axis.Rules would hide
// nothing and raise CS0109 ("does not hide an accessible
// member"). See ClassModel.ParentHasRules XML doc.
MarkParentHasRules(templates, classModels);


foreach (var template in templates)
{
Expand Down Expand Up @@ -642,20 +651,19 @@ private static void MarkInheritedProperties(
break;

case "Assets.CuttingTools.ToolingMeasurement":
// ToolingMeasurement extends `Measurement` (the
// CuttingTools abstract Measurement base, NOT
// Assets.Pallet.Measurement). The CuttingTools
// Measurement.g.cs is hand-maintained / frozen —
// not produced by any current renderer flow — so
// it never enters the export-side ClassModel
// graph the inheritance walk traverses, and a
// Name-only lookup of "Measurement" resolves to
// Pallet.Measurement (which lacks Code). Class
// side only — IMeasurement.g.cs has `Code`
// commented out, so the interface child does NOT
// hide anything and emitting `new` there would
// produce CS0109 instead.
classOnlyNames.Add("Code");
// No hand-stitched inheritance seed needed. The
// Assets.CuttingTools.Measurement base IS produced
// by the current renderer flow (via
// MTConnectAssetInformationModel.ParseAssetInformationModel's
// sharedMeasurement injection which imports the
// Pallet Measurement class under Assets.CuttingTools),
// so the export-side ClassModel graph already carries
// its property list. The Pallet Measurement lacks
// Code, and the interface IMeasurement.g.cs likewise
// has Code commented out — hence emitting `new` on
// ToolingMeasurement.Code would raise CS0109 on both
// the class and interface sides. Fall through to the
// default inheritance walk with no override.
break;
}

Expand Down Expand Up @@ -701,6 +709,82 @@ private static void MarkInheritedProperties(
}
}

/// <summary>
/// Marks each <see cref="ClassModel.ParentHasRules"/> flag by walking
/// the <see cref="MTConnectClassModel.ParentName"/> ancestor chain and
/// checking whether any ancestor declares a non-empty
/// <see cref="MTConnectClassModel.Rules"/> array. <c>Model.scriban</c>
/// uses the flag to decide whether the generated <c>Rules</c> field
/// needs the <c>new</c> modifier — mere parent presence is not
/// sufficient, since a class can extend a parent that itself carries
/// no <c>Rules</c> (e.g. <c>Axis : AbstractAxis</c>, where
/// <c>AbstractAxis</c> has no <c>Rules</c>). Emitting <c>new</c> in
/// that case hides nothing and the compiler raises CS0109.
/// </summary>
/// <remarks>
/// Deliberately independent of <see cref="MarkInheritedProperties"/>
/// rather than folded into its walk: that method's per-class loop
/// starts with <c>if (!HasAnyProperties(template)) continue;</c>,
/// which would skip the Rules-ancestor check for any class that
/// declares Rules but no Properties. Keeping the walk separate — at
/// the cost of rebuilding the byId/byName lookup tables — avoids
/// that guard clause entirely so every ClassModel with a parent gets
/// checked regardless of its own property count.
/// </remarks>
private static void MarkParentHasRules(
List<ITemplateModel> templates,
IEnumerable<MTConnectClassModel> importClassModels)
{
if (templates == null) return;

var classTemplates = templates.OfType<MTConnectClassModel>().ToList();
if (classTemplates.Count == 0) return;

var byId = new Dictionary<string, MTConnectClassModel>(StringComparer.Ordinal);
var byName = new Dictionary<string, MTConnectClassModel>(StringComparer.Ordinal);
foreach (var ct in classTemplates)
{
if (!string.IsNullOrEmpty(ct.Id)) byId.TryAdd(ct.Id, ct);
if (!string.IsNullOrEmpty(ct.Name)) byName.TryAdd(ct.Name, ct);
}
if (importClassModels != null)
{
foreach (var cm in importClassModels)
{
if (cm == null) continue;
if (!string.IsNullOrEmpty(cm.Id)) byId.TryAdd(cm.Id, cm);
if (!string.IsNullOrEmpty(cm.Name)) byName.TryAdd(cm.Name, cm);
}
}

foreach (var template in classTemplates)
{
if (template is not ClassModel classModel) continue;
if (string.IsNullOrEmpty(template.ParentName)) continue;

var visited = new HashSet<string>(StringComparer.Ordinal);
var currentId = template.Id;
var parentName = template.ParentName;
var parentHasRules = false;

while (!string.IsNullOrEmpty(parentName))
{
var parent = ResolveParent(currentId, parentName, byId, byName);
if (parent == null) break;
if (!visited.Add(parent.Id ?? parentName)) break;
if (parent.Rules != null && parent.Rules.Length > 0)
{
parentHasRules = true;
break;
}
currentId = parent.Id;
parentName = parent.ParentName;
}

classModel.ParentHasRules = parentHasRules;
}
}

/// <summary>
/// Resolves a parent ClassModel from <paramref name="parentName"/>
/// (a bare <c>ClassName</c> as stored in
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
// Copyright (c) 2024 TrakHound Inc., All Rights Reserved.
// TrakHound Inc. licenses this file to you under the MIT license.
{{-# Shape-B consolidated Descriptions template. Valid for every MTConnect version. #}}
{{-# Covers both the enum-descriptions and the string-constant-descriptions callers. #}}
{{-# When is_string is truthy, the Get(...) overload takes a `string value` and the #}}
{{-# class doc reads "string constant" instead of "value"; when falsy, the Get(...) #}}
{{-# overload takes an enum-typed value and the class doc reads "value". #}}

namespace {{namespace}}
{
/// <summary>
/// Description text for each <see cref="{{name}}"/> value as defined by the MTConnect Standard.
/// Description text for each <see cref="{{name}}"/> {{ if is_string }}string constant{{ else }}value{{ end }} as defined by the MTConnect Standard.
/// </summary>
public static class {{name}}Descriptions
{
Expand All @@ -21,9 +26,9 @@ namespace {{namespace}}


/// <summary>
/// Returns the MTConnect Standard description text for the specified <see cref="{{name}}"/> value, or <c>null</c> when none is defined.
/// Returns the MTConnect Standard description text for the specified{{ if is_string }}{{ else }} <see cref="{{name}}"/>{{ end }} value, or <c>null</c> when none is defined.
/// </summary>
public static string Get({{name}} value)
public static string Get({{ if is_string }}string{{ else }}{{name}}{{ end }} value)
{
switch (value)
{
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace {{namespace}}
/// SysML model, preserved verbatim so downstream consumers can
/// inspect the spec's raw validation rules at runtime.
/// </summary>
public {{ if (parent_name) }}new {{ end }}static readonly string[] Rules = new[]
public {{ if (parent_has_rules) }}new {{ end }}static readonly string[] Rules = new[]
{
{{- for rule in rules }}{{ if (!for.last) }}
"{{ rule | string.replace `\` `\\` | string.replace `"` `\"` | string.replace "\r" "\\r" | string.replace "\n" "\\n" | string.replace "\t" "\\t" }}",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) 2025 TrakHound Inc., All Rights Reserved.
// TrakHound Inc. licenses this file to you under the MIT license.

namespace {{namespace}}
{
/// <summary>
/// {{description}}
/// </summary>
public interface I{{name}} : IMeasurement
{
}
}
Loading
Loading