From 0b72c742d2bdf4c4be51b2f1979706b67e434725 Mon Sep 17 00:00:00 2001 From: teramako Date: Tue, 9 Sep 2025 11:47:34 +0000 Subject: [PATCH 01/12] Add syntax parameters tests for Import-MamlHelp for - https://github.com/PowerShell/platyPS/issues/815 - https://github.com/PowerShell/platyPS/issues/816 --- test/Pester/ImportMamlHelp.Tests.ps1 | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/test/Pester/ImportMamlHelp.Tests.ps1 b/test/Pester/ImportMamlHelp.Tests.ps1 index d50d280c..9365b7bf 100644 --- a/test/Pester/ImportMamlHelp.Tests.ps1 +++ b/test/Pester/ImportMamlHelp.Tests.ps1 @@ -46,5 +46,31 @@ Describe "Import-YamlHelp tests" { $cmdlet.Metadata[$key] | Should -Be $Value } } + + Context "Syntax parameters checks" { + It "Add-Member cmdlet has correct number of syntax parameters" { + $cmdlet.Syntax[0].SyntaxParameters.Count | Should -be 8 + } + + It "'' of syntax parameter for Add-Member cmdlet has correct property values" -testcases @( + @{ Name = 'MemberType'; Type = 'System.Management.Automation.PSMemberTypes'; Position = '0'; IsMandatory = $true; IsPositional = $true; IsSwitchParameter = $false }, + @{ Name = 'Name'; Type = 'System.String'; Position = '1'; IsMandatory = $true; IsPositional = $true; IsSwitchParameter = $false }, + @{ Name = 'Value'; Type = 'System.Object'; Position = '2'; IsMandatory = $false; IsPositional = $true; IsSwitchParameter = $false }, + @{ Name = 'SecondValue'; Type = 'System.Object'; Position = '3'; IsMandatory = $false; IsPositional = $true; IsSwitchParameter = $false }, + @{ Name = 'Force'; Type = 'System.Management.Automation.SwitchParameter'; Position = 'named'; IsMandatory = $false; IsPositional = $false; IsSwitchParameter = $true }, + @{ Name = 'InputObject'; Type = 'System.Management.Automation.PSObject'; Position = 'named'; IsMandatory = $true; IsPositional = $false; IsSwitchParameter = $false }, + @{ Name = 'PassThru'; Type = 'System.Management.Automation.SwitchParameter'; Position = 'named'; IsMandatory = $false; IsPositional = $false; IsSwitchParameter = $true }, + @{ Name = 'TypeName'; Type = 'System.String'; Position = 'named'; IsMandatory = $false; IsPositional = $false; IsSwitchParameter = $false } + ) { + param ([string]$Name, [string]$Type, [string]$Position, [bool]$IsMandatory, [bool]$IsPositional, [bool]$IsSwitchParameter) + $syntaxParam = $cmdlet.Syntax[0].SyntaxParameters.Where({$_.ParameterName -eq $Name}) + $syntaxParam.ParameterName | Should -be $Name + $syntaxParam.ParameterType | Should -be $Type + $syntaxParam.Position | should -be $Position + $syntaxParam.IsMandatory | should -be $IsMandatory + $syntaxParam.IsPositional | should -be $IsPositional + $syntaxParam.IsSwitchParameter | should -be $IsSwitchParameter + } + } } -} \ No newline at end of file +} From 0dafebfcbd74073b18c1be65317e7cfa1ad03ef3 Mon Sep 17 00:00:00 2001 From: teramako Date: Tue, 9 Sep 2025 20:56:23 +0900 Subject: [PATCH 02/12] Fix issues related syntax parameters in reading and writing Maml help Fix following issues: MamlWriter: - generates syntax parameters as wrong order. The order should be positional parameters, mandatory parameters, and others MamlReader: - https://github.com/PowerShell/platyPS/issues/816 - Read `position` attribute correctly. And treat the parameter is positional or not. - Read `` correctly as ParameterType. (if `` is not defined, read '`) And treat the type is SwitchParameter or not. - https://github.com/PowerShell/platyPS/issues/815 - Fixed problem that `Parameter` instances are registered as duplicately --- src/MamlWriter/MamlHelpers.cs | 22 +++++-- src/Transform/TransformMaml.cs | 115 ++++++++++++++++++++++++--------- 2 files changed, 103 insertions(+), 34 deletions(-) diff --git a/src/MamlWriter/MamlHelpers.cs b/src/MamlWriter/MamlHelpers.cs index bc9025d7..dc056c2e 100644 --- a/src/MamlWriter/MamlHelpers.cs +++ b/src/MamlWriter/MamlHelpers.cs @@ -154,10 +154,8 @@ private static SyntaxItem ConvertSyntax(Model.SyntaxItem syntax) { newSyntax.CommandName = syntax.CommandName.Substring(0, firstSpace); } - foreach(var parameter in syntax.GetParametersInOrder()) - { - newSyntax.Parameters.Add(ConvertParameter(parameter)); - } + syntax.SortParameters(); + newSyntax.Parameters.AddRange(syntax.SyntaxParameters.Select(ConvertSyntaxParameter)); return newSyntax; } @@ -204,6 +202,22 @@ private static Parameter ConvertParameter(Model.Parameter parameter) return newParameter; } + private static Parameter ConvertSyntaxParameter(Model.SyntaxParameter syntaxParam) + { + var newParameter = new MAML.Parameter() + { + Name = syntaxParam.ParameterName, + IsMandatory = syntaxParam.IsMandatory, + Position = syntaxParam.Position, + Value = new MAML.ParameterValue() + { + DataType = syntaxParam.ParameterType, + IsMandatory = true, + }, + }; + return newParameter; + } + private static CommandExample ConvertExample(Example example, int exampleNumber) { var tempDescription = new List(); diff --git a/src/Transform/TransformMaml.cs b/src/Transform/TransformMaml.cs index 9c3127a9..c0269f09 100644 --- a/src/Transform/TransformMaml.cs +++ b/src/Transform/TransformMaml.cs @@ -420,44 +420,29 @@ private Collection ReadSyntaxItems(XmlReader reader) unnamedParameterSetName, isDefaultParameterSet: false); + int position = int.MaxValue; + List positionalSyntaxParameters = []; while (reader.ReadToNextSibling(Constants.MamlCommandParameterTag)) { - var parameter = ReadParameter(reader.ReadSubtree(), parameterSetCount: -1); - syntaxItem.SyntaxParameters.Add(new SyntaxParameter(parameter)); - try + var syntaxParameter = ReadSyntaxParameter(reader.ReadSubtree()); + syntaxItem.SyntaxParameters.Add(syntaxParameter); + if (syntaxParameter.IsPositional) { - // This may possibly throw because the position is duplicated. - // This is because we don't have a way to disambiguate between a positional parameter - // in one parameter set and a non-positional parameter in another parameter set. - // In this case, we will try to add the parameter with a negative position to show we - // could not assign it appropriately. - syntaxItem.AddParameter(parameter); - syntaxItem.SyntaxParameters.Add(new SyntaxParameter(parameter)); - } - catch - { - int minKey = syntaxItem.PositionalParameterKeys.Min(x => x); - if (minKey >= 0) - { - minKey = -1; - } - else - { - minKey--; - } - - parameter.ParameterSets.ForEach(x => x.Position = minKey.ToString()); - try - { - syntaxItem.AddParameter(parameter); - } - catch (Exception exception) + positionalSyntaxParameters.Add(syntaxParameter); + // set the position value to the minimum value in each parameter + if (int.TryParse(syntaxParameter.Position, NumberStyles.None, CultureInfo.InvariantCulture, out var p)) { - throw new InvalidOperationException($"Error adding parameter '{parameter.Name}' to syntax item {commandName}", exception); + position = Math.Min(p, position); } } } + // re-numbering positional parameters from the minimum value + foreach (var p in positionalSyntaxParameters.OrderBy(p => int.Parse(p.Position))) + { + p.Position = (position++).ToString(); + } + foreach(var paramName in syntaxItem.ParameterNames) { if (_paramSetMap.ContainsKey(paramName)) @@ -476,6 +461,76 @@ private Collection ReadSyntaxItems(XmlReader reader) return null; } + private SyntaxParameter ReadSyntaxParameter(XmlReader reader) + { + string name = string.Empty; + string type = string.Empty; + string position = Constants.NamedString; + bool required = false; + + reader.Read(); + + if (reader.HasAttributes) + { + if (reader.MoveToAttribute("required")) + { + bool.TryParse(reader.Value, out required); + } + + if (reader.MoveToAttribute("position")) + { + // Value is like '0' or 'named' + position = reader.Value; + } + + reader.MoveToElement(); + } + + if (reader.ReadToDescendant(Constants.MamlNameTag)) + { + name = reader.ReadElementContentAsString(); + } + + // We read the next element and check the name as it could parameterValue or dev:type + while (reader.Read()) + { + if (string.Equals(reader.Name, Constants.MamlCommandParameterValueTag, StringComparison.OrdinalIgnoreCase)) + { + type = reader.ReadElementContentAsString(); + } + else if (string.IsNullOrEmpty(type) + && string.Equals(reader.Name, Constants.MamlDevTypeTag, StringComparison.OrdinalIgnoreCase)) + { + if (reader.ReadToDescendant(Constants.MamlNameTag)) + { + type = reader.ReadElementContentAsString(); + } + } + } + + if (string.IsNullOrEmpty(type)) + { + throw new InvalidDataException($"Invalid syntaxItem.parameter data: or is none"); + } + + SyntaxParameter syntaxParameter = new SyntaxParameter(name) + { + IsMandatory = required, + ParameterType = type, + IsPositional = int.TryParse(position, NumberStyles.None, CultureInfo.InvariantCulture, out _), + Position = position, + IsSwitchParameter = type is "SwitchParameter" or "System.Management.Automation.SwitchParameter", + }; + + // need to go the end of command:parameter + if (reader.ReadState != ReadState.EndOfFile) + { + reader.ReadEndElement(); + } + + return syntaxParameter; + } + private Parameter ReadParameter(XmlReader reader, int parameterSetCount) { string name = string.Empty; From a88a7c08257ffbf96829911e1ae8a5d71d93a5c4 Mon Sep 17 00:00:00 2001 From: teramako Date: Wed, 10 Sep 2025 11:07:38 +0000 Subject: [PATCH 03/12] Refactoring remove members in Mode.SyntaxItem that are no longer used - Remove property: `Model.SyntaxItem.PositionalParameterKeys` - Remove field: `Model.SyntaxItem._positionalParameters` - Remove field: `Model.SyntaxItem._requiredParameters` - Remove method: `GetParametersInOrder()` --- src/Model/SyntaxItem.cs | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/Model/SyntaxItem.cs b/src/Model/SyntaxItem.cs index e1741163..d90f0485 100644 --- a/src/Model/SyntaxItem.cs +++ b/src/Model/SyntaxItem.cs @@ -28,16 +28,6 @@ public ReadOnlyCollection ParameterNames { get => new ReadOnlyCollection(_parameterNames); } - public ReadOnlyCollection PositionalParameterKeys { - get => new ReadOnlyCollection(_positionalParameters.Keys); - } - - // Sort parameters by position - private SortedList _positionalParameters; - - // Sort parameters by if they are Required by name - private SortedList _requiredParameters; - // Sort parameters by name private SortedList _alphabeticOrderParameters; @@ -49,8 +39,6 @@ public SyntaxItem(string commandName, string parameterSetName, bool isDefaultPar ParameterSetName = parameterSetName; IsDefaultParameterSet = isDefaultParameterSet; - _positionalParameters = new SortedList(); - _requiredParameters = new SortedList(); _alphabeticOrderParameters = new SortedList(); } @@ -67,8 +55,6 @@ public SyntaxItem(SyntaxItem syntaxItem) HasCmdletBinding = syntaxItem.HasCmdletBinding; Parameters = new List(syntaxItem.Parameters); - _positionalParameters = new SortedList(syntaxItem._positionalParameters); - _requiredParameters = new SortedList(syntaxItem._requiredParameters); _alphabeticOrderParameters = new SortedList(syntaxItem._alphabeticOrderParameters); _parameterNames = new List(syntaxItem._parameterNames); } @@ -183,24 +169,6 @@ private string GetFormattedSyntaxParameter(string paramName, string paramTypeNam } } - public IEnumerable GetParametersInOrder() - { - foreach (KeyValuePair kv in _positionalParameters) - { - yield return kv.Value; - } - - foreach (KeyValuePair kv in _requiredParameters) - { - yield return kv.Value; - } - - foreach (KeyValuePair kv in _alphabeticOrderParameters) - { - yield return kv.Value; - } - } - /// /// This emits the command and parameters as if they were returned by Get-Command -syntax /// From 6397f362d69062d4d0a19dba92ecbedae70b232a Mon Sep 17 00:00:00 2001 From: teramako Date: Wed, 10 Sep 2025 12:54:38 +0000 Subject: [PATCH 04/12] Refactoring 2: make `Model.SyntaxItem` simpler - Remove codes related `SyntaxItem.Parameters`. Because all of these are no longer needed for syntax generation. - Remove `SyntaxItem._parameterNames` - Remove `SyntaxItem._alphabeticOrderParameters` - Remove `SyntaxItem.Parameters` - Remove `SyntaxItem.ParameterNames` - Remove `SyntaxItem.AddParameter()` - Added code to reset `SyntaxItem.HasCmdletBinding` value to `CommandHelp.HasCmdletBinding` when loading Markdown. This is the same behavior as when loading Yaml. - Fix tests: Since `SyntaxItem.ParameterNames` has been removed, differences in `CommandHelp.Syntax.ParameterNames` are no longer reported. --- src/Common/YamlUtils.cs | 2 - .../CommandHelpMarkdownReader.cs | 1 + src/Model/CommandHelp.cs | 28 ------------ src/Model/SyntaxItem.cs | 45 ------------------- src/Transform/TransformBase.cs | 4 -- src/Transform/TransformMaml.cs | 17 ------- test/Pester/CompareCommandHelp.Tests.ps1 | 20 ++++----- 7 files changed, 10 insertions(+), 107 deletions(-) diff --git a/src/Common/YamlUtils.cs b/src/Common/YamlUtils.cs index 3e7a0bda..950cc1ff 100644 --- a/src/Common/YamlUtils.cs +++ b/src/Common/YamlUtils.cs @@ -646,8 +646,6 @@ private static List GetSyntaxFromDictionary(OrderedDictionary dictio } } - // add the parameter name - si.Parameters.ForEach(p => si.AddParameter(p)); syntaxes.Add(si); } } diff --git a/src/MarkdownReader/CommandHelpMarkdownReader.cs b/src/MarkdownReader/CommandHelpMarkdownReader.cs index 5d85e244..b9ff7942 100644 --- a/src/MarkdownReader/CommandHelpMarkdownReader.cs +++ b/src/MarkdownReader/CommandHelpMarkdownReader.cs @@ -256,6 +256,7 @@ internal static CommandHelp GetCommandHelpFromMarkdown(ParsedMarkdownContent mar { syntaxDiagnostics.ForEach(d => commandHelp.Diagnostics.TryAddDiagnostic(d)); } + commandHelp.Syntax.ForEach(si => si.HasCmdletBinding = commandHelp.HasCmdletBinding); List aliasesDiagnostics = new(); bool aliasHeaderFound = false; diff --git a/src/Model/CommandHelp.cs b/src/Model/CommandHelp.cs index 7618e72c..5d527a46 100644 --- a/src/Model/CommandHelp.cs +++ b/src/Model/CommandHelp.cs @@ -202,34 +202,6 @@ internal void AddExampleItemRange(IEnumerable example) internal void AddParameter(Parameter parameter) { Parameters.Add(parameter); - foreach(var parameterSet in parameter.ParameterSets) - { - if (string.Compare(parameterSet.Name, "(All)", StringComparison.OrdinalIgnoreCase) == 0) - { - foreach(var syntax in SyntaxDictionary.Values) - { - try - { - syntax.AddParameter(parameter); - } - catch - { - // This is okay, we just don't want to add it to the syntax item if it's already there. - } - } - } - else if (SyntaxDictionary.TryGetValue(parameterSet.Name, out var syntaxItem)) - { - try - { - syntaxItem.AddParameter(parameter); - } - catch - { - // This is okay, we just don't want to add it to the syntax item if it's already there. - } - } - } } public bool TryGetParameter(string name, out Parameter? parameter) diff --git a/src/Model/SyntaxItem.cs b/src/Model/SyntaxItem.cs index d90f0485..2484e83f 100644 --- a/src/Model/SyntaxItem.cs +++ b/src/Model/SyntaxItem.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Text; @@ -20,17 +19,6 @@ public class SyntaxItem : IEquatable public List SyntaxParameters = new(); - public List Parameters = new(); - - private List _parameterNames = new(); - - public ReadOnlyCollection ParameterNames { - get => new ReadOnlyCollection(_parameterNames); - } - - // Sort parameters by name - private SortedList _alphabeticOrderParameters; - public bool IsDefaultParameterSet { get; } public SyntaxItem(string commandName, string parameterSetName, bool isDefaultParameterSet) @@ -38,8 +26,6 @@ public SyntaxItem(string commandName, string parameterSetName, bool isDefaultPar CommandName = commandName; ParameterSetName = parameterSetName; IsDefaultParameterSet = isDefaultParameterSet; - - _alphabeticOrderParameters = new SortedList(); } /// @@ -53,24 +39,6 @@ public SyntaxItem(SyntaxItem syntaxItem) IsDefaultParameterSet = syntaxItem.IsDefaultParameterSet; SyntaxParameters = new List(syntaxItem.SyntaxParameters); HasCmdletBinding = syntaxItem.HasCmdletBinding; - Parameters = new List(syntaxItem.Parameters); - - _alphabeticOrderParameters = new SortedList(syntaxItem._alphabeticOrderParameters); - _parameterNames = new List(syntaxItem._parameterNames); - } - - public void AddParameter(Parameter parameter) - { - string name = parameter.Name; - - if (Constants.CommonParametersNames.Contains(name)) - { - HasCmdletBinding = true; - return; - } - - _parameterNames.Add(name); - _alphabeticOrderParameters.Add(name, parameter); } /// @@ -116,19 +84,6 @@ public void SortParameters() SyntaxParameters = sortedList; } - public void AddParameter(SyntaxParameter parameter) - { - string name = parameter.ParameterName; - - if (Constants.CommonParametersNames.Contains(name)) - { - HasCmdletBinding = true; - return; - } - - _parameterNames.Add(name); - } - private string GetFormattedSyntaxParameter(string paramName, string paramTypeName, bool isPositional, bool isRequired) { bool isSwitchParam = string.Equals(paramTypeName, "SwitchParameter", StringComparison.OrdinalIgnoreCase); diff --git a/src/Transform/TransformBase.cs b/src/Transform/TransformBase.cs index e1fa9b78..78df4642 100644 --- a/src/Transform/TransformBase.cs +++ b/src/Transform/TransformBase.cs @@ -376,8 +376,6 @@ protected IEnumerable GetSyntaxItem(CommandInfo? cmdletInfo, dynamic string.Compare(paramInfo.ParameterType.Name, "SwitchParameter", true) == 0) ); } - Parameter param = GetParameterInfo(cmdletInfo, helpItem, paramInfo); - syn.AddParameter(param); } // now take the named parameters. @@ -393,8 +391,6 @@ protected IEnumerable GetSyntaxItem(CommandInfo? cmdletInfo, dynamic string.Compare(paramInfo.ParameterType.Name, "SwitchParameter", true) == 0); syn.SyntaxParameters.Add(sParm); } - Parameter param = GetParameterInfo(cmdletInfo, helpItem, paramInfo); - syn.AddParameter(param); } syntaxItems.Add(syn); diff --git a/src/Transform/TransformMaml.cs b/src/Transform/TransformMaml.cs index c0269f09..71128c4d 100644 --- a/src/Transform/TransformMaml.cs +++ b/src/Transform/TransformMaml.cs @@ -19,9 +19,6 @@ namespace Microsoft.PowerShell.PlatyPS { internal class TransformMaml : TransformBase { - // Dictionary of parameter name -> parameterset which it belongs - private Dictionary> _paramSetMap = new(); - public TransformMaml(TransformSettings settings) : base(settings) { } @@ -120,8 +117,6 @@ private Collection ReadMaml(string mamlFile) } cmdHelp.Metadata = MetadataUtils.GetCommandHelpBaseMetadata(cmdHelp); - - _paramSetMap.Clear(); } else { @@ -443,18 +438,6 @@ private Collection ReadSyntaxItems(XmlReader reader) p.Position = (position++).ToString(); } - foreach(var paramName in syntaxItem.ParameterNames) - { - if (_paramSetMap.ContainsKey(paramName)) - { - _paramSetMap[paramName].Add(unnamedParameterSetName); - } - else - { - _paramSetMap.Add(paramName, new List() { unnamedParameterSetName }); - } - } - return syntaxItem; } diff --git a/test/Pester/CompareCommandHelp.Tests.ps1 b/test/Pester/CompareCommandHelp.Tests.ps1 index 3f526f1a..20b5b632 100644 --- a/test/Pester/CompareCommandHelp.Tests.ps1 +++ b/test/Pester/CompareCommandHelp.Tests.ps1 @@ -15,23 +15,22 @@ Describe "Compare-CommandHelp can find differences" { } It "Should properly identify the number of differences" { - $result1.where({$_ -match "are not the same|are different"}).Count | Should -Be 11 + $result1.where({$_ -match "are not the same|are different"}).Count | Should -Be 7 } It "Should properly identify the elements which are different" { - $expected = "CommandHelp.Syntax.ParameterSetName", "CommandHelp.Syntax.ParameterNames", "CommandHelp.Syntax.ParameterSetName", - "CommandHelp.Syntax.ParameterNames", "CommandHelp.Syntax.ParameterSetName", "CommandHelp.Syntax.ParameterNames", - "CommandHelp.Syntax.ParameterSetName", "CommandHelp.Syntax.ParameterNames", "CommandHelp.Examples.Title", - "CommandHelp.Examples.Remarks", "CommandHelp.Diagnostics" + $expected = "CommandHelp.Syntax.ParameterSetName", "CommandHelp.Syntax.ParameterSetName", + "CommandHelp.Syntax.ParameterSetName", "CommandHelp.Syntax.ParameterSetName", + "CommandHelp.Examples.Title", "CommandHelp.Examples.Remarks", + "CommandHelp.Diagnostics" $observed = $result1.split("`n").Where({$_ -match "are not the same|are different"}).foreach({$_.Substring(2).trim().split()[0]}) $observed | Should -Be $expected } It "Should be possible to exclude an element from comparison" { - $expected = "CommandHelp.Syntax.ParameterSetName", "CommandHelp.Syntax.ParameterNames", "CommandHelp.Syntax.ParameterSetName", - "CommandHelp.Syntax.ParameterNames", "CommandHelp.Syntax.ParameterSetName", "CommandHelp.Syntax.ParameterNames", - "CommandHelp.Syntax.ParameterSetName", "CommandHelp.Syntax.ParameterNames", "CommandHelp.Examples.Title", - "CommandHelp.Examples.Remarks" + $expected = "CommandHelp.Syntax.ParameterSetName", "CommandHelp.Syntax.ParameterSetName", + "CommandHelp.Syntax.ParameterSetName", "CommandHelp.Syntax.ParameterSetName", + "CommandHelp.Examples.Title", "CommandHelp.Examples.Remarks" $observed = $result2.split("`n").Where({$_ -match "are not the same|are different"}).foreach({$_.Substring(2).trim().split()[0]}) $observed | Should -Be $expected @@ -39,8 +38,7 @@ Describe "Compare-CommandHelp can find differences" { It "Default excluded properties should be Diagnostics and ParameterNames" { $expected = "M excluding comparison of CommandHelp.AliasHeaderFound", - "M excluding comparison of CommandHelp.Diagnostics", - "M excluding comparison of CommandHelp.Syntax.ParameterNames" + "M excluding comparison of CommandHelp.Diagnostics" $observed = $result3.split("`n").Where({$_ -match "excluding comparison"})|Sort-Object -Unique $observed | Should -Be $expected } From 3063c94f2ed37e354b14c37309d2f3c7b46fa48f Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 12 Sep 2025 09:31:14 +0000 Subject: [PATCH 05/12] fix: wrong namespace of `name` element in `` --- src/MamlWriter/DataType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MamlWriter/DataType.cs b/src/MamlWriter/DataType.cs index c6cccfd3..4d9f36c5 100644 --- a/src/MamlWriter/DataType.cs +++ b/src/MamlWriter/DataType.cs @@ -16,7 +16,7 @@ public class DataType /// /// The data-type name. /// - [XmlElement("name", Namespace = Constants.XmlNamespace.Dev, Order = 0)] + [XmlElement("name", Namespace = Constants.XmlNamespace.MAML, Order = 0)] public string Name { get; set; } = string.Empty; /// From ff936279be991bb7a195acd765e411b62dbe61d9 Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 12 Sep 2025 09:34:51 +0000 Subject: [PATCH 06/12] Add `` in `` for Maml help generation --- src/MamlWriter/MamlHelpers.cs | 26 ++++++++++++++++++++++---- src/MamlWriter/Parameter.cs | 3 +++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/MamlWriter/MamlHelpers.cs b/src/MamlWriter/MamlHelpers.cs index dc056c2e..798fd06a 100644 --- a/src/MamlWriter/MamlHelpers.cs +++ b/src/MamlWriter/MamlHelpers.cs @@ -86,7 +86,7 @@ public static Command ConvertCommandHelpToMamlCommand(CommandHelp commandHelp) { foreach (var syntax in commandHelp.Syntax) { - command.Syntax.Add(ConvertSyntax(syntax)); + command.Syntax.Add(ConvertSyntax(syntax, commandHelp.Parameters)); } } @@ -142,7 +142,7 @@ private static IEnumerable ConvertInputOutput(List parameters) { var newSyntax = new SyntaxItem(); var firstSpace = syntax.CommandName.IndexOf(' '); @@ -155,7 +155,7 @@ private static SyntaxItem ConvertSyntax(Model.SyntaxItem syntax) newSyntax.CommandName = syntax.CommandName.Substring(0, firstSpace); } syntax.SortParameters(); - newSyntax.Parameters.AddRange(syntax.SyntaxParameters.Select(ConvertSyntaxParameter)); + newSyntax.Parameters.AddRange(syntax.SyntaxParameters.Select(sp => ConvertSyntaxParameter(sp, parameters))); return newSyntax; } @@ -202,7 +202,7 @@ private static Parameter ConvertParameter(Model.Parameter parameter) return newParameter; } - private static Parameter ConvertSyntaxParameter(Model.SyntaxParameter syntaxParam) + private static Parameter ConvertSyntaxParameter(Model.SyntaxParameter syntaxParam, List parameters) { var newParameter = new MAML.Parameter() { @@ -215,6 +215,24 @@ private static Parameter ConvertSyntaxParameter(Model.SyntaxParameter syntaxPara IsMandatory = true, }, }; + if (syntaxParam.IsSwitchParameter) + { + newParameter.Type = new DataType() + { + Name = "System.Management.Automation.SwitchParameter" + }; + } + else + { + var parameter = parameters.FirstOrDefault(p => string.Equals(p.Name, syntaxParam.ParameterName, StringComparison.Ordinal)); + if (parameter is not null) + { + newParameter.Type = new DataType() + { + Name = parameter.Type + }; + } + } return newParameter; } diff --git a/src/MamlWriter/Parameter.cs b/src/MamlWriter/Parameter.cs index 37a4f287..9518f399 100644 --- a/src/MamlWriter/Parameter.cs +++ b/src/MamlWriter/Parameter.cs @@ -31,6 +31,9 @@ public class Parameter [XmlElement("parameterValue", Namespace = Constants.XmlNamespace.Command, Order = 2)] public ParameterValue Value { get; set; } = new ParameterValue(); + [XmlElement("type", Namespace = Constants.XmlNamespace.Dev, Order = 3)] + public DataType? Type { get; set; } + /// /// Is the parameter mandatory? /// From ec74cbd05331e5105244091d71e203c6f31a04e8 Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 12 Sep 2025 10:13:04 +0000 Subject: [PATCH 07/12] Assign alias values for parameter in MAML help generation --- src/MamlWriter/MamlHelpers.cs | 9 +++++++++ test/Pester/ExportMamlCommandHelp.Tests.ps1 | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/src/MamlWriter/MamlHelpers.cs b/src/MamlWriter/MamlHelpers.cs index 798fd06a..35cb77c4 100644 --- a/src/MamlWriter/MamlHelpers.cs +++ b/src/MamlWriter/MamlHelpers.cs @@ -199,6 +199,11 @@ private static Parameter ConvertParameter(Model.Parameter parameter) } } + if (parameter.Aliases.Count > 0) + { + newParameter.Aliases = string.Join(", ", parameter.Aliases); + } + return newParameter; } @@ -231,6 +236,10 @@ private static Parameter ConvertSyntaxParameter(Model.SyntaxParameter syntaxPara { Name = parameter.Type }; + if (parameter.Aliases.Count > 0) + { + newParameter.Aliases = string.Join(", ", parameter.Aliases); + } } } return newParameter; diff --git a/test/Pester/ExportMamlCommandHelp.Tests.ps1 b/test/Pester/ExportMamlCommandHelp.Tests.ps1 index 619155bc..710dc04a 100644 --- a/test/Pester/ExportMamlCommandHelp.Tests.ps1 +++ b/test/Pester/ExportMamlCommandHelp.Tests.ps1 @@ -143,5 +143,10 @@ Describe "Export-MamlCommandHelp tests" { $maml = Get-Content -Path $mamlFile -Raw $maml | Should -BeLike '*https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/get-date?view=powershell-7.4&WT.mc_id=ps-gethelp*' } + + It "Should have the proper aliases for Get-Date" { + $xml2.SelectNodes('//command:command', $ns2).Where({$_.details.name -eq "Get-Date"}).Parameters.parameter. + Where({$_.Name -in @('Date', 'UnixTimeSeconds')}).aliases | Should -Be 'LastWriteTime', 'UnixTime' + } } } From f10f75d9be25f4d22a6ca091052870c5941e670b Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 12 Sep 2025 12:13:37 +0000 Subject: [PATCH 08/12] don't render if the parameter is SwitchParameter If the parameter is a SwitchParameter, skip rendering the element. Instead, use the element to represent the formal parameter type. This change will result in the following changes in the help display SYNTAX: ... `[-Confirm ]` -> ... `[-Confirm]` PARAMETERS: `-Confirm []` -> `-Confirm` --- src/MamlWriter/MamlHelpers.cs | 20 ++++++++++++++------ src/MamlWriter/Parameter.cs | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/MamlWriter/MamlHelpers.cs b/src/MamlWriter/MamlHelpers.cs index 35cb77c4..47d6f5c6 100644 --- a/src/MamlWriter/MamlHelpers.cs +++ b/src/MamlWriter/MamlHelpers.cs @@ -167,8 +167,14 @@ private static PipelineInputType GetPipelineInputType(Model.Parameter parameter) return pipelineInput; } - private static ParameterValue GetParameterValue(Model.Parameter parameter) + private static ParameterValue? GetParameterValue(Model.Parameter parameter) { + // dont render element when the parameter type is SwitchParameter + if (parameter.Type is "SwitchParameter" or "System.Management.Automation.SwitchParameter") + { + return null; + } + var parameterValue = new ParameterValue(); if (parameter is not null) { @@ -190,6 +196,7 @@ private static Parameter ConvertParameter(Model.Parameter parameter) var pSet = parameter.ParameterSets.FirstOrDefault(); newParameter.Position = pSet is null ? Model.Constants.NamedString : pSet.Position; newParameter.Value = GetParameterValue(parameter); + newParameter.Type = new DataType() { Name = parameter.Type }; if (parameter.Description is not null) { @@ -214,11 +221,6 @@ private static Parameter ConvertSyntaxParameter(Model.SyntaxParameter syntaxPara Name = syntaxParam.ParameterName, IsMandatory = syntaxParam.IsMandatory, Position = syntaxParam.Position, - Value = new MAML.ParameterValue() - { - DataType = syntaxParam.ParameterType, - IsMandatory = true, - }, }; if (syntaxParam.IsSwitchParameter) { @@ -229,6 +231,12 @@ private static Parameter ConvertSyntaxParameter(Model.SyntaxParameter syntaxPara } else { + newParameter.Value = new MAML.ParameterValue() + { + DataType = syntaxParam.ParameterType, + IsMandatory = true + }; + var parameter = parameters.FirstOrDefault(p => string.Equals(p.Name, syntaxParam.ParameterName, StringComparison.Ordinal)); if (parameter is not null) { diff --git a/src/MamlWriter/Parameter.cs b/src/MamlWriter/Parameter.cs index 9518f399..6fdb7133 100644 --- a/src/MamlWriter/Parameter.cs +++ b/src/MamlWriter/Parameter.cs @@ -29,7 +29,7 @@ public class Parameter /// The parameter value information ("command:parameterValue"). /// [XmlElement("parameterValue", Namespace = Constants.XmlNamespace.Command, Order = 2)] - public ParameterValue Value { get; set; } = new ParameterValue(); + public ParameterValue? Value { get; set; } [XmlElement("type", Namespace = Constants.XmlNamespace.Dev, Order = 3)] public DataType? Type { get; set; } From b481b3905c6693fc3c0a97791a307dc3b2312d52 Mon Sep 17 00:00:00 2001 From: teramako Date: Sun, 14 Sep 2025 08:26:37 +0000 Subject: [PATCH 09/12] Render accepted values in generating Maml help. outputs XML like the following: ```xml Accepted Value 1 Accepted Value 2 ``` --- src/MamlWriter/MamlHelpers.cs | 11 +++++++++++ src/MamlWriter/Parameter.cs | 11 +++++++++-- test/Pester/ExportMamlCommandHelp.Tests.ps1 | 7 +++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/MamlWriter/MamlHelpers.cs b/src/MamlWriter/MamlHelpers.cs index 47d6f5c6..7c9714d6 100644 --- a/src/MamlWriter/MamlHelpers.cs +++ b/src/MamlWriter/MamlHelpers.cs @@ -211,6 +211,12 @@ private static Parameter ConvertParameter(Model.Parameter parameter) newParameter.Aliases = string.Join(", ", parameter.Aliases); } + if (parameter.AcceptedValues.Count > 0) + { + var acceptedValues = parameter.AcceptedValues.Select(val => new ParameterValue() { DataType = val }); + newParameter.ParameterValueGroup = acceptedValues.ToList(); + } + return newParameter; } @@ -248,6 +254,11 @@ private static Parameter ConvertSyntaxParameter(Model.SyntaxParameter syntaxPara { newParameter.Aliases = string.Join(", ", parameter.Aliases); } + if (parameter.AcceptedValues.Count > 0) + { + var acceptedValues = parameter.AcceptedValues.Select(val => new ParameterValue() { DataType = val }); + newParameter.ParameterValueGroup = acceptedValues.ToList(); + } } } return newParameter; diff --git a/src/MamlWriter/Parameter.cs b/src/MamlWriter/Parameter.cs index 6fdb7133..4027f6c3 100644 --- a/src/MamlWriter/Parameter.cs +++ b/src/MamlWriter/Parameter.cs @@ -25,13 +25,20 @@ public class Parameter [XmlArrayItem("para", Namespace = Constants.XmlNamespace.MAML)] public List Description { get; set; } = new List(); + /// + /// The parameter value group information ("command:parameterValueGroup"). + /// + [XmlArray("parameterValueGroup", Namespace = Constants.XmlNamespace.Command, Order = 2)] + [XmlArrayItem("parameterValue", Namespace = Constants.XmlNamespace.Command)] + public List? ParameterValueGroup { get; set; } + /// /// The parameter value information ("command:parameterValue"). /// - [XmlElement("parameterValue", Namespace = Constants.XmlNamespace.Command, Order = 2)] + [XmlElement("parameterValue", Namespace = Constants.XmlNamespace.Command, Order = 3)] public ParameterValue? Value { get; set; } - [XmlElement("type", Namespace = Constants.XmlNamespace.Dev, Order = 3)] + [XmlElement("type", Namespace = Constants.XmlNamespace.Dev, Order = 4)] public DataType? Type { get; set; } /// diff --git a/test/Pester/ExportMamlCommandHelp.Tests.ps1 b/test/Pester/ExportMamlCommandHelp.Tests.ps1 index 710dc04a..1f00ea64 100644 --- a/test/Pester/ExportMamlCommandHelp.Tests.ps1 +++ b/test/Pester/ExportMamlCommandHelp.Tests.ps1 @@ -148,5 +148,12 @@ Describe "Export-MamlCommandHelp tests" { $xml2.SelectNodes('//command:command', $ns2).Where({$_.details.name -eq "Get-Date"}).Parameters.parameter. Where({$_.Name -in @('Date', 'UnixTimeSeconds')}).aliases | Should -Be 'LastWriteTime', 'UnixTime' } + + It "Should have the proper parameterValueGroup for 'DisplayHint' parameter of Get-Date" { + $parameter = $xml2.SelectNodes('//command:command', $ns2).Where({$_.details.name -eq "Get-Date"}). + parameters.parameter.Where({$_.Name -eq 'DisplayHint'}) + $parameter.SelectNodes('./command:parameterValueGroup/command:parameterValue', $ns2).'#text' | + Should -BeExactly 'Date', 'Time', 'DateTime' + } } } From c9f5bbffee50e1e7b84c504411a7712f7587d267 Mon Sep 17 00:00:00 2001 From: teramako Date: Sun, 14 Sep 2025 10:44:25 +0000 Subject: [PATCH 10/12] Add tests of syntax parameters and parameters for Export-MamlCommandHelp --- test/Pester/ExportMamlCommandHelp.Tests.ps1 | 48 +++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/Pester/ExportMamlCommandHelp.Tests.ps1 b/test/Pester/ExportMamlCommandHelp.Tests.ps1 index 1f00ea64..f7d8c268 100644 --- a/test/Pester/ExportMamlCommandHelp.Tests.ps1 +++ b/test/Pester/ExportMamlCommandHelp.Tests.ps1 @@ -155,5 +155,53 @@ Describe "Export-MamlCommandHelp tests" { $parameter.SelectNodes('./command:parameterValueGroup/command:parameterValue', $ns2).'#text' | Should -BeExactly 'Date', 'Time', 'DateTime' } + + It "Should have the proper values for '' syntax parameter of Get-Date" -testcases @( + @{ name = "Date"; position = "0"; aliases = "LastWriteTime"; parameterValue = "DateTime"; type = "System.DateTime"; }, + @{ name = "Year"; position = "named"; aliases = "none"; parameterValue = "Int32"; type = "System.Int32"; }, + @{ name = "AsUTC"; position = "named"; aliases = "none"; parameterValue = $null; type = "System.Management.Automation.SwitchParameter" } + ) { + param($name, $position, $aliases, $parameterValue, $type) + + $command = $xml2.SelectNodes('//command:command', $ns2).Where({$_.details.name -eq "Get-Date"}) + $syntaxParam = $command.syntax.syntaxItem[0].parameter.Where({$_.name -eq $name}); + $syntaxParam.Count | Should -Be 1; + $syntaxParam.position | Should -BeExactly $position + $syntaxParam.aliases | Should -BeExactly $aliases + if ($null -eq $parameterValue) + { + $syntaxParam.parameterValue | Should -BeNullOrEmpty + } + else + { + $syntaxParam.parameterValue."#text" | Should -BeExactly $parameterValue + $syntaxParam.parameterValue.required | Should -BeExactly 'true' + } + $syntaxParam.type.name | Should -BeExactly $type + } + + It "Should have the proper values for '' parameter of Get-Date" -testcases @( + @{ name = "Date"; position = "0"; aliases = "LastWriteTime"; parameterValue = "System.DateTime"; type = "System.DateTime"; }, + @{ name = "Year"; position = "Named"; aliases = "none"; parameterValue = "System.Int32"; type = "System.Int32"; }, + @{ name = "AsUTC"; position = "Named"; aliases = "none"; parameterValue = $null; type = "System.Management.Automation.SwitchParameter" } + ) { + param($name, $position, $aliases, $parameterValue, $type) + + $command = $xml2.SelectNodes('//command:command', $ns2).Where({$_.details.name -eq "Get-Date"}) + $param = $command.parameters.parameter.Where({$_.name -eq $name}); + $param.Count | Should -Be 1; + $param.position | Should -BeExactly $position + $param.aliases | Should -BeExactly $aliases + if ($null -eq $parameterValue) + { + $param.parameterValue | Should -BeNullOrEmpty + } + else + { + $param.parameterValue."#text" | Should -BeExactly $parameterValue + $param.parameterValue.required | Should -BeExactly 'true' + } + $param.type.name | Should -BeExactly $type + } } } From 43e06702f8d99e76004ef69f43089b6bd4369fce Mon Sep 17 00:00:00 2001 From: teramako Date: Sun, 14 Sep 2025 10:46:19 +0000 Subject: [PATCH 11/12] fix: `required` attribute of `` should be `true` always. This affects to Help display. Before: ``` PARAMETERS: NAME [] ``` After: ``` PARAMETERS: NAME ``` --- src/MamlWriter/MamlHelpers.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MamlWriter/MamlHelpers.cs b/src/MamlWriter/MamlHelpers.cs index 7c9714d6..5bbc19f2 100644 --- a/src/MamlWriter/MamlHelpers.cs +++ b/src/MamlWriter/MamlHelpers.cs @@ -180,9 +180,9 @@ private static PipelineInputType GetPipelineInputType(Model.Parameter parameter) { parameterValue.DataType = parameter.Type; parameterValue.IsVariableLength = parameter.VariableLength; - // We just mark mandatory if one of the parameter sets is mandatory since MAML doesn't - // have a way to disambiguate these. - parameterValue.IsMandatory = parameter.ParameterSets.Any(x => x.IsRequired); + // Should be `true`. This indicates not the parameter is mandatory or not, but the parameter **value** is mandatory or not. + // The parameter which parameter value is not required is SwitchParameter, but SwitchParameter does not ouput this element itself. + parameterValue.IsMandatory = true; } return parameterValue; } From 8a346e11de945293e428aafa077f8c5b06209964 Mon Sep 17 00:00:00 2001 From: teramako Date: Sun, 14 Sep 2025 11:40:37 +0000 Subject: [PATCH 12/12] Set `HasCmdletBinding` of each syntax instance the same as commandHelp --- src/Transform/TransformMaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Transform/TransformMaml.cs b/src/Transform/TransformMaml.cs index 71128c4d..3c5aeb0e 100644 --- a/src/Transform/TransformMaml.cs +++ b/src/Transform/TransformMaml.cs @@ -115,6 +115,7 @@ private Collection ReadMaml(string mamlFile) { cmdHelp.Parameters.RemoveAll(p => string.Compare(p.Name, MAML.Constants.NoCommonParameter, true) == 0); } + cmdHelp.Syntax.ForEach(si => si.HasCmdletBinding = cmdHelp.HasCmdletBinding); cmdHelp.Metadata = MetadataUtils.GetCommandHelpBaseMetadata(cmdHelp); }