Add quote handling in Verb, StrictModeVersion, Scope & PropertyType Argument Completers with single helper method (#24839)

This commit is contained in:
Armaan Mcleod
2025-02-01 14:52:52 +05:00
committed by GitHub
parent c505f4ba39
commit 459fc8d285
7 changed files with 241 additions and 148 deletions
@@ -226,31 +226,13 @@ namespace Microsoft.PowerShell.Commands
string wordToComplete,
CommandAst commandAst,
IDictionary fakeBoundParameters)
{
if (!IsRegistryProvider(fakeBoundParameters))
{
yield break;
}
string quote = CompletionCompleters.HandleDoubleAndSingleQuote(ref wordToComplete);
var propertyTypePattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
foreach (string propertyType in s_RegistryPropertyTypes)
{
if (propertyTypePattern.IsMatch(propertyType))
{
string completionText = quote == string.Empty
? propertyType
: quote + propertyType + quote;
yield return new CompletionResult(
completionText,
propertyType,
CompletionResultType.ParameterValue,
GetRegistryPropertyTypeToolTip(propertyType));
}
}
}
=> IsRegistryProvider(fakeBoundParameters)
? CompletionCompleters.GetMatchingResults(
wordToComplete,
possibleCompletionValues: s_RegistryPropertyTypes,
toolTipMapping: GetRegistryPropertyTypeToolTip,
resultType: CompletionResultType.ParameterValue)
: [];
/// <summary>
/// Checks if parameter paths are from Registry provider.
@@ -2664,7 +2664,7 @@ namespace Microsoft.PowerShell.Commands
// -Verb is not supported on non-Windows platforms as well as Windows headless SKUs
if (!Platform.IsWindowsDesktop)
{
yield break;
return [];
}
// Completion: Start-Process -FilePath <path> -Verb <wordToComplete>
@@ -2676,12 +2676,7 @@ namespace Microsoft.PowerShell.Commands
// Complete file verbs if extension exists
if (Path.HasExtension(filePath))
{
foreach (string verb in CompleteFileVerbs(filePath, wordToComplete))
{
yield return new CompletionResult(verb);
}
yield break;
return CompleteFileVerbs(wordToComplete, filePath);
}
// Otherwise check if command is an Application to resolve executable full path with extension
@@ -2699,34 +2694,23 @@ namespace Microsoft.PowerShell.Commands
// Start-Process & Get-Command select first found application based on PATHEXT environment variable
if (commands.Count >= 1)
{
foreach (string verb in CompleteFileVerbs(commands[0].Source, wordToComplete))
{
yield return new CompletionResult(verb);
}
return CompleteFileVerbs(wordToComplete, filePath: commands[0].Source);
}
}
return [];
}
/// <summary>
/// Completes file verbs.
/// </summary>
/// <param name="filePath">The file path to get verbs.</param>
/// <param name="wordToComplete">The word to complete.</param>
/// <param name="filePath">The file path to get verbs.</param>
/// <returns>List of file verbs to complete.</returns>
private static IEnumerable<string> CompleteFileVerbs(string filePath, string wordToComplete)
{
var verbPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
string[] verbs = new ProcessStartInfo(filePath).Verbs;
foreach (string verb in verbs)
{
if (verbPattern.IsMatch(verb))
{
yield return verb;
}
}
}
private static IEnumerable<CompletionResult> CompleteFileVerbs(string wordToComplete, string filePath)
=> CompletionCompleters.GetMatchingResults(
wordToComplete,
possibleCompletionValues: new ProcessStartInfo(filePath).Verbs);
}
#if !UNIX
@@ -8382,6 +8382,45 @@ namespace System.Management.Automation
return quote;
}
/// <summary>
/// Get matching completions from word to complete.
/// This makes it easier to handle different variations of completions with consideration of quotes.
/// </summary>
/// <param name="wordToComplete">The word to complete.</param>
/// <param name="possibleCompletionValues">The possible completion values to iterate.</param>
/// <param name="toolTipMapping">The optional tool tip mapping delegate.</param>
/// <param name="resultType">The optional completion result type. Default is Text.</param>
/// <returns></returns>
internal static IEnumerable<CompletionResult> GetMatchingResults(
string wordToComplete,
IEnumerable<string> possibleCompletionValues,
Func<string, string> toolTipMapping = null,
CompletionResultType resultType = CompletionResultType.Text)
{
string quote = HandleDoubleAndSingleQuote(ref wordToComplete);
var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
foreach (string value in possibleCompletionValues)
{
if (pattern.IsMatch(value))
{
string completionText = quote == string.Empty
? value
: quote + value + quote;
string listItemText = value;
yield return new CompletionResult(
completionText,
listItemText,
resultType,
toolTip: toolTipMapping is null
? listItemText
: toolTipMapping(value));
}
}
}
internal static bool IsSplattedVariable(Ast targetExpr)
{
if (targetExpr is VariableExpressionAst && ((VariableExpressionAst)targetExpr).Splatted)
@@ -29,16 +29,8 @@ namespace System.Management.Automation
string wordToComplete,
CommandAst commandAst,
IDictionary fakeBoundParameters)
{
var scopePattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
foreach (string scope in s_Scopes)
{
if (scopePattern.IsMatch(scope))
{
yield return new CompletionResult(scope);
}
}
}
=> CompletionCompleters.GetMatchingResults(
wordToComplete,
possibleCompletionValues: s_Scopes);
}
}
@@ -2741,17 +2741,9 @@ namespace Microsoft.PowerShell.Commands
string wordToComplete,
CommandAst commandAst,
IDictionary fakeBoundParameters)
{
var strictModeVersionPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
foreach (string version in s_strictModeVersions)
{
if (strictModeVersionPattern.IsMatch(version))
{
yield return new CompletionResult(version);
}
}
}
=> CompletionCompleters.GetMatchingResults(
wordToComplete,
possibleCompletionValues: s_strictModeVersions);
}
#endregion Set-StrictMode
+110 -67
View File
@@ -1332,35 +1332,6 @@ namespace System.Management.Automation
/// <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>
@@ -1384,10 +1355,7 @@ namespace System.Management.Automation
foreach (Type verbType in VerbTypes)
{
if (SessionStateUtilities.CollectionContainsValue(
groups,
GetVerbGroupDisplayName(verbType),
StringComparer.OrdinalIgnoreCase))
if (GroupsContainVerbType(groups, verbType))
{
foreach (VerbInfo verb in FilterVerbsByType(verbs, verbType))
{
@@ -1397,6 +1365,62 @@ namespace System.Management.Automation
}
}
/// <summary>
/// Checks if verb type exists in list of groups.
/// </summary>
/// <param name="groups">The list of groups</param>
/// <param name="verbType">The verb type to check.</param>
/// <returns>True if verb type was found, False if not found.</returns>
private static bool GroupsContainVerbType(string[] groups, Type verbType)
=> SessionStateUtilities.CollectionContainsValue(
groups,
GetVerbGroupDisplayName(verbType),
StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Enumerates field names from a Verb Type.
/// </summary>
/// <param name="verbType">The verb type.</param>
/// <returns>List of field names.</returns>
private static IEnumerable<string> EnumerateFieldNamesFromVerbType(Type verbType)
{
foreach (FieldInfo field in verbType.GetFields())
{
if (field.IsLiteral)
{
yield return field.Name;
}
}
}
/// <summary>
/// Enumerates field names from all Verb Types.
/// </summary>
/// <returns>List of field names.</returns>
private static IEnumerable<string> EnumerateFieldNamesFromAllVerbTypes()
{
foreach (Type verbType in VerbTypes)
{
foreach (string fieldName in EnumerateFieldNamesFromVerbType(verbType))
{
yield return fieldName;
}
}
}
/// <summary>
/// Enumerates command verb names.
/// </summary>
/// <param name="commands">The collection of commands.</param>
/// <returns>List of command verb names.</returns>
private static IEnumerable<string> EnumerateCommandVerbNames(Collection<CmdletInfo> commands)
{
foreach (CmdletInfo command in commands)
{
yield return command.Verb;
}
}
/// <summary>
/// Filters verbs by type.
/// </summary>
@@ -1407,12 +1431,9 @@ namespace System.Management.Automation
{
if (verbs is null || verbs.Length == 0)
{
foreach (FieldInfo field in verbType.GetFields())
foreach (string fieldName in EnumerateFieldNamesFromVerbType(verbType))
{
if (field.IsLiteral)
{
yield return CreateVerbFromField(field, verbType);
}
yield return CreateVerbFromField(fieldName, verbType);
}
yield break;
@@ -1422,17 +1443,14 @@ namespace System.Management.Automation
verbs,
WildcardOptions.IgnoreCase);
foreach (FieldInfo field in verbType.GetFields())
foreach (string fieldName in EnumerateFieldNamesFromVerbType(verbType))
{
if (field.IsLiteral)
{
if (SessionStateUtilities.MatchesAnyWildcardPattern(
field.Name,
if (SessionStateUtilities.MatchesAnyWildcardPattern(
fieldName,
verbPatterns,
defaultValue: false))
{
yield return CreateVerbFromField(field, verbType);
}
{
yield return CreateVerbFromField(fieldName, verbType);
}
}
}
@@ -1440,15 +1458,15 @@ namespace System.Management.Automation
/// <summary>
/// Creates Verb info object from field info.
/// </summary>
/// <param name="field">The field.</param>
/// <param name="fieldName">The field name.</param>
/// <param name="verbType">The verb type.</param>
/// <returns>VerbInfo object.</returns>
private static VerbInfo CreateVerbFromField(FieldInfo field, Type verbType) => new()
private static VerbInfo CreateVerbFromField(string fieldName, Type verbType) => new()
{
Verb = field.Name,
AliasPrefix = VerbAliasPrefixes.GetVerbAliasPrefix(field.Name),
Verb = fieldName,
AliasPrefix = VerbAliasPrefixes.GetVerbAliasPrefix(fieldName),
Group = GetVerbGroupDisplayName(verbType),
Description = VerbDescriptions.GetVerbDescription(field.Name)
Description = VerbDescriptions.GetVerbDescription(fieldName)
};
/// <summary>
@@ -1472,8 +1490,6 @@ namespace System.Management.Automation
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"))
@@ -1494,12 +1510,7 @@ namespace System.Management.Automation
groups = Array.ConvertAll((object[])groupParameterValue, group => group.ToString());
}
foreach (VerbInfo verb in FilterByVerbsAndGroups(verbs, groups))
{
yield return new CompletionResult(verb.Verb);
}
yield break;
return CompleteVerbWithGroups(wordToComplete, groups);
}
// Completion: Get-Command -Noun <noun> -Verb <wordToComplete>
@@ -1520,23 +1531,55 @@ namespace System.Management.Automation
Collection<CmdletInfo> commands = ps.Invoke<CmdletInfo>();
foreach (string verb in FilterByVerbsAndCommands(verbs, commands))
{
yield return new CompletionResult(verb);
}
yield break;
return CompleteVerbWithCommands(wordToComplete, commands);
}
// Complete all verbs by default if above cases not completed
return CompleteVerbForAllTypes(wordToComplete);
}
/// <summary>
/// Completes verb with list of groups.
/// </summary>
/// <param name="wordToComplete">The word to complete.</param>
/// <param name="groups">The list of groups.</param>
/// <returns>List of completions for verb.</returns>
private static IEnumerable<CompletionResult> CompleteVerbWithGroups(string wordToComplete, string[] groups)
{
foreach (Type verbType in VerbTypes)
{
foreach (VerbInfo verb in FilterVerbsByType(verbs, verbType))
if (GroupsContainVerbType(groups, verbType))
{
yield return new CompletionResult(verb.Verb);
foreach (CompletionResult result in CompletionCompleters.GetMatchingResults(
wordToComplete,
possibleCompletionValues: EnumerateFieldNamesFromVerbType(verbType)))
{
yield return result;
}
}
}
}
/// <summary>
/// Completes verb with list of commands.
/// </summary>
/// <param name="wordToComplete">The word to complete.</param>
/// <param name="commands">The list of commands.</param>
/// <returns>List of completions for verb.</returns>
private static IEnumerable<CompletionResult> CompleteVerbWithCommands(string wordToComplete, Collection<CmdletInfo> commands)
=> CompletionCompleters.GetMatchingResults(
wordToComplete,
possibleCompletionValues: EnumerateCommandVerbNames(commands));
/// <summary>
/// Completes verb for all types.
/// </summary>
/// <param name="wordToComplete">The word to complete.</param>
/// <returns>List of completions for verb.</returns>
private static IEnumerable<CompletionResult> CompleteVerbForAllTypes(string wordToComplete)
=> CompletionCompleters.GetMatchingResults(
wordToComplete,
possibleCompletionValues: EnumerateFieldNamesFromAllVerbTypes());
}
private static readonly Dictionary<string, bool> s_validVerbs = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);