Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter completion for enum parameter against ValidateRange-attributes #17750

Merged
22 changes: 22 additions & 0 deletions src/System.Management.Automation/engine/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,28 @@ private static Type GetCommonType(Type minType, Type maxType)

return resultType;
}

/// <summary>
/// Returns only the elements that passed the attribute's validation.
/// </summary>
/// <param name="elementsToValidate">The objects to validate.</param>
internal IEnumerable GetValidatedElements(IEnumerable elementsToValidate)
{
foreach (var el in elementsToValidate)
{
try
{
ValidateElement(el);
}
catch (ValidationMetadataException)
{
// Element was not in range - drop
continue;
}

yield return el;
}
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1770,28 +1770,36 @@ internal static string ConcatenateStringPathArguments(CommandElementAst stringAs
{
RemoveLastNullCompletionResult(result);

string enumString = LanguagePrimitives.EnumSingleTypeConverter.EnumValues(parameterType);
string separator = CultureInfo.CurrentUICulture.TextInfo.ListSeparator;
string[] enumArray = enumString.Split(separator, StringSplitOptions.RemoveEmptyEntries);
IEnumerable enumValues = LanguagePrimitives.EnumSingleTypeConverter.GetEnumValues(parameterType);

// Exclude values not accepted by ValidateRange-attributes
foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes)
{
if (att is ValidateRangeAttribute rangeAtt)
{
enumValues = rangeAtt.GetValidatedElements(enumValues);
fflaten marked this conversation as resolved.
Show resolved Hide resolved
}
}

string wordToComplete = context.WordToComplete ?? string.Empty;
string quote = HandleDoubleAndSingleQuote(ref wordToComplete);

var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
var enumList = new List<string>();

foreach (string value in enumArray)
foreach (Enum value in enumValues)
{
if (wordToComplete.Equals(value, StringComparison.OrdinalIgnoreCase))
string name = value.ToString();
if (wordToComplete.Equals(name, StringComparison.OrdinalIgnoreCase))
{
string completionText = quote == string.Empty ? value : quote + value + quote;
fullMatch = new CompletionResult(completionText, value, CompletionResultType.ParameterValue, value);
string completionText = quote == string.Empty ? name : quote + name + quote;
fullMatch = new CompletionResult(completionText, name, CompletionResultType.ParameterValue, name);
continue;
}

if (pattern.IsMatch(value))
if (pattern.IsMatch(name))
{
enumList.Add(value);
enumList.Add(name);
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/System.Management.Automation/engine/LanguagePrimitives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2085,6 +2085,14 @@ internal static string EnumValues(Type enumType)
return string.Join(CultureInfo.CurrentUICulture.TextInfo.ListSeparator, enumHashEntry.names);
}

/// <summary>
/// Returns all values for the provided enum type.
/// </summary>
/// <param name="enumType">The enum type to retrieve values from.</param>
/// <returns>Array of enum values for the specified type.</returns>
internal static Array GetEnumValues(Type enumType)
=> EnumSingleTypeConverter.GetEnumHashEntry(enumType).values;

public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase)
{
return EnumSingleTypeConverter.BaseConvertFrom(sourceValue, destinationType, formatProvider, ignoreCase, false);
Expand Down
25 changes: 25 additions & 0 deletions test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1345,6 +1345,31 @@ ConstructorTestClass(int i, bool b)
$res.CompletionMatches[1].CompletionText | Should -BeExactly 'Configuration'
}

It 'Tab completion for enum parameter is filtered against <Name>' -TestCases @(
@{ Name = 'ValidateRange with enum-values'; Attribute = '[ValidateRange([System.ConsoleColor]::Blue, [System.ConsoleColor]::Cyan)]' }
@{ Name = 'ValidateRange with int-values'; Attribute = '[ValidateRange(9, 11)]' }
@{ Name = 'multiple ValidateRange-attributes'; Attribute = '[ValidateRange([System.ConsoleColor]::Blue, [System.ConsoleColor]::Cyan)][ValidateRange([System.ConsoleColor]::Gray, [System.ConsoleColor]::Red)]' }
) {
param($Name, $Attribute)
$functionDefinition = 'param ( {0}[consolecolor]$color )' -f $Attribute
Set-Item -Path function:baz -Value $functionDefinition
$inputStr = 'baz -color '
$res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length
$res.CompletionMatches | Should -HaveCount 3
$res.CompletionMatches[0].CompletionText | Should -BeExactly 'Blue'
$res.CompletionMatches[1].CompletionText | Should -BeExactly 'Cyan'
$res.CompletionMatches[2].CompletionText | Should -BeExactly 'Green'
}

It 'Tab completion for enum parameter is filtered with ValidateRange using rangekind' {
$functionDefinition = 'param ( [ValidateRange([System.Management.Automation.ValidateRangeKind]::NonPositive)][consolecolor]$color )' -f $Attribute
Set-Item -Path function:baz -Value $functionDefinition
$inputStr = 'baz -color '
$res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length
$res.CompletionMatches | Should -HaveCount 1
$res.CompletionMatches[0].CompletionText | Should -BeExactly 'Black' # 0 = NonPositive
}

It "Test [CommandCompletion]::GetNextResult" {
$inputStr = "Get-Command -Type Alias,c"
$res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length
Expand Down