From e4ef617ead38e805255b8ad61d7e39efb6914933 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 28 Feb 2025 13:07:48 +1000 Subject: [PATCH 1/7] Use parameter HelpMessage for tool tip completion Use the help message on the parameter in the completion tool tip to provide better completion help. This can be utilised by other tools to provide more context for a parameter outside of the current type and parameter name which is quite minimal. --- .../CommandCompletion/CompletionCompleters.cs | 89 +++++++++++++-- .../TabCompletion/TabCompletion.Tests.ps1 | 104 ++++++++++++++++++ 2 files changed, 182 insertions(+), 11 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index e25586fa55d..e0667fe4906 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -670,6 +670,7 @@ private static List GetParameterCompletionResults(string param { Diagnostics.Assert(bindingInfo.InfoType.Equals(PseudoBindingInfoType.PseudoBindingSucceed), "The pseudo binding should succeed"); List result = new List(); + Assembly commandAssembly = bindingInfo.CommandInfo.CommandMetadata.CommandType.Assembly; if (parameterName == string.Empty) { @@ -677,7 +678,8 @@ private static List GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); return result; } @@ -699,7 +701,8 @@ private static List GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); } return result; @@ -714,7 +717,8 @@ private static List GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.BoundParameters.Values, - withColon); + withColon, + commandAssembly); } return result; @@ -778,7 +782,8 @@ private static List GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); return result; } @@ -786,11 +791,31 @@ private static List GetParameterCompletionResults(string param WildcardPattern pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase); string parameterType = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] "; + + string helpMessage = null; + if (param.Parameter.CompiledAttributes is not null) + { + foreach (Attribute attr in param.Parameter.CompiledAttributes) + { + if (attr is ParameterAttribute pattr) + { + helpMessage = GetParameterHelpMessage( + pattr, + commandAssembly); + break; + } + } + } + if (!string.IsNullOrWhiteSpace(helpMessage)) + { + helpMessage = $" - {helpMessage.Trim()}"; + } + string colonSuffix = withColon ? ":" : string.Empty; if (pattern.IsMatch(matchedParameterName)) { string completionText = "-" + matchedParameterName + colonSuffix; - string tooltip = parameterType + matchedParameterName; + string tooltip = parameterType + matchedParameterName + helpMessage; result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip)); } else @@ -804,7 +829,7 @@ private static List GetParameterCompletionResults(string param $"-{alias}{colonSuffix}", alias, CompletionResultType.ParameterName, - parameterType + alias)); + parameterType + alias + helpMessage)); } } } @@ -812,6 +837,38 @@ private static List GetParameterCompletionResults(string param return result; } +#nullable enable + /// + /// Gets the help message text for the parameter attribute. + /// + /// The attribute to check for the help message. + /// The assembly to lookup resources messages, this should be the assembly the cmdlet is defined in. + /// The help message of the parameter or null if none is defined.> + private static string? GetParameterHelpMessage( + ParameterAttribute attr, + Assembly? assembly = null) + { + if (!string.IsNullOrWhiteSpace(attr.HelpMessage)) + { + return attr.HelpMessage.Trim(); + } + + if (assembly is null || string.IsNullOrWhiteSpace(attr.HelpMessageBaseName) || string.IsNullOrWhiteSpace(attr.HelpMessageResourceId)) + { + return null; + } + + try + { + return ResourceManagerCache.GetResourceString(assembly, attr.HelpMessageBaseName, attr.HelpMessageResourceId)?.Trim(); + } + catch (Exception e) + { + return $"Failed to find the help message: {e.Message}"; + } + } +#nullable disable + /// /// Get the parameter completion results by using the given valid parameter sets and available parameters. /// @@ -819,12 +876,14 @@ private static List GetParameterCompletionResults(string param /// /// /// + /// Optional assembly used to lookup parameter help messages. /// private static List GetParameterCompletionResults( string parameterName, uint validParameterSetFlags, IEnumerable parameters, - bool withColon) + bool withColon, + Assembly commandAssembly = null) { var result = new List(); var commonParamResult = new List(); @@ -840,6 +899,7 @@ private static List GetParameterCompletionResults( string name = param.Parameter.Name; string type = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] "; + string helpMessage = null; bool isCommonParameter = Cmdlet.CommonParameters.Contains(name, StringComparer.OrdinalIgnoreCase); List listInUse = isCommonParameter ? commonParamResult : result; @@ -855,20 +915,27 @@ private static List GetParameterCompletionResults( { foreach (var attr in compiledAttributes) { - var pattr = attr as ParameterAttribute; - if (pattr != null && pattr.DontShow) + if (attr is not ParameterAttribute pattr) + { + continue; + } + + helpMessage = GetParameterHelpMessage(pattr, commandAssembly); + if (pattr.DontShow) { showToUser = false; addCommonParameters = false; - break; } + break; } } if (showToUser) { string completionText = "-" + name + colonSuffix; - string tooltip = type + name; + string tooltip = string.IsNullOrWhiteSpace(helpMessage) + ? $"{type}{name}" + : $"{type}{name} - {helpMessage}"; listInUse.Add(new CompletionResult(completionText, name, CompletionResultType.ParameterName, tooltip)); } diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 93bdfe89882..94f70c04700 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1124,6 +1124,110 @@ param([ValidatePattern( $res.CompletionMatches[0].CompletionText | Should -BeExactly '$DemoVar1' $res.CompletionMatches[1].CompletionText | Should -BeExactly '$DemoVar2' } + + It 'Should include help message in parameter tool tip by direct match' { + Function Test-Function { + param ( + [Parameter(HelpMessage = 'Some help message')] + $Foo + ) + } + + $expected = '[Object] Foo - Some help message' + $Script = 'Test-Function -Foo' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Foo' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should include help message in parameter tool tip by multiple match' { + Function Test-Function { + param ( + [Parameter(HelpMessage = 'Some help message')] + $Foo, + + $Bar + ) + } + + $expected = '[Object] Foo - Some help message' + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Foo' + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Foo' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should retrieve help message from dynamic parameter' { + Function Test-Function { + [CmdletBinding()] + param () + dynamicparam { + $attr = [System.Management.Automation.ParameterAttribute]@{ + HelpMessage = "Howdy partner" + } + $attrCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() + $attrCollection.Add($attr) + + $dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new('Foo', [string], $attrCollection) + + $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() + $paramDictionary.Add('Foo', $dynParam) + $paramDictionary + } + + end {} + } + + $expected = '[string] Foo - Howdy partner' + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Foo' + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Foo' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should have type and name for parameter without help message' { + Function Test-Function { + param ( + [Parameter()] + $WithAttribute, + + $WithoutAttribute + ) + } + + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | + Where-Object CompletionText -in '-WithAttribute', '-WithoutAttribute' | + Sort-Object CompletionText + $res.Count | Should -Be 2 + + $res.CompletionText[0] | Should -BeExactly '-WithAttribute' + $res.ToolTip[0] | Should -BeExactly '[Object] WithAttribute' + + $res.CompletionText[1] | Should -BeExactly '-WithoutAttribute' + $res.ToolTip[1] | Should -BeExactly '[Object] WithoutAttribute' + } + + It 'Should include help resource message in parameter tool tip by direct match' { + $expected = '[string] Activity - Text to describe the activity for which progress is being reported.' + $Script = 'Write-Progress -Activity' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Activity' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should include help resource message in parameter tool tip by multiple matches' { + $expected = '[string] Activity - Text to describe the activity for which progress is being reported.' + $Script = 'Write-Progress -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Activity' + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Activity' + $res.ToolTip | Should -BeExactly $expected + } Context 'Start-Process -Verb parameter completion' { BeforeAll { From e23a577add3d4c3cd81d70cb8a1ba437ca20e50f Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 28 Feb 2025 14:24:01 +1000 Subject: [PATCH 2/7] Handle multiple Parameter attributes --- .../CommandCompletion/CompletionCompleters.cs | 14 ++++-- .../TabCompletion/TabCompletion.Tests.ps1 | 46 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index e0667fe4906..791db8783e7 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -802,7 +802,10 @@ private static List GetParameterCompletionResults(string param helpMessage = GetParameterHelpMessage( pattr, commandAssembly); - break; + if (!string.IsNullOrEmpty(helpMessage)) + { + break; + } } } } @@ -919,14 +922,17 @@ private static List GetParameterCompletionResults( { continue; } - - helpMessage = GetParameterHelpMessage(pattr, commandAssembly); if (pattr.DontShow) { showToUser = false; addCommonParameters = false; + break; + } + + if (string.IsNullOrWhiteSpace(helpMessage)) + { + helpMessage = GetParameterHelpMessage(pattr, commandAssembly); } - break; } } diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 94f70c04700..4e72283d3e4 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1228,6 +1228,52 @@ param([ValidatePattern( $res.CompletionText | Should -BeExactly '-Activity' $res.ToolTip | Should -BeExactly $expected } + + It 'Should use first valid HelpMessage with multiple parameter attributes by direct match' { + Function Test-Function { + [CmdletBinding(DefaultParameterSetName = 'Default')] + param ( + [Parameter(ParameterSetName = 'Foo', HelpMessage = 'Foo')] + [Parameter(ParameterSetName = 'Default')] + [string] + $Param, + + [Parameter(ParameterSetName = 'Foo')] + [switch] + $Foo + ) + } + + $expected = '[string] Param - Foo' + $Script = 'Test-Function -Param' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Param' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should use first valid HelpMessage with multiple parameter attributes by multiple match' { + Function Test-Function { + [CmdletBinding(DefaultParameterSetName = 'Default')] + param ( + [Parameter(ParameterSetName = 'Foo', HelpMessage = 'Foo')] + [Parameter(ParameterSetName = 'Default')] + [string] + $Param, + + [Parameter(ParameterSetName = 'Foo')] + [switch] + $Foo + ) + } + + $expected = '[string] Param - Foo' + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Param' + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Param' + $res.ToolTip | Should -BeExactly $expected + } Context 'Start-Process -Verb parameter completion' { BeforeAll { From d7c060f944d35813e1288f5a2787f6709208aa05 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 28 Feb 2025 19:58:39 +1000 Subject: [PATCH 3/7] Changes from review --- .../CommandCompletion/CompletionCompleters.cs | 4 +- .../TabCompletion/TabCompletion.Tests.ps1 | 38 +++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 791db8783e7..4b39ebbf565 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -865,9 +865,9 @@ private static List GetParameterCompletionResults(string param { return ResourceManagerCache.GetResourceString(assembly, attr.HelpMessageBaseName, attr.HelpMessageResourceId)?.Trim(); } - catch (Exception e) + catch (Exception) { - return $"Failed to find the help message: {e.Message}"; + return null; } } #nullable disable diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 4e72283d3e4..5823838bc05 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1192,41 +1192,41 @@ param([ValidatePattern( Function Test-Function { param ( [Parameter()] - $WithAttribute, + $WithParamAttribute, - $WithoutAttribute + $WithoutParamAttribute ) } $Script = 'Test-Function -' $res = (TabExpansion2 -inputScript $Script).CompletionMatches | - Where-Object CompletionText -in '-WithAttribute', '-WithoutAttribute' | + Where-Object CompletionText -in '-WithParamAttribute', '-WithoutParamAttribute' | Sort-Object CompletionText $res.Count | Should -Be 2 - $res.CompletionText[0] | Should -BeExactly '-WithAttribute' - $res.ToolTip[0] | Should -BeExactly '[Object] WithAttribute' + $res.CompletionText[0] | Should -BeExactly '-WithoutParamAttribute' + $res.ToolTip[0] | Should -BeExactly '[Object] WithoutParamAttribute' - $res.CompletionText[1] | Should -BeExactly '-WithoutAttribute' - $res.ToolTip[1] | Should -BeExactly '[Object] WithoutAttribute' + $res.CompletionText[1] | Should -BeExactly '-WithParamAttribute' + $res.ToolTip[1] | Should -BeExactly '[Object] WithParamAttribute' } It 'Should include help resource message in parameter tool tip by direct match' { - $expected = '[string] Activity - Text to describe the activity for which progress is being reported.' + $expected = '`[string`] Activity - *' $Script = 'Write-Progress -Activity' $res = (TabExpansion2 -inputScript $Script).CompletionMatches $res.Count | Should -Be 1 $res.CompletionText | Should -BeExactly '-Activity' - $res.ToolTip | Should -BeExactly $expected + $res.ToolTip | Should -BeLikeExactly $expected } It 'Should include help resource message in parameter tool tip by multiple matches' { - $expected = '[string] Activity - Text to describe the activity for which progress is being reported.' + $expected = '`[string`] Activity - *' $Script = 'Write-Progress -' $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Activity' $res.Count | Should -Be 1 $res.CompletionText | Should -BeExactly '-Activity' - $res.ToolTip | Should -BeExactly $expected + $res.ToolTip | Should -BeLikeExactly $expected } It 'Should use first valid HelpMessage with multiple parameter attributes by direct match' { @@ -1274,6 +1274,22 @@ param([ValidatePattern( $res.CompletionText | Should -BeExactly '-Param' $res.ToolTip | Should -BeExactly $expected } + + It 'Should ignore errors when faling to get HelpMessage resource' { + Function Test-Function { + param ( + [Parameter(HelpMessageBaseName="invalid", HelpMessageResourceId="SomeId")] + $Foo + ) + } + + $expected = '[Object] Foo' + $Script = 'Test-Function -Foo' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Foo' + $res.ToolTip | Should -BeExactly $expected + } Context 'Start-Process -Verb parameter completion' { BeforeAll { From 62898470569176669c43c4131083db227093ca0d Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Sat, 1 Mar 2025 14:16:22 +1000 Subject: [PATCH 4/7] Use TryGet pattern and simplify null checks --- .../CommandCompletion/CompletionCompleters.cs | 74 +++++++++---------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 4b39ebbf565..a3616ebdc22 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -792,27 +792,18 @@ private static List GetParameterCompletionResults(string param WildcardPattern pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase); string parameterType = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] "; - string helpMessage = null; + string helpMessage = string.Empty; if (param.Parameter.CompiledAttributes is not null) { foreach (Attribute attr in param.Parameter.CompiledAttributes) { - if (attr is ParameterAttribute pattr) + if (attr is ParameterAttribute pattr && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage)) { - helpMessage = GetParameterHelpMessage( - pattr, - commandAssembly); - if (!string.IsNullOrEmpty(helpMessage)) - { - break; - } + helpMessage = $" - {attrHelpMessage}"; + break; } } } - if (!string.IsNullOrWhiteSpace(helpMessage)) - { - helpMessage = $" - {helpMessage.Trim()}"; - } string colonSuffix = withColon ? ":" : string.Empty; if (pattern.IsMatch(matchedParameterName)) @@ -842,32 +833,38 @@ private static List GetParameterCompletionResults(string param #nullable enable /// - /// Gets the help message text for the parameter attribute. + /// Try and get the help message text for the parameter attribute. /// /// The attribute to check for the help message. /// The assembly to lookup resources messages, this should be the assembly the cmdlet is defined in. - /// The help message of the parameter or null if none is defined.> - private static string? GetParameterHelpMessage( + /// The help message if it was found otherwise null. + /// True if the help message was set or false if not.> + private static bool TryGetParameterHelpMessage( ParameterAttribute attr, - Assembly? assembly = null) + Assembly? assembly, + [NotNullWhen(true)] out string? message) { - if (!string.IsNullOrWhiteSpace(attr.HelpMessage)) + message = null; + + if (attr.HelpMessage is not null) { - return attr.HelpMessage.Trim(); + message = attr.HelpMessage; + return true; } - if (assembly is null || string.IsNullOrWhiteSpace(attr.HelpMessageBaseName) || string.IsNullOrWhiteSpace(attr.HelpMessageResourceId)) + if (assembly is null || attr.HelpMessageBaseName is null || attr.HelpMessageResourceId is null) { - return null; + return false; } try { - return ResourceManagerCache.GetResourceString(assembly, attr.HelpMessageBaseName, attr.HelpMessageResourceId)?.Trim(); + message = ResourceManagerCache.GetResourceString(assembly, attr.HelpMessageBaseName, attr.HelpMessageResourceId); + return message is not null; } catch (Exception) { - return null; + return false; } } #nullable disable @@ -902,7 +899,7 @@ private static List GetParameterCompletionResults( string name = param.Parameter.Name; string type = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] "; - string helpMessage = null; + string helpMessage = string.Empty; bool isCommonParameter = Cmdlet.CommonParameters.Contains(name, StringComparer.OrdinalIgnoreCase); List listInUse = isCommonParameter ? commonParamResult : result; @@ -918,20 +915,19 @@ private static List GetParameterCompletionResults( { foreach (var attr in compiledAttributes) { - if (attr is not ParameterAttribute pattr) + if (attr is ParameterAttribute pattr) { - continue; - } - if (pattr.DontShow) - { - showToUser = false; - addCommonParameters = false; - break; - } - - if (string.IsNullOrWhiteSpace(helpMessage)) - { - helpMessage = GetParameterHelpMessage(pattr, commandAssembly); + if (pattr.DontShow) + { + showToUser = false; + addCommonParameters = false; + break; + } + + if (helpMessage is not null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage)) + { + helpMessage = $" - {attrHelpMessage}"; + } } } } @@ -939,9 +935,7 @@ private static List GetParameterCompletionResults( if (showToUser) { string completionText = "-" + name + colonSuffix; - string tooltip = string.IsNullOrWhiteSpace(helpMessage) - ? $"{type}{name}" - : $"{type}{name} - {helpMessage}"; + string tooltip = $"{type}{name}{helpMessage}"; listInUse.Add(new CompletionResult(completionText, name, CompletionResultType.ParameterName, tooltip)); } From 0e2947e6ee6229c9a276e7a14878043bcc5186cf Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Mon, 10 Mar 2025 05:45:00 +1000 Subject: [PATCH 5/7] Update tests based on review comments --- .../TabCompletion/TabCompletion.Tests.ps1 | 171 +++++++++--------- 1 file changed, 81 insertions(+), 90 deletions(-) diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 5823838bc05..8e4fdd9d660 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1124,38 +1124,93 @@ param([ValidatePattern( $res.CompletionMatches[0].CompletionText | Should -BeExactly '$DemoVar1' $res.CompletionMatches[1].CompletionText | Should -BeExactly '$DemoVar2' } - - It 'Should include help message in parameter tool tip by direct match' { + + It 'Should include parameter help message in tool tip - SingleMatch ' -TestCases @( + @{ SingleMatch = $true } + @{ SingleMatch = $false } + ) { + param ($SingleMatch) + Function Test-Function { param ( [Parameter(HelpMessage = 'Some help message')] - $Foo + $ParamWithHelp, + + $ParamWithoutHelp ) } - $expected = '[Object] Foo - Some help message' - $Script = 'Test-Function -Foo' - $res = (TabExpansion2 -inputScript $Script).CompletionMatches + $expected = '[Object] ParamWithHelp - Some help message' + + if ($SingleMatch) { + $Script = 'Test-Function -ParamWithHelp' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + } + else { + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp' + } + $res.Count | Should -Be 1 - $res.CompletionText | Should -BeExactly '-Foo' + $res.CompletionText | Should -BeExactly '-ParamWithHelp' $res.ToolTip | Should -BeExactly $expected } - It 'Should include help message in parameter tool tip by multiple match' { + It 'Should include parameter help resource message in tool tip - SingleMatch ' -TestCases @( + @{ SingleMatch = $true } + @{ SingleMatch = $false } + ) { + param ($SingleMatch) + + $expected = '`[string`] Activity - *' + + if ($SingleMatch) { + $Script = 'Write-Progress -Activity' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + } + else { + $Script = 'Write-Progress -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Activity' + } + + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Activity' + $res.ToolTip | Should -BeLikeExactly $expected + } + + It 'Should skip empty parameter HelpMessage with multiple parameters - SingleMatch ' -TestCases @( + @{ SingleMatch = $true } + @{ SingleMatch = $false } + ) { + param ($SingleMatch) + Function Test-Function { + [CmdletBinding(DefaultParameterSetName = 'SetWithoutHelp')] param ( - [Parameter(HelpMessage = 'Some help message')] - $Foo, - - $Bar + [Parameter(ParameterSetName = 'SetWithHelp', HelpMessage = 'Help Message')] + [Parameter(ParameterSetName = 'SetWithoutHelp')] + [string] + $ParamWithHelp, + + [Parameter(ParameterSetName = 'SetWithHelp')] + [switch] + $ParamWithoutHelp ) } - $expected = '[Object] Foo - Some help message' - $Script = 'Test-Function -' - $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Foo' + $expected = '[string] ParamWithHelp - Help Message' + + if ($SingleMatch) { + $Script = 'Test-Function -ParamWithHelp' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + } + else { + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp' + } + $res.Count | Should -Be 1 - $res.CompletionText | Should -BeExactly '-Foo' + $res.CompletionText | Should -BeExactly '-ParamWithHelp' $res.ToolTip | Should -BeExactly $expected } @@ -1170,21 +1225,21 @@ param([ValidatePattern( $attrCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attrCollection.Add($attr) - $dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new('Foo', [string], $attrCollection) + $dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new('DynamicParam', [string], $attrCollection) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() - $paramDictionary.Add('Foo', $dynParam) + $paramDictionary.Add('DynamicParam', $dynParam) $paramDictionary } end {} } - $expected = '[string] Foo - Howdy partner' + $expected = '[string] DynamicParam - Howdy partner' $Script = 'Test-Function -' - $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Foo' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-DynamicParam' $res.Count | Should -Be 1 - $res.CompletionText | Should -BeExactly '-Foo' + $res.CompletionText | Should -BeExactly '-DynamicParam' $res.ToolTip | Should -BeExactly $expected } @@ -1211,83 +1266,19 @@ param([ValidatePattern( $res.ToolTip[1] | Should -BeExactly '[Object] WithParamAttribute' } - It 'Should include help resource message in parameter tool tip by direct match' { - $expected = '`[string`] Activity - *' - $Script = 'Write-Progress -Activity' - $res = (TabExpansion2 -inputScript $Script).CompletionMatches - $res.Count | Should -Be 1 - $res.CompletionText | Should -BeExactly '-Activity' - $res.ToolTip | Should -BeLikeExactly $expected - } - - It 'Should include help resource message in parameter tool tip by multiple matches' { - $expected = '`[string`] Activity - *' - $Script = 'Write-Progress -' - $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Activity' - $res.Count | Should -Be 1 - $res.CompletionText | Should -BeExactly '-Activity' - $res.ToolTip | Should -BeLikeExactly $expected - } - - It 'Should use first valid HelpMessage with multiple parameter attributes by direct match' { - Function Test-Function { - [CmdletBinding(DefaultParameterSetName = 'Default')] - param ( - [Parameter(ParameterSetName = 'Foo', HelpMessage = 'Foo')] - [Parameter(ParameterSetName = 'Default')] - [string] - $Param, - - [Parameter(ParameterSetName = 'Foo')] - [switch] - $Foo - ) - } - - $expected = '[string] Param - Foo' - $Script = 'Test-Function -Param' - $res = (TabExpansion2 -inputScript $Script).CompletionMatches - $res.Count | Should -Be 1 - $res.CompletionText | Should -BeExactly '-Param' - $res.ToolTip | Should -BeExactly $expected - } - - It 'Should use first valid HelpMessage with multiple parameter attributes by multiple match' { - Function Test-Function { - [CmdletBinding(DefaultParameterSetName = 'Default')] - param ( - [Parameter(ParameterSetName = 'Foo', HelpMessage = 'Foo')] - [Parameter(ParameterSetName = 'Default')] - [string] - $Param, - - [Parameter(ParameterSetName = 'Foo')] - [switch] - $Foo - ) - } - - $expected = '[string] Param - Foo' - $Script = 'Test-Function -' - $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Param' - $res.Count | Should -Be 1 - $res.CompletionText | Should -BeExactly '-Param' - $res.ToolTip | Should -BeExactly $expected - } - It 'Should ignore errors when faling to get HelpMessage resource' { Function Test-Function { param ( [Parameter(HelpMessageBaseName="invalid", HelpMessageResourceId="SomeId")] - $Foo + $InvalidHelpParam ) } - - $expected = '[Object] Foo' - $Script = 'Test-Function -Foo' + + $expected = '[Object] InvalidHelpParam' + $Script = 'Test-Function -InvalidHelpParam' $res = (TabExpansion2 -inputScript $Script).CompletionMatches $res.Count | Should -Be 1 - $res.CompletionText | Should -BeExactly '-Foo' + $res.CompletionText | Should -BeExactly '-InvalidHelpParam' $res.ToolTip | Should -BeExactly $expected } From cedbe09b4ae5b4de860b46e0709eb09a68317483 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Wed, 30 Jul 2025 04:51:30 +1000 Subject: [PATCH 6/7] Changes from review --- .../CommandCompletion/CompletionCompleters.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index a3616ebdc22..1aece349de9 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -670,7 +670,11 @@ private static List GetParameterCompletionResults(string param { Diagnostics.Assert(bindingInfo.InfoType.Equals(PseudoBindingInfoType.PseudoBindingSucceed), "The pseudo binding should succeed"); List result = new List(); - Assembly commandAssembly = bindingInfo.CommandInfo.CommandMetadata.CommandType.Assembly; + Assembly commandAssembly = null; + if (bindingInfo.CommandInfo is CmdletInfo) + { + commandAssembly = bindingInfo.CommandInfo.CommandMetadata.CommandType.Assembly; + } if (parameterName == string.Empty) { @@ -809,7 +813,7 @@ private static List GetParameterCompletionResults(string param if (pattern.IsMatch(matchedParameterName)) { string completionText = "-" + matchedParameterName + colonSuffix; - string tooltip = parameterType + matchedParameterName + helpMessage; + string tooltip = $"{parameterType}{matchedParameterName}{helpMessage}"; result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip)); } else @@ -823,7 +827,7 @@ private static List GetParameterCompletionResults(string param $"-{alias}{colonSuffix}", alias, CompletionResultType.ParameterName, - parameterType + alias + helpMessage)); + $"{parameterType}{alias}{helpMessage}")); } } } @@ -899,7 +903,7 @@ private static List GetParameterCompletionResults( string name = param.Parameter.Name; string type = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] "; - string helpMessage = string.Empty; + string helpMessage = null; bool isCommonParameter = Cmdlet.CommonParameters.Contains(name, StringComparer.OrdinalIgnoreCase); List listInUse = isCommonParameter ? commonParamResult : result; @@ -924,7 +928,7 @@ private static List GetParameterCompletionResults( break; } - if (helpMessage is not null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage)) + if (helpMessage is null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage)) { helpMessage = $" - {attrHelpMessage}"; } From 242744b016143d3c477df32f339af6488b19a7c2 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Wed, 30 Jul 2025 09:19:30 +1000 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Dongbo Wang --- .../engine/CommandCompletion/CompletionCompleters.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 1aece349de9..880aedb04ce 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -671,9 +671,9 @@ private static List GetParameterCompletionResults(string param Diagnostics.Assert(bindingInfo.InfoType.Equals(PseudoBindingInfoType.PseudoBindingSucceed), "The pseudo binding should succeed"); List result = new List(); Assembly commandAssembly = null; - if (bindingInfo.CommandInfo is CmdletInfo) + if (bindingInfo.CommandInfo is CmdletInfo cmdletInfo) { - commandAssembly = bindingInfo.CommandInfo.CommandMetadata.CommandType.Assembly; + commandAssembly = cmdletInfo.CommandMetadata.CommandType.Assembly; } if (parameterName == string.Empty) @@ -812,7 +812,7 @@ private static List GetParameterCompletionResults(string param string colonSuffix = withColon ? ":" : string.Empty; if (pattern.IsMatch(matchedParameterName)) { - string completionText = "-" + matchedParameterName + colonSuffix; + string completionText = $"-{matchedParameterName}{colonSuffix}"; string tooltip = $"{parameterType}{matchedParameterName}{helpMessage}"; result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip)); } @@ -938,7 +938,7 @@ private static List GetParameterCompletionResults( if (showToUser) { - string completionText = "-" + name + colonSuffix; + string completionText = $"-{name}{colonSuffix}"; string tooltip = $"{type}{name}{helpMessage}"; listInUse.Add(new CompletionResult(completionText, name, CompletionResultType.ParameterName, tooltip));