Skip to content

Commit cdafee2

Browse files
committed
Merge feat/dry-generator-phase1-consolidation (PR TrakHound#233) into integration/up-to-pr-233 on top of integration/up-to-pr-232
2 parents 4dad061 + 987122a commit cdafee2

207 files changed

Lines changed: 5984 additions & 1891 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.config/dotnet-tools.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
"commands": [
88
"reportgenerator"
99
]
10+
},
11+
"dotnet-stryker": {
12+
"version": "4.16.0",
13+
"commands": [
14+
"stryker"
15+
]
1016
}
1117
}
1218
}

MTConnect.NET.sln

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MTConnect.NET-HTTP-Tests",
143143
EndProject
144144
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}"
145145
EndProject
146+
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}"
147+
EndProject
146148
Global
147149
GlobalSection(SolutionConfigurationPlatforms) = preSolution
148150
Debug|Any CPU = Debug|Any CPU
@@ -519,6 +521,14 @@ Global
519521
{6CE969D2-A1E8-4BC1-85D8-303701B42F64}.Package|Any CPU.Build.0 = Debug|Any CPU
520522
{6CE969D2-A1E8-4BC1-85D8-303701B42F64}.Release|Any CPU.ActiveCfg = Release|Any CPU
521523
{6CE969D2-A1E8-4BC1-85D8-303701B42F64}.Release|Any CPU.Build.0 = Release|Any CPU
524+
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
525+
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Debug|Any CPU.Build.0 = Debug|Any CPU
526+
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
527+
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Docker|Any CPU.Build.0 = Debug|Any CPU
528+
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Package|Any CPU.ActiveCfg = Debug|Any CPU
529+
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Package|Any CPU.Build.0 = Debug|Any CPU
530+
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Release|Any CPU.ActiveCfg = Release|Any CPU
531+
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57}.Release|Any CPU.Build.0 = Release|Any CPU
522532
EndGlobalSection
523533
GlobalSection(SolutionProperties) = preSolution
524534
HideSolutionNode = FALSE
@@ -576,7 +586,7 @@ Global
576586
{17E64F59-0E62-4FCE-BEC4-EABBCF95B9A2} = {BBF53739-168D-4635-8595-083AC0C65E4C}
577587
{AE09D1CA-5572-40BF-B984-74230E8634E1} = {14375E03-6BF8-45E6-B868-D2399368992B}
578588
{3E89B860-A428-470C-8E48-0DDABC4027F0} = {14375E03-6BF8-45E6-B868-D2399368992B}
579-
{6CE969D2-A1E8-4BC1-85D8-303701B42F64} = {14375E03-6BF8-45E6-B868-D2399368992B}
589+
{8B61CE3B-DC8A-47CE-A34B-38BC57DFFD57} = {14375E03-6BF8-45E6-B868-D2399368992B}
580590
EndGlobalSection
581591
GlobalSection(ExtensibilityGlobals) = postSolution
582592
SolutionGuid = {CC13D3AD-18BF-4695-AB2A-087EF0885B20}

