Use parameter HelpMessage for tool tip in parameter completion (#25108)

Use the help message on the parameter in the completion tool tip to provide better completion help. This can be utilized by other tools to provide more context for a parameter outside of the current type and parameter name which is quite minimal.
This commit is contained in:
Jordan Borean
2025-07-30 12:05:13 -07:00
committed by GitHub
parent b801027560
commit 11eeae97f0
2 changed files with 243 additions and 15 deletions
@@ -670,6 +670,11 @@ namespace System.Management.Automation
{
Diagnostics.Assert(bindingInfo.InfoType.Equals(PseudoBindingInfoType.PseudoBindingSucceed), "The pseudo binding should succeed");
List<CompletionResult> result = new List<CompletionResult>();
Assembly commandAssembly = null;
if (bindingInfo.CommandInfo is CmdletInfo cmdletInfo)
{
commandAssembly = cmdletInfo.CommandMetadata.CommandType.Assembly;
}
if (parameterName == string.Empty)
{
@@ -677,7 +682,8 @@ namespace System.Management.Automation
parameterName,
bindingInfo.ValidParameterSetsFlags,
bindingInfo.UnboundParameters,
withColon);
withColon,
commandAssembly);
return result;
}
@@ -699,7 +705,8 @@ namespace System.Management.Automation
parameterName,
bindingInfo.ValidParameterSetsFlags,
bindingInfo.UnboundParameters,
withColon);
withColon,
commandAssembly);
}
return result;
@@ -714,7 +721,8 @@ namespace System.Management.Automation
parameterName,
bindingInfo.ValidParameterSetsFlags,
bindingInfo.BoundParameters.Values,
withColon);
withColon,
commandAssembly);
}
return result;
@@ -778,7 +786,8 @@ namespace System.Management.Automation
parameterName,
bindingInfo.ValidParameterSetsFlags,
bindingInfo.UnboundParameters,
withColon);
withColon,
commandAssembly);
return result;
}
@@ -786,11 +795,25 @@ namespace System.Management.Automation
WildcardPattern pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase);
string parameterType = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] ";
string helpMessage = string.Empty;
if (param.Parameter.CompiledAttributes is not null)
{
foreach (Attribute attr in param.Parameter.CompiledAttributes)
{
if (attr is ParameterAttribute pattr && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage))
{
helpMessage = $" - {attrHelpMessage}";
break;
}
}
}
string colonSuffix = withColon ? ":" : string.Empty;
if (pattern.IsMatch(matchedParameterName))
{
string completionText = "-" + matchedParameterName + colonSuffix;
string tooltip = parameterType + matchedParameterName;
string completionText = $"-{matchedParameterName}{colonSuffix}";
string tooltip = $"{parameterType}{matchedParameterName}{helpMessage}";
result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip));
}
else
@@ -804,7 +827,7 @@ namespace System.Management.Automation
$"-{alias}{colonSuffix}",
alias,
CompletionResultType.ParameterName,
parameterType + alias));
$"{parameterType}{alias}{helpMessage}"));
}
}
}
@@ -812,6 +835,44 @@ namespace System.Management.Automation
return result;
}
#nullable enable
/// <summary>
/// Try and get the help message text for the parameter attribute.
/// </summary>
/// <param name="attr">The attribute to check for the help message.</param>
/// <param name="assembly">The assembly to lookup resources messages, this should be the assembly the cmdlet is defined in.</param>
/// <param name="message">The help message if it was found otherwise null.</param>
/// <returns>True if the help message was set or false if not.></returns>
private static bool TryGetParameterHelpMessage(
ParameterAttribute attr,
Assembly? assembly,
[NotNullWhen(true)] out string? message)
{
message = null;
if (attr.HelpMessage is not null)
{
message = attr.HelpMessage;
return true;
}
if (assembly is null || attr.HelpMessageBaseName is null || attr.HelpMessageResourceId is null)
{
return false;
}
try
{
message = ResourceManagerCache.GetResourceString(assembly, attr.HelpMessageBaseName, attr.HelpMessageResourceId);
return message is not null;
}
catch (Exception)
{
return false;
}
}
#nullable disable
/// <summary>
/// Get the parameter completion results by using the given valid parameter sets and available parameters.
/// </summary>
@@ -819,12 +880,14 @@ namespace System.Management.Automation
/// <param name="validParameterSetFlags"></param>
/// <param name="parameters"></param>
/// <param name="withColon"></param>
/// <param name="commandAssembly">Optional assembly used to lookup parameter help messages.</param>
/// <returns></returns>
private static List<CompletionResult> GetParameterCompletionResults(
string parameterName,
uint validParameterSetFlags,
IEnumerable<MergedCompiledCommandParameter> parameters,
bool withColon)
bool withColon,
Assembly commandAssembly = null)
{
var result = new List<CompletionResult>();
var commonParamResult = new List<CompletionResult>();
@@ -840,6 +903,7 @@ namespace System.Management.Automation
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<CompletionResult> listInUse = isCommonParameter ? commonParamResult : result;
@@ -855,20 +919,27 @@ namespace System.Management.Automation
{
foreach (var attr in compiledAttributes)
{
var pattr = attr as ParameterAttribute;
if (pattr != null && pattr.DontShow)
if (attr is ParameterAttribute pattr)
{
showToUser = false;
addCommonParameters = false;
break;
if (pattr.DontShow)
{
showToUser = false;
addCommonParameters = false;
break;
}
if (helpMessage is null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage))
{
helpMessage = $" - {attrHelpMessage}";
}
}
}
}
if (showToUser)
{
string completionText = "-" + name + colonSuffix;
string tooltip = type + name;
string completionText = $"-{name}{colonSuffix}";
string tooltip = $"{type}{name}{helpMessage}";
listInUse.Add(new CompletionResult(completionText, name, CompletionResultType.ParameterName,
tooltip));
}
@@ -1125,6 +1125,163 @@ param([ValidatePattern(
$res.CompletionMatches[1].CompletionText | Should -BeExactly '$DemoVar2'
}
It 'Should include parameter help message in tool tip - SingleMatch <SingleMatch>' -TestCases @(
@{ SingleMatch = $true }
@{ SingleMatch = $false }
) {
param ($SingleMatch)
Function Test-Function {
param (
[Parameter(HelpMessage = 'Some help message')]
$ParamWithHelp,
$ParamWithoutHelp
)
}
$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 '-ParamWithHelp'
$res.ToolTip | Should -BeExactly $expected
}
It 'Should include parameter help resource message in tool tip - SingleMatch <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 <SingleMatch>' -TestCases @(
@{ SingleMatch = $true }
@{ SingleMatch = $false }
) {
param ($SingleMatch)
Function Test-Function {
[CmdletBinding(DefaultParameterSetName = 'SetWithoutHelp')]
param (
[Parameter(ParameterSetName = 'SetWithHelp', HelpMessage = 'Help Message')]
[Parameter(ParameterSetName = 'SetWithoutHelp')]
[string]
$ParamWithHelp,
[Parameter(ParameterSetName = 'SetWithHelp')]
[switch]
$ParamWithoutHelp
)
}
$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 '-ParamWithHelp'
$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('DynamicParam', [string], $attrCollection)
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$paramDictionary.Add('DynamicParam', $dynParam)
$paramDictionary
}
end {}
}
$expected = '[string] DynamicParam - Howdy partner'
$Script = 'Test-Function -'
$res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-DynamicParam'
$res.Count | Should -Be 1
$res.CompletionText | Should -BeExactly '-DynamicParam'
$res.ToolTip | Should -BeExactly $expected
}
It 'Should have type and name for parameter without help message' {
Function Test-Function {
param (
[Parameter()]
$WithParamAttribute,
$WithoutParamAttribute
)
}
$Script = 'Test-Function -'
$res = (TabExpansion2 -inputScript $Script).CompletionMatches |
Where-Object CompletionText -in '-WithParamAttribute', '-WithoutParamAttribute' |
Sort-Object CompletionText
$res.Count | Should -Be 2
$res.CompletionText[0] | Should -BeExactly '-WithoutParamAttribute'
$res.ToolTip[0] | Should -BeExactly '[Object] WithoutParamAttribute'
$res.CompletionText[1] | Should -BeExactly '-WithParamAttribute'
$res.ToolTip[1] | Should -BeExactly '[Object] WithParamAttribute'
}
It 'Should ignore errors when faling to get HelpMessage resource' {
Function Test-Function {
param (
[Parameter(HelpMessageBaseName="invalid", HelpMessageResourceId="SomeId")]
$InvalidHelpParam
)
}
$expected = '[Object] InvalidHelpParam'
$Script = 'Test-Function -InvalidHelpParam'
$res = (TabExpansion2 -inputScript $Script).CompletionMatches
$res.Count | Should -Be 1
$res.CompletionText | Should -BeExactly '-InvalidHelpParam'
$res.ToolTip | Should -BeExactly $expected
}
Context 'Start-Process -Verb parameter completion' {
BeforeAll {
function GetProcessInfoVerbs([string]$path, [switch]$singleQuote, [switch]$doubleQuote) {