Skip to content

Commit

Permalink
Filter completion for enum parameter against ValidateRange attribut…
Browse files Browse the repository at this point in the history
…es (#17750)
  • Loading branch information
fflaten committed May 22, 2023
1 parent 06ab3b1 commit c57ada2
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 9 deletions.
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 @@ -1796,28 +1796,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);
}
}

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 @@ -2076,6 +2076,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 @@ -1597,6 +1597,31 @@ class InheritedClassTest : System.Attribute
$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

0 comments on commit c57ada2

Please sign in to comment.