build/MTConnect.NET-DocsGen/CliInventory.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,10 +342,15 @@ private static CliInfo CollectDotNetTool(string name, string file, string repoRo
342342
if (headerDescs.TryGetValue(flagName, out var headerDesc)) desc = headerDesc;
343343
desc ??= ExtractDotnetFlagDescription(text, flagName);
344344

345-
// Detect whether the case body calls `RequireValue` — if it
346-
// does, the flag takes a value.
345+
// Detect whether the case body calls `RequireValue` — if it does,
346+
// the flag takes a value. The scan is bounded to the CURRENT case
347+
// block only: it stops at the next `case "…":` label, a `default:`
348+
// label, or a `break;` terminator, so a boolean flag whose case
349+
// body sits above a value-taking case (like `--full-tree` above
350+
// `case "--output": … RequireValue(…)`) does not falsely inherit
351+
// the neighbour's value shape.
347352
bool takesValue = Regex.IsMatch(text,
348-
$@"case\s+""{Regex.Escape(flagName)}""\s*:[\s\S]{{0,200}}?RequireValue");
353+
$@"case\s+""{Regex.Escape(flagName)}""\s*:(?:(?!\s*case\s+""|\s*default\s*:|\bbreak\s*;)[\s\S])*?RequireValue");
349354
flags.Add(new CliFlag(
350355
Name: flagName,
351356
Short: null,

build/MTConnect.NET-SysML-Import/CSharp/ClassModel.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,22 @@ public class ClassModel : MTConnectClassModel, ITemplateModel
7575
/// </summary>
7676
public new List<PropertyModel> Properties { get; set; } = new();
7777

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

7995
/// <summary>
8096
/// Parameterless constructor used by the import pipeline when it

build/MTConnect.NET-SysML-Import/CSharp/EnumModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public string RenderModel()
9393
public string RenderDescriptions()
9494
{
9595
if (Values == null || Values.Count == 0) return null;
96-
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumDescriptions.scriban");
96+
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumOrStringDescriptions.scriban");
9797
return template.Render(this);
9898
}
9999
}

build/MTConnect.NET-SysML-Import/CSharp/EnumStringModel.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ internal class EnumStringModel : MTConnectEnumModel, ITemplateModel
1414

1515
public bool IsPartial { get; set; }
1616

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

1826
public EnumStringModel() { }
1927

@@ -88,7 +96,7 @@ public string RenderModel()
8896

8997
public string RenderDescriptions()
9098
{
91-
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumStringDescriptions.scriban");
99+
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumOrStringDescriptions.scriban");
92100
return template.Render(this);
93101
}
94102
}

build/MTConnect.NET-SysML-Import/CSharp/MeasurementModel.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ public string RenderModel()
6666
}
6767

6868
/// <inheritdoc />
69-
public string RenderInterface() => null;
69+
public string RenderInterface()
70+
{
71+
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "Pallets.MeasurementInterface.scriban");
72+
return template.Render(this);
73+
}
7074

7175
/// <inheritdoc />
7276
public string RenderDescriptions() => null;

build/MTConnect.NET-SysML-Import/CSharp/ObservationModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public string RenderModel()
7272
/// <inheritdoc />
7373
public string RenderDescriptions()
7474
{
75-
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumDescriptions.scriban");
75+
var template = TemplateLoader.LoadOrThrow("CSharp", "Templates", "EnumOrStringDescriptions.scriban");
7676
return template.Render(this);
7777
}
7878
}

build/MTConnect.NET-SysML-Import/CSharp/TemplateRenderer.cs

Lines changed: 98 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,15 @@ public static void Render(MTConnectModel mtconnectModel, string outputPath)
306306
// so its `Code` property hides Measurement.Code and needs `new`.
307307
MarkInheritedProperties(templates, classModels);
308308

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

