diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index e7b289418f..5b00b9df42 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1311,6 +1311,28 @@ namespace System.Management.Automation return resultType; } + + /// + /// Returns only the elements that passed the attribute's validation. + /// + /// The objects to validate. + 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; + } + } } /// diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 1cf7082b3c..8dae46f339 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1796,9 +1796,16 @@ namespace System.Management.Automation { 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); @@ -1806,18 +1813,19 @@ namespace System.Management.Automation var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); var enumList = new List(); - 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); } } diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index ac3c1c1a70..a93bd1658d 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -2076,6 +2076,14 @@ namespace System.Management.Automation return string.Join(CultureInfo.CurrentUICulture.TextInfo.ListSeparator, enumHashEntry.names); } + /// + /// Returns all values for the provided enum type. + /// + /// The enum type to retrieve values from. + /// Array of enum values for the specified type. + 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); diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 1016650db3..fee6c8c000 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1597,6 +1597,31 @@ class InheritedClassTest : System.Attribute $res.CompletionMatches[1].CompletionText | Should -BeExactly 'Configuration' } + It 'Tab completion for enum parameter is filtered against ' -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