diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index d816892bd7..ac8b404835 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -8421,6 +8421,33 @@ namespace System.Management.Automation } } + /// + /// Calls Get-Command to get command info objects. + /// + /// The fake bound parameters. + /// The parameters to add. + /// Collection of command info objects. + internal static Collection 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 commands = ps.Invoke(); + + return commands; + } + internal static bool IsSplattedVariable(Ast targetExpr) { if (targetExpr is VariableExpressionAst && ((VariableExpressionAst)targetExpr).Splatted) diff --git a/src/System.Management.Automation/engine/GetCommandCommand.cs b/src/System.Management.Automation/engine/GetCommandCommand.cs index a941ce0f2b..9f96c0196e 100644 --- a/src/System.Management.Automation/engine/GetCommandCommand.cs +++ b/src/System.Management.Automation/engine/GetCommandCommand.cs @@ -1694,40 +1694,50 @@ namespace Microsoft.PowerShell.Commands } /// + /// Provides argument completion for Noun parameter. /// public class NounArgumentCompleter : IArgumentCompleter { - /// /// - public IEnumerable CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) + /// Returns completion results for Noun parameter. + /// + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of completion results. + public IEnumerable CompleteArgument( + string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) => CompletionCompleters.GetMatchingResults( + wordToComplete, + possibleCompletionValues: GetCommandNouns(fakeBoundParameters)); + + /// + /// Get sorted set of command nouns using Get-Command. + /// + /// The fake bound parameters. + /// Sorted set of command nouns. + private static SortedSet GetCommandNouns(IDictionary fakeBoundParameters) { - if (fakeBoundParameters == null) - { - throw PSTraceSource.NewArgumentNullException(nameof(fakeBoundParameters)); - } + Collection commands = CompletionCompleters.GetCommandInfo(fakeBoundParameters, "Module", "Verb"); + SortedSet 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 nouns = new HashSet(); - var results = ps.Invoke(); - 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; } } } diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 049c909fad..5bb67a3ab2 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -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 ''" -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 = @'