310319
foreach (var template in templates)
311320
{
@@ -642,20 +651,19 @@ private static void MarkInheritedProperties(
642651
break;
643652

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

@@ -701,6 +709,82 @@ private static void MarkInheritedProperties(
701709
}
702710
}
703711

712+
/// <summary>
713+
/// Marks each <see cref="ClassModel.ParentHasRules"/> flag by walking
714+
/// the <see cref="MTConnectClassModel.ParentName"/> ancestor chain and
715+
/// checking whether any ancestor declares a non-empty
716+
/// <see cref="MTConnectClassModel.Rules"/> array. <c>Model.scriban</c>
717+
/// uses the flag to decide whether the generated <c>Rules</c> field
718+
/// needs the <c>new</c> modifier — mere parent presence is not
719+
/// sufficient, since a class can extend a parent that itself carries
720+
/// no <c>Rules</c> (e.g. <c>Axis : AbstractAxis</c>, where
721+
/// <c>AbstractAxis</c> has no <c>Rules</c>). Emitting <c>new</c> in
722+
/// that case hides nothing and the compiler raises CS0109.
723+
/// </summary>
724+
/// <remarks>
725+
/// Deliberately independent of <see cref="MarkInheritedProperties"/>
726+
/// rather than folded into its walk: that method's per-class loop
727+
/// starts with <c>if (!HasAnyProperties(template)) continue;</c>,
728+
/// which would skip the Rules-ancestor check for any class that
729+
/// declares Rules but no Properties. Keeping the walk separate — at
730+
/// the cost of rebuilding the byId/byName lookup tables — avoids
731+
/// that guard clause entirely so every ClassModel with a parent gets
732+
/// checked regardless of its own property count.
733+
/// </remarks>
734+
private static void MarkParentHasRules(
735+
List<ITemplateModel> templates,
736+
IEnumerable<MTConnectClassModel> importClassModels)
737+
{
738+
if (templates == null) return;
739+
740+
var classTemplates = templates.OfType<MTConnectClassModel>().ToList();
741+
if (classTemplates.Count == 0) return;
742+
743+
var byId = new Dictionary<string, MTConnectClassModel>(StringComparer.Ordinal);
744+
var byName = new Dictionary<string, MTConnectClassModel>(StringComparer.Ordinal);
745+
foreach (var ct in classTemplates)
746+
{
747+
if (!string.IsNullOrEmpty(ct.Id)) byId.TryAdd(ct.Id, ct);
748+
if (!string.IsNullOrEmpty(ct.Name)) byName.TryAdd(ct.Name, ct);
749+
}
750+
if (importClassModels != null)
751+
{
752+
foreach (var cm in importClassModels)
753+
{
754+
if (cm == null) continue;
755+
if (!string.IsNullOrEmpty(cm.Id)) byId.TryAdd(cm.Id, cm);
756+
if (!string.IsNullOrEmpty(cm.Name)) byName.TryAdd(cm.Name, cm);
757+
}
758+
}
759+
760+
foreach (var template in classTemplates)
761+
{
762+
if (template is not ClassModel classModel) continue;
763+
if (string.IsNullOrEmpty(template.ParentName)) continue;
764+
765+
var visited = new HashSet<string>(StringComparer.Ordinal);
766+
var currentId = template.Id;
767+
var parentName = template.ParentName;
768+
var parentHasRules = false;
769+
770+
while (!string.IsNullOrEmpty(parentName))
771+
{
772+
var parent = ResolveParent(currentId, parentName, byId, byName);
773+
if (parent == null) break;
774+
if (!visited.Add(parent.Id ?? parentName)) break;
775+
if (parent.Rules != null && parent.Rules.Length > 0)
776+
{
777+
parentHasRules = true;
778+
break;
779+
}
780+
currentId = parent.Id;
781+
parentName = parent.ParentName;
782+
}
783+
784+
classModel.ParentHasRules = parentHasRules;
785+
}
786+
}
787+
704788
/// <summary>
705789
/// Resolves a parent ClassModel from <paramref name="parentName"/>
706790
/// (a bare <c>ClassName</c> as stored in

build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumDescriptions.scriban renamed to build/MTConnect.NET-SysML-Import/CSharp/Templates/EnumOrStringDescriptions.scriban

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
// Copyright (c) 2024 TrakHound Inc., All Rights Reserved.
22
// TrakHound Inc. licenses this file to you under the MIT license.
3+
{{-# Shape-B consolidated Descriptions template. Valid for every MTConnect version. #}}
4+
{{-# Covers both the enum-descriptions and the string-constant-descriptions callers. #}}
5+
{{-# When is_string is truthy, the Get(...) overload takes a `string value` and the #}}
6+
{{-# class doc reads "string constant" instead of "value"; when falsy, the Get(...) #}}
7+
{{-# overload takes an enum-typed value and the class doc reads "value". #}}
38

49
namespace {{namespace}}
510
{
611
/// <summary>
7-
/// Description text for each <see cref="{{name}}"/> value as defined by the MTConnect Standard.
12+
/// Description text for each <see cref="{{name}}"/> {{ if is_string }}string constant{{ else }}value{{ end }} as defined by the MTConnect Standard.
813
/// </summary>
914
public static class {{name}}Descriptions
1015
{
@@ -21,9 +26,9 @@ namespace {{namespace}}
2126

2227

2328
/// <summary>
24-
/// Returns the MTConnect Standard description text for the specified <see cref="{{name}}"/> value, or <c>null</c> when none is defined.
29+
/// 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.
2530
/// </summary>
26-
public static string Get({{name}} value)
31+
public static string Get({{ if is_string }}string{{ else }}{{name}}{{ end }} value)
2732
{
2833
switch (value)
2934
{

0 commit comments

Comments
 (0)