Add -Verb argument completer for Get-Verb/ Get-Command and refactor Get-Verb (#20286)

This commit is contained in:
Armaan Mcleod 2023-12-17 03:34:51 +11:00 committed by GitHub
parent 72a3caa841
commit 0e18be6659
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 290 additions and 46 deletions

View File

@ -1,10 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Reflection;
using static System.Management.Automation.Verbs;
namespace Microsoft.PowerShell.Commands
{
@ -19,6 +17,7 @@ namespace Microsoft.PowerShell.Commands
/// Optional Verb filter.
/// </summary>
[Parameter(ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[ArgumentCompleter(typeof(VerbArgumentCompleter))]
public string[] Verb
{
get; set;
@ -39,45 +38,9 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
protected override void ProcessRecord()
{
Type[] verbTypes = new Type[] { typeof(VerbsCommon), typeof(VerbsCommunications), typeof(VerbsData),
typeof(VerbsDiagnostic), typeof(VerbsLifecycle), typeof(VerbsOther), typeof(VerbsSecurity) };
Collection<WildcardPattern> matchingVerbs = SessionStateUtilities.CreateWildcardsFromStrings(
this.Verb,
WildcardOptions.IgnoreCase
);
foreach (Type type in verbTypes)
foreach (VerbInfo verb in FilterByVerbsAndGroups(Verb, Group))
{
string groupName = type.Name.Substring(5);
if (this.Group != null)
{
if (!SessionStateUtilities.CollectionContainsValue(this.Group, groupName, StringComparer.OrdinalIgnoreCase))
{
continue;
}
}
foreach (FieldInfo field in type.GetFields())
{
if (field.IsLiteral)
{
if (this.Verb != null)
{
if (!SessionStateUtilities.MatchesAnyWildcardPattern(field.Name, matchingVerbs, false))
{
continue;
}
}
VerbInfo verb = new();
verb.Verb = field.Name;
verb.AliasPrefix = VerbAliasPrefixes.GetVerbAliasPrefix(field.Name);
verb.Group = groupName;
verb.Description = VerbDescriptions.GetVerbDescription(field.Name);
WriteObject(verb);
}
}
WriteObject(verb);
}
}
}

View File

@ -12,6 +12,7 @@ using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
using static System.Management.Automation.Verbs;
using Dbg = System.Management.Automation.Diagnostics;
namespace Microsoft.PowerShell.Commands
@ -71,6 +72,7 @@ namespace Microsoft.PowerShell.Commands
/// Gets or sets the verb parameter to the cmdlet.
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "CmdletSet")]
[ArgumentCompleter(typeof(VerbArgumentCompleter))]
public string[] Verb
{
get

View File

@ -1,10 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Management.Automation.Language;
using System.Reflection;
using Microsoft.PowerShell.Commands;
using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation
@ -1157,10 +1160,7 @@ namespace System.Management.Automation
{
static Verbs()
{
Type[] verbTypes = new Type[] { typeof(VerbsCommon), typeof(VerbsCommunications), typeof(VerbsData),
typeof(VerbsDiagnostic), typeof(VerbsLifecycle), typeof(VerbsOther), typeof(VerbsSecurity) };
foreach (Type type in verbTypes)
foreach (Type type in VerbTypes)
{
foreach (FieldInfo field in type.GetFields())
{
@ -1311,6 +1311,234 @@ namespace System.Management.Automation
#endif
}
/// <summary>
/// Gets all verb types.
/// </summary>
/// <value>List of all verb types.</value>
private static Type[] VerbTypes => new Type[] {
typeof(VerbsCommon),
typeof(VerbsCommunications),
typeof(VerbsData),
typeof(VerbsDiagnostic),
typeof(VerbsLifecycle),
typeof(VerbsOther),
typeof(VerbsSecurity)
};
/// <summary>
/// Gets verb group display name from type.
/// </summary>
/// <param name="verbType">The verb type.</param>
/// <returns>Verb group display name.</returns>
private static string GetVerbGroupDisplayName(Type verbType) => verbType.Name.Substring(5);
/// <summary>
/// Filters by verbs and commands.
/// </summary>
/// <param name="verbs">The array of verbs.</param>
/// <param name="commands">The collection of commands.</param>
/// <returns>List of Verbs.</returns>
private static IEnumerable<string> FilterByVerbsAndCommands(string[] verbs, Collection<CmdletInfo> commands)
{
if (commands is null || commands.Count == 0)
{
yield break;
}
Collection<WildcardPattern> verbPatterns = SessionStateUtilities.CreateWildcardsFromStrings(
verbs,
WildcardOptions.IgnoreCase);
foreach (CmdletInfo command in commands)
{
if (SessionStateUtilities.MatchesAnyWildcardPattern(
command.Verb,
verbPatterns,
defaultValue: false))
{
yield return command.Verb;
}
}
}
/// <summary>
/// Filters by verbs and groups.
/// </summary>
/// <param name="verbs">The array of verbs.</param>
/// <param name="groups">The array of groups.</param>
/// <returns>List of Verbs.</returns>
internal static IEnumerable<VerbInfo> FilterByVerbsAndGroups(string[] verbs, string[] groups)
{
if (groups is null || groups.Length == 0)
{
foreach (Type verbType in VerbTypes)
{
foreach (VerbInfo verb in FilterVerbsByType(verbs, verbType))
{
yield return verb;
}
}
yield break;
}
foreach (Type verbType in VerbTypes)
{
if (SessionStateUtilities.CollectionContainsValue(
groups,
GetVerbGroupDisplayName(verbType),
StringComparer.OrdinalIgnoreCase))
{
foreach (VerbInfo verb in FilterVerbsByType(verbs, verbType))
{
yield return verb;
}
}
}
}
/// <summary>
/// Filters verbs by type.
/// </summary>
/// <param name="verbs">The array of verbs.</param>
/// <param name="verbType">The verb type.</param>
/// <returns>List of Verbs.</returns>
private static IEnumerable<VerbInfo> FilterVerbsByType(string[] verbs, Type verbType)
{
if (verbs is null || verbs.Length == 0)
{
foreach (FieldInfo field in verbType.GetFields())
{
if (field.IsLiteral)
{
yield return CreateVerbFromField(field, verbType);
}
}
yield break;
}
Collection<WildcardPattern> verbPatterns = SessionStateUtilities.CreateWildcardsFromStrings(
verbs,
WildcardOptions.IgnoreCase);
foreach (FieldInfo field in verbType.GetFields())
{
if (field.IsLiteral)
{
if (SessionStateUtilities.MatchesAnyWildcardPattern(
field.Name,
verbPatterns,
defaultValue: false))
{
yield return CreateVerbFromField(field, verbType);
}
}
}
}
/// <summary>
/// Creates Verb info object from field info.
/// </summary>
/// <param name="field">The field.</param>
/// <param name="verbType">The verb type.</param>
/// <returns>VerbInfo object.</returns>
private static VerbInfo CreateVerbFromField(FieldInfo field, Type verbType) => new()
{
Verb = field.Name,
AliasPrefix = VerbAliasPrefixes.GetVerbAliasPrefix(field.Name),
Group = GetVerbGroupDisplayName(verbType),
Description = VerbDescriptions.GetVerbDescription(field.Name)
};
/// <summary>
/// Provides argument completion for Verb parameter.
/// </summary>
public class VerbArgumentCompleter : IArgumentCompleter
{
/// <summary>
/// Returns completion results for verb 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)
{
var verbs = new string[] { wordToComplete + "*" };
// Completion: Get-Verb -Group <group> -Verb <wordToComplete>
if (commandName.Equals("Get-Verb", StringComparison.OrdinalIgnoreCase)
&& fakeBoundParameters.Contains("Group"))
{
string[] groups = null;
object groupParameterValue = fakeBoundParameters["Group"];
Type groupParameterValueType = groupParameterValue.GetType();
if (groupParameterValueType == typeof(string))
{
groups = new string[] { groupParameterValue.ToString() };
}
else if (groupParameterValueType.IsArray
&& groupParameterValueType.GetElementType() == typeof(object))
{
groups = Array.ConvertAll((object[])groupParameterValue, group => group.ToString());
}
foreach (VerbInfo verb in FilterByVerbsAndGroups(verbs, groups))
{
yield return new CompletionResult(verb.Verb);
}
yield break;
}
// Completion: Get-Command -Noun <noun> -Verb <wordToComplete>
else if (commandName.Equals("Get-Command", StringComparison.OrdinalIgnoreCase)
&& fakeBoundParameters.Contains("Noun"))
{
using var ps = PowerShell.Create(RunspaceMode.CurrentRunspace);
var commandInfo = new CmdletInfo("Get-Command", typeof(GetCommandCommand));
ps.AddCommand(commandInfo);
ps.AddParameter("Noun", fakeBoundParameters["Noun"]);
if (fakeBoundParameters.Contains("Module"))
{
ps.AddParameter("Module", fakeBoundParameters["Module"]);
}
Collection<CmdletInfo> commands = ps.Invoke<CmdletInfo>();
foreach (string verb in FilterByVerbsAndCommands(verbs, commands))
{
yield return new CompletionResult(verb);
}
yield break;
}
// Complete all verbs by default if above cases not completed
foreach (Type verbType in VerbTypes)
{
foreach (VerbInfo verb in FilterVerbsByType(verbs, verbType))
{
yield return new CompletionResult(verb.Verb);
}
}
}
}
private static readonly Dictionary<string, bool> s_validVerbs = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, string[]> s_recommendedAlternateVerbs = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);

View File

@ -744,6 +744,57 @@ ConstructorTestClass(int i, bool b)
$res.CompletionMatches[0].CompletionText | Should -BeExactly '$TestVar1'
}
Context 'Get-Verb & Get-Command -Verb parameter completion' {
BeforeAll {
$allVerbs = 'Add Approve Assert Backup Block Build Checkpoint Clear Close Compare Complete Compress Confirm Connect Convert ConvertFrom ConvertTo Copy Debug Deny Deploy Disable Disconnect Dismount Edit Enable Enter Exit Expand Export Find Format Get Grant Group Hide Import Initialize Install Invoke Join Limit Lock Measure Merge Mount Move New Open Optimize Out Ping Pop Protect Publish Push Read Receive Redo Register Remove Rename Repair Request Reset Resize Resolve Restart Restore Resume Revoke Save Search Select Send Set Show Skip Split Start Step Stop Submit Suspend Switch Sync Test Trace Unblock Undo Uninstall Unlock Unprotect Unpublish Unregister Update Use Wait Watch Write'
$verbsStartingWithRe = 'Read Receive Redo Register Remove Rename Repair Request Reset Resize Resolve Restart Restore Resume Revoke'
$verbsStartingWithEx = 'Exit Expand Export'
$verbsStartingWithConv = 'Convert ConvertFrom ConvertTo'
$lifeCycleVerbsStartingWithRe = 'Register Request Restart Resume'
$dataVerbsStartingwithEx = 'Expand Export'
$lifeCycleAndCommmonVerbsStartingWithRe = 'Redo Register Remove Rename Request Reset Resize Restart Resume'
$allLifeCycleAndCommonVerbs = 'Add Approve Assert Build Clear Close Complete Confirm Copy Deny Deploy Disable Enable Enter Exit Find Format Get Hide Install Invoke Join Lock Move New Open Optimize Pop Push Redo Register Remove Rename Request Reset Resize Restart Resume Search Select Set Show Skip Split Start Step Stop Submit Suspend Switch Undo Uninstall Unlock Unregister Wait Watch'
$allJsonVerbs = 'ConvertFrom ConvertTo Test'
$jsonVerbsStartingWithConv = 'ConvertFrom ConvertTo'
$allJsonAndJobVerbs = 'ConvertFrom ConvertTo Debug Get Receive Remove Start Stop Test Wait'
$jsonAndJobVerbsStartingWithSt = 'Start Stop'
$allObjectVerbs = 'Compare ForEach Group Measure New Select Sort Tee Where'
$utilityModuleObjectVerbs = 'Compare Group Measure New Select Sort Tee'
$utilityModuleObjectVerbsStartingWithS = 'Select Sort'
$coreModuleObjectVerbs = 'ForEach Where'
}
It "Should complete Verb parameter for '<TextInput>'" -TestCases @(
@{ TextInput = 'Get-Verb -Verb '; ExpectedVerbs = $allVerbs }
@{ TextInput = 'Get-Verb -Group Lifecycle, Common -Verb '; ExpectedVerbs = $allLifeCycleAndCommonVerbs }
@{ TextInput = 'Get-Verb -Verb Re'; ExpectedVerbs = $verbsStartingWithRe }
@{ TextInput = 'Get-Verb -Group Lifecycle -Verb Re'; ExpectedVerbs = $lifeCycleVerbsStartingWithRe }
@{ TextInput = 'Get-Verb -Group Lifecycle, Common -Verb Re'; ExpectedVerbs = $lifeCycleAndCommmonVerbsStartingWithRe }
@{ TextInput = 'Get-Verb -Verb Ex'; ExpectedVerbs = $verbsStartingWithEx }
@{ TextInput = 'Get-Verb -Group Data -Verb Ex'; ExpectedVerbs = $dataVerbsStartingwithEx }
@{ TextInput = 'Get-Verb -Group NonExistentGroup -Verb '; ExpectedVerbs = '' }
@{ TextInput = 'Get-Verb -Verb Conv'; ExpectedVerbs = $verbsStartingWithConv }
@{ TextInput = 'Get-Command -Verb '; ExpectedVerbs = $allVerbs }
@{ TextInput = 'Get-Command -Verb Re'; ExpectedVerbs = $verbsStartingWithRe }
@{ TextInput = 'Get-Command -Verb Ex'; ExpectedVerbs = $verbsStartingWithEx }
@{ TextInput = 'Get-Command -Verb Conv'; ExpectedVerbs = $verbsStartingWithConv }
@{ TextInput = 'Get-Command -Noun Json -Verb '; ExpectedVerbs = $allJsonVerbs }
@{ TextInput = 'Get-Command -Noun Json -Verb Conv'; ExpectedVerbs = $jsonVerbsStartingWithConv }
@{ TextInput = 'Get-Command -Noun Json, Job -Verb '; ExpectedVerbs = $allJsonAndJobVerbs }
@{ TextInput = 'Get-Command -Noun Json, Job -Verb St'; ExpectedVerbs = $jsonAndJobVerbsStartingWithSt }
@{ TextInput = 'Get-Command -Noun NonExistentNoun -Verb '; ExpectedVerbs = '' }
@{ TextInput = 'Get-Command -Noun Object -Module Microsoft.PowerShell.Utility,Microsoft.PowerShell.Core -Verb '; ExpectedVerbs = $allObjectVerbs }
@{ TextInput = 'Get-Command -Noun Object -Module Microsoft.PowerShell.Utility -Verb '; ExpectedVerbs = $utilityModuleObjectVerbs }
@{ TextInput = 'Get-Command -Noun Object -Module Microsoft.PowerShell.Utility -Verb S'; ExpectedVerbs = $utilityModuleObjectVerbsStartingWithS }
@{ TextInput = 'Get-Command -Noun Object -Module Microsoft.PowerShell.Core -Verb '; ExpectedVerbs = $coreModuleObjectVerbs }
) {
param($TextInput, $ExpectedVerbs)
$res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length
$completionText = $res.CompletionMatches.CompletionText | Sort-Object
$completionText -join ' ' | Should -BeExactly $ExpectedVerbs
}
}
Context 'StrictMode Version parameter completion' {
BeforeAll {
$allStrictModeVersions = '1.0 2.0 3.0 Latest'