Add completion single/double quote support for -Noun parameter (#24977)

This commit is contained in:
Armaan Mcleod
2025-02-11 14:20:19 +05:00
committed by GitHub
parent 9941e38c19
commit 0f7ba437eb
3 changed files with 140 additions and 23 deletions
@@ -8421,6 +8421,33 @@ namespace System.Management.Automation
}
}
/// <summary>
/// Calls Get-Command to get command info objects.
/// </summary>
/// <param name="fakeBoundParameters">The fake bound parameters.</param>
/// <param name="parametersToAdd">The parameters to add.</param>
/// <returns>Collection of command info objects.</returns>
internal static Collection<CommandInfo> GetCommandInfo(
IDictionary fakeBoundParameters,
params string[] parametersToAdd)
{
using var ps = PowerShell.Create(RunspaceMode.CurrentRunspace);
ps.AddCommand("Get-Command");
foreach (string parameter in parametersToAdd)
{
if (fakeBoundParameters.Contains(parameter))
{
ps.AddParameter(parameter, fakeBoundParameters[parameter]);
}
}
Collection<CommandInfo> commands = ps.Invoke<CommandInfo>();
return commands;
}
internal static bool IsSplattedVariable(Ast targetExpr)
{
if (targetExpr is VariableExpressionAst && ((VariableExpressionAst)targetExpr).Splatted)
@@ -1694,40 +1694,50 @@ namespace Microsoft.PowerShell.Commands
}
/// <summary>
/// Provides argument completion for Noun parameter.
/// </summary>
public class NounArgumentCompleter : IArgumentCompleter
{
/// <summary>
/// </summary>
public IEnumerable<CompletionResult> CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters)
/// Returns completion results for Noun parameter.
/// </summary>
/// <param name="commandName">The command name.</param>
/// <param name="parameterName">The parameter name.</param>
/// <param name="wordToComplete">The word to complete.</param>
/// <param name="commandAst">The command AST.</param>
/// <param name="fakeBoundParameters">The fake bound parameters.</param>
/// <returns>List of completion results.</returns>
public IEnumerable<CompletionResult> CompleteArgument(
string commandName,
string parameterName,
string wordToComplete,
CommandAst commandAst,
IDictionary fakeBoundParameters) => CompletionCompleters.GetMatchingResults(
wordToComplete,
possibleCompletionValues: GetCommandNouns(fakeBoundParameters));
/// <summary>
/// Get sorted set of command nouns using Get-Command.
/// </summary>
/// <param name="fakeBoundParameters">The fake bound parameters.</param>
/// <returns>Sorted set of command nouns.</returns>
private static SortedSet<string> GetCommandNouns(IDictionary fakeBoundParameters)
{
if (fakeBoundParameters == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(fakeBoundParameters));
}
Collection<CommandInfo> commands = CompletionCompleters.GetCommandInfo(fakeBoundParameters, "Module", "Verb");
SortedSet<string> nouns = new(StringComparer.OrdinalIgnoreCase);
var commandInfo = new CmdletInfo("Get-Command", typeof(GetCommandCommand));
var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace)
.AddCommand(commandInfo)
.AddParameter("Noun", wordToComplete + "*");
if (fakeBoundParameters.Contains("Module"))
foreach (CommandInfo command in commands)
{
ps.AddParameter("Module", fakeBoundParameters["Module"]);
}
HashSet<string> nouns = new HashSet<string>();
var results = ps.Invoke<CommandInfo>();
foreach (var result in results)
{
var dash = result.Name.IndexOf('-');
if (dash != -1)
string commandName = command.Name;
int dashIndex = commandName.IndexOf('-');
if (dashIndex != -1)
{
nouns.Add(result.Name.Substring(dash + 1));
string noun = commandName.Substring(dashIndex + 1);
nouns.Add(noun);
}
}
return nouns.Order().Select(static noun => new CompletionResult(noun, noun, CompletionResultType.Text, noun));
return nouns;
}
}
}
@@ -1078,6 +1078,86 @@ ConstructorTestClass(int i, bool b)
}
}
Context 'Get-Command -Noun parameter completion' {
BeforeAll {
function GetModuleCommandNouns(
[string]$Module,
[string]$Verb,
[switch]$SingleQuote,
[switch]$DoubleQuote)
{
$commandParams = @{}
if ($PSBoundParameters.ContainsKey('Module')) {
$commandParams['Module'] = $Module
}
if ($PSBoundParameters.ContainsKey('Verb')) {
$commandParams['Verb'] = $Verb
}
$nouns = (Get-Command @commandParams).Noun
if ($SingleQuote) {
return ($nouns | ForEach-Object { "'$_'" })
}
elseif ($DoubleQuote) {
return ($nouns | ForEach-Object { """$_""" })
}
return $nouns
}
$utilityModuleName = 'Microsoft.PowerShell.Utility'
$allUtilityCommandNouns = GetModuleCommandNouns -Module $utilityModuleName
$allUtilityCommandNounsSingleQuote = GetModuleCommandNouns -Module $utilityModuleName -SingleQuote
$allUtilityCommandNounsDoubleQuote = GetModuleCommandNouns -Module $utilityModuleName -DoubleQuote
$utilityCommandNounsStartingWithF = $allUtilityCommandNouns | Where-Object { $_ -like 'F*'}
$utilityCommandNounsStartingWithFSingleQuote = $allUtilityCommandNounsSingleQuote | Where-Object { $_ -like "'F*"}
$utilityCommandNounsStartingWithFDoubleQuote = $allUtilityCommandNounsDoubleQuote | Where-Object { $_ -like """F*"}
$allUtilityCommandNounsWithConvertToVerb = GetModuleCommandNouns -Module $utilityModuleName -Verb 'ConvertTo'
$allUtilityCommandNounsWithConvertToVerbSingleQuote = GetModuleCommandNouns -Module $utilityModuleName -SingleQuote -Verb 'ConvertTo'
$allUtilityCommandNounsWithConvertToVerbDoubleQuote = GetModuleCommandNouns -Module $utilityModuleName -DoubleQuote -Verb 'ConvertTo'
$utilityCommandNounsWithConvertToVerb = $allUtilityCommandNounsWithConvertToVerb | Where-Object { $_ -in 'CliXml', 'Csv', 'Html', 'Json', 'Xml' }
$utilityCommandNounsWithConvertToVerbSingleQuote = $allUtilityCommandNounsWithConvertToVerbSingleQuote | Where-Object { $_ -in "'CliXml'", "'Csv'", "'Html'", "'Json'", "'Xml'" }
$utilityCommandNounsWithConvertToVerbDoubleQuote = $allUtilityCommandNounsWithConvertToVerbDoubleQuote | Where-Object { $_ -in """CliXml""", """Csv""", """Html""", """Json""", """Xml""" }
$utilityCommandNounsWithConvertToVerbStartingWithC = $allUtilityCommandNounsWithConvertToVerb | Where-Object { $_ -in 'CliXml', 'Csv' }
$utilityCommandNounsWithConvertToVerbStartingWithCSingleQuote = $allUtilityCommandNounsWithConvertToVerbSingleQuote | Where-Object { $_ -in "'CliXml'", "'Csv'" }
$utilityCommandNounsWithConvertToVerbStartingWithCDoubleQuote = $allUtilityCommandNounsWithConvertToVerbDoubleQuote | Where-Object { $_ -in """CliXml""", """Csv""" }
}
It "Should complete Noun for '<TextInput>'" -TestCases @(
@{ TextInput = "Get-Command -Module $utilityModuleName -Noun "; ExpectedNouns = $allUtilityCommandNouns }
@{ TextInput = "Get-Command -Module $utilityModuleName -Noun '"; ExpectedNouns = $allUtilityCommandNounsSingleQuote }
@{ TextInput = "Get-Command -Module $utilityModuleName -Noun """; ExpectedNouns = $allUtilityCommandNounsDoubleQuote }
@{ TextInput = "Get-Command -Module $utilityModuleName -Noun F"; ExpectedNouns = $utilityCommandNounsStartingWithF }
@{ TextInput = "Get-Command -Module $utilityModuleName -Noun 'F"; ExpectedNouns = $utilityCommandNounsStartingWithFSingleQuote }
@{ TextInput = "Get-Command -Module $utilityModuleName -Noun ""F"; ExpectedNouns = $utilityCommandNounsStartingWithFDoubleQuote }
@{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun "; ExpectedNouns = $utilityCommandNounsWithConvertToVerb }
@{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun '"; ExpectedNouns = $utilityCommandNounsWithConvertToVerbSingleQuote }
@{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun """; ExpectedNouns = $utilityCommandNounsWithConvertToVerbDoubleQuote }
@{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun C"; ExpectedNouns = $utilityCommandNounsWithConvertToVerbStartingWithC }
@{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun 'C"; ExpectedNouns = $utilityCommandNounsWithConvertToVerbStartingWithCSingleQuote }
@{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun ""C"; ExpectedNouns = $utilityCommandNounsWithConvertToVerbStartingWithCDoubleQuote }
) {
param($TextInput, $ExpectedNouns)
$res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length
$completionText = $res.CompletionMatches.CompletionText
# Avoid using Sort-Object -Unique because it generates different order than SortedSet on MacOS/Linux
$sortedSetExpectedNouns = [System.Collections.Generic.SortedSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
foreach ($noun in $ExpectedNouns)
{
$sortedSetExpectedNouns.Add($noun) | Out-Null
}
$completionText -join ' ' | Should -BeExactly ($sortedSetExpectedNouns -join ' ')
}
}
Context "Format cmdlet's View paramter completion" {
BeforeAll {
$viewDefinition = @'