diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 71ecdfd33..28057a043 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -7,6 +7,12 @@ "commands": [ "reportgenerator" ] + }, + "dotnet-stryker": { + "version": "4.16.0", + "commands": [ + "stryker" + ] } } } diff --git a/MTConnect.NET.sln b/MTConnect.NET.sln index 5f678ae53..b7daadfae 100644 --- a/MTConnect.NET.sln +++ b/MTConnect.NET.sln @@ -141,6 +141,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MTConnect.NET-Tests-Agents" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MTConnect.NET-HTTP-Tests", "tests\MTConnect.NET-HTTP-Tests\MTConnect.NET-HTTP-Tests.csproj", "{3E89B860-A428-470C-8E48-0DDABC4027F0}" 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 @@ -509,6 +511,14 @@ Global {3E89B860-A428-470C-8E48-0DDABC4027F0}.Package|Any CPU.Build.0 = Debug|Any CPU {3E89B860-A428-470C-8E48-0DDABC4027F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {3E89B860-A428-470C-8E48-0DDABC4027F0}.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 @@ -565,6 +575,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} + {8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57} = {14375E03-6BF8-45E6-B868-D2399368992B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CC13D3AD-18BF-4695-AB2A-087EF0885B20} diff --git a/build/MTConnect.NET-DocsGen/CliInventory.cs b/build/MTConnect.NET-DocsGen/CliInventory.cs index e6ca63b0e..a507d5218 100644 --- a/build/MTConnect.NET-DocsGen/CliInventory.cs +++ b/build/MTConnect.NET-DocsGen/CliInventory.cs @@ -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 neighbour'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, diff --git a/build/MTConnect.NET-SysML-Import/CSharp/EnumModel.cs b/build/MTConnect.NET-SysML-Import/CSharp/EnumModel.cs index be6b5358a..d8c83a92e 100644 --- a/build/MTConnect.NET-SysML-Import/CSharp/EnumModel.cs +++ b/build/MTConnect.NET-SysML-Import/CSharp/EnumModel.cs @@ -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); } } diff --git a/build/MTConnect.NET-SysML-Import/CSharp/EnumStringModel.cs b/build/MTConnect.NET-SysML-Import/CSharp/EnumStringModel.cs index 654dacf1c..1e88f9c3c 100644 --- a/build/MTConnect.NET-SysML-Import/CSharp/EnumStringModel.cs +++ b/build/MTConnect.NET-SysML-Import/CSharp/EnumStringModel.cs @@ -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() { } @@ -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); } } diff --git a/build/MTConnect.NET-SysML-Import/CSharp/MeasurementModel.cs b/build/MTConnect.NET-SysML-Import/CSharp/MeasurementModel.cs index 5ac65a18f..2c9c01342 100644 --- a/build/MTConnect.NET-SysML-Import/CSharp/MeasurementModel.cs +++ b/build/MTConnect.NET-SysML-Import/CSharp/MeasurementModel.cs @@ -66,7 +66,11 @@ public string RenderModel() } /// - public string RenderInterface() => null; + public string RenderInterface() + { + var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "Pallets.MeasurementInterface.scriban"); + return template.Render(this); + } /// public string RenderDescriptions() => null; diff --git a/build/MTConnect.NET-SysML-Import/CSharp/ObservationModel.cs b/build/MTConnect.NET-SysML-Import/CSharp/ObservationModel.cs index b788e2a0a..d89a4f260 100644 --- a/build/MTConnect.NET-SysML-Import/CSharp/ObservationModel.cs +++ b/build/MTConnect.NET-SysML-Import/CSharp/ObservationModel.cs @@ -72,7 +72,7 @@ public string RenderModel() /// public string RenderDescriptions() { - var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumDescriptions.scriban"); + var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumOrStringDescriptions.scriban"); return template.Render(this); } } diff --git a/build/MTConnect.NET-SysML-Import/CSharp/TemplateRenderer.cs b/build/MTConnect.NET-SysML-Import/CSharp/TemplateRenderer.cs index 8b9bbec83..010b497e9 100644 --- a/build/MTConnect.NET-SysML-Import/CSharp/TemplateRenderer.cs +++ b/build/MTConnect.NET-SysML-Import/CSharp/TemplateRenderer.cs @@ -643,20 +643,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; } diff --git a/build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumDescriptions.scriban b/build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumOrStringDescriptions.scriban similarity index 53% rename from build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumDescriptions.scriban rename to build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumOrStringDescriptions.scriban index 16691ed33..23d908b3d 100644 --- a/build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumDescriptions.scriban +++ b/build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumOrStringDescriptions.scriban @@ -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}} { /// - /// Description text for each value as defined by the MTConnect Standard. + /// Description text for each {{ if is_string }}string constant{{ else }}value{{ end }} as defined by the MTConnect Standard. /// public static class {{name}}Descriptions { @@ -21,9 +26,9 @@ namespace {{namespace}} /// - /// Returns the MTConnect Standard description text for the specified value, or null when none is defined. + /// Returns the MTConnect Standard description text for the specified{{ if is_string }}{{ else }} {{ end }} value, or null when none is defined. /// - public static string Get({{name}} value) + public static string Get({{ if is_string }}string{{ else }}{{name}}{{ end }} value) { switch (value) { diff --git a/build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumStringDescriptions.scriban b/build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumStringDescriptions.scriban deleted file mode 100644 index f85216280..000000000 --- a/build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumStringDescriptions.scriban +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2024 TrakHound Inc., All Rights Reserved. -// TrakHound Inc. licenses this file to you under the MIT license. - -namespace {{namespace}} -{ - /// - /// Description text for each string constant as defined by the MTConnect Standard. - /// - public static class {{name}}Descriptions - { -{{- i = 0 }}{{- for value in values }}{{ i = i + 1 }} - /// - /// {{value.description}} - /// - public const string {{value.name}} = "{{value.description}}"; - {{- if (i < (values | array.size)) }} - {{ end }} -{{- end }} - -{{- if ((values | array.size) > 0) }}{{ i = 0 }} - - - /// - /// Returns the MTConnect Standard description text for the specified value, or null when none is defined. - /// - public static string Get(string value) - { - switch (value) - { -{{- for value in values }}{{ i = i + 1 }} - case {{name}}.{{value.name}}: return "{{value.description}}"; -{{- end }} - } - - return null; - } -{{- end }} - } -} \ No newline at end of file diff --git a/build/MTConnect.NET-SysML-Import/CSharp/Templates/Pallets.MeasurementInterface.scriban b/build/MTConnect.NET-SysML-Import/CSharp/Templates/Pallets.MeasurementInterface.scriban new file mode 100644 index 000000000..97dcf30ea --- /dev/null +++ b/build/MTConnect.NET-SysML-Import/CSharp/Templates/Pallets.MeasurementInterface.scriban @@ -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}} +{ + /// + /// {{description}} + /// + public interface I{{name}} : IMeasurement + { + } +} \ No newline at end of file diff --git a/build/MTConnect.NET-SysML-Import/Program.cs b/build/MTConnect.NET-SysML-Import/Program.cs index b4ad131b7..706556438 100644 --- a/build/MTConnect.NET-SysML-Import/Program.cs +++ b/build/MTConnect.NET-SysML-Import/Program.cs @@ -2,39 +2,100 @@ using MTConnect.SysML.CSharp; using MTConnect.SysML.Json_cppagent; using MTConnect.SysML.Xml; +using System.Diagnostics; using System.Linq; +using System.Text; using System.Text.Json; +using System.Text.RegularExpressions; // SysML importer entry point. Runs on Linux / macOS / Windows / CI. // // Usage: // dotnet run --project build/MTConnect.NET-SysML-Import \ -// -- --xmi \ +// -- --new-xmi \ // --output \ +// [--previous-xmi ] \ +// [--compat-version-label