From 044fbf0955b88ec7b66dad83c6af075561011416 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sat, 23 Jul 2022 10:15:51 +0000 Subject: [PATCH 01/11] filter enum param completion against validaterange --- .../CommandCompletion/CompletionCompleters.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 679ad427e9d..7551190c582 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1770,9 +1770,28 @@ private static void ProcessParameter( { RemoveLastNullCompletionResult(result); - string enumString = LanguagePrimitives.EnumSingleTypeConverter.EnumValues(parameterType); - string separator = CultureInfo.CurrentUICulture.TextInfo.ListSeparator; - string[] enumArray = enumString.Split(separator, StringSplitOptions.RemoveEmptyEntries); + var enumNames = new List(); + foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) + { + if (att is ValidateRangeAttribute rangeAtt) + { + foreach (var value in parameterType.GetEnumValues()) + { + // ValidateRangeAttribute ctor already validated MinRange/MaxRange not null and comparable type + if (LanguagePrimitives.Compare(value, rangeAtt.MaxRange) != 1 + && LanguagePrimitives.Compare(value, rangeAtt.MinRange) != -1) + { + enumNames.Add(value.ToString()); + } + } + break; + } + } + + if (enumNames.Count == 0) + { + enumNames.AddRange(parameterType.GetEnumNames()); + } string wordToComplete = context.WordToComplete ?? string.Empty; string quote = HandleDoubleAndSingleQuote(ref wordToComplete); @@ -1780,7 +1799,7 @@ private static void ProcessParameter( var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); var enumList = new List(); - foreach (string value in enumArray) + foreach (string value in enumNames) { if (wordToComplete.Equals(value, StringComparison.OrdinalIgnoreCase)) { From 43472db1211b1d37e9cf6cb8671ecb943eefda94 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sat, 23 Jul 2022 10:16:13 +0000 Subject: [PATCH 02/11] add test --- .../Host/TabCompletion/TabCompletion.Tests.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 790dbe9ecc8..5525ac22f60 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1345,6 +1345,16 @@ ConstructorTestClass(int i, bool b) $res.CompletionMatches[1].CompletionText | Should -BeExactly 'Configuration' } + It 'Tab completion for enum type parameter with ValidateRange is filtered' { + function baz ([ValidateRange([System.ConsoleColor]::Blue, [System.ConsoleColor]::Cyan)][consolecolor]$color) {} + $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 "Test [CommandCompletion]::GetNextResult" { $inputStr = "Get-Command -Type Alias,c" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length From 5ff360835636d6801af95dbce714b55d5231834d Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sat, 23 Jul 2022 13:57:38 +0000 Subject: [PATCH 03/11] support multiple validaterange-attributes --- .../CommandCompletion/CompletionCompleters.cs | 19 +++++++------------ .../TabCompletion/TabCompletion.Tests.ps1 | 10 ++++++++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 7551190c582..56a41c3136c 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1770,36 +1770,31 @@ private static void ProcessParameter( { RemoveLastNullCompletionResult(result); - var enumNames = new List(); + var enumValues = parameterType.GetEnumValues().Cast().ToList(); + // Exclude values not accepted by ValidateRange-attributes foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) { if (att is ValidateRangeAttribute rangeAtt) { - foreach (var value in parameterType.GetEnumValues()) + for (int i = enumValues.Count - 1; i >= 0; i--) { // ValidateRangeAttribute ctor already validated MinRange/MaxRange not null and comparable type - if (LanguagePrimitives.Compare(value, rangeAtt.MaxRange) != 1 - && LanguagePrimitives.Compare(value, rangeAtt.MinRange) != -1) + if (LanguagePrimitives.Compare(enumValues[i], rangeAtt.MaxRange) == 1 + || LanguagePrimitives.Compare(enumValues[i], rangeAtt.MinRange) == -1) { - enumNames.Add(value.ToString()); + enumValues.RemoveAt(i); } } - break; } } - if (enumNames.Count == 0) - { - enumNames.AddRange(parameterType.GetEnumNames()); - } - string wordToComplete = context.WordToComplete ?? string.Empty; string quote = HandleDoubleAndSingleQuote(ref wordToComplete); var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); var enumList = new List(); - foreach (string value in enumNames) + foreach (string value in enumValues.ConvertAll(v => v.ToString())) { if (wordToComplete.Equals(value, StringComparison.OrdinalIgnoreCase)) { diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 5525ac22f60..59e28744110 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1345,8 +1345,14 @@ ConstructorTestClass(int i, bool b) $res.CompletionMatches[1].CompletionText | Should -BeExactly 'Configuration' } - It 'Tab completion for enum type parameter with ValidateRange is filtered' { - function baz ([ValidateRange([System.ConsoleColor]::Blue, [System.ConsoleColor]::Cyan)][consolecolor]$color) {} + 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 enum-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 From 015bcc6c749b96839a9e49e17ce688460230a1af Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sun, 24 Jul 2022 17:20:53 +0000 Subject: [PATCH 04/11] add helper in ValidateRange to filter list --- .../engine/Attributes.cs | 15 +++++++++++++++ .../CommandCompletion/CompletionCompleters.cs | 10 +--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 9965824a927..26277a7dad1 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1311,6 +1311,21 @@ private static Type GetCommonType(Type minType, Type maxType) return resultType; } + + internal List FilterElements(IEnumerable elements) + { + var result = new List(); + foreach (var value in elements) + { + try + { + ValidateElement(value); + result.Add(value); + } + catch { } + } + return result; + } } /// diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 56a41c3136c..6a7df0ea710 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1776,15 +1776,7 @@ private static void ProcessParameter( { if (att is ValidateRangeAttribute rangeAtt) { - for (int i = enumValues.Count - 1; i >= 0; i--) - { - // ValidateRangeAttribute ctor already validated MinRange/MaxRange not null and comparable type - if (LanguagePrimitives.Compare(enumValues[i], rangeAtt.MaxRange) == 1 - || LanguagePrimitives.Compare(enumValues[i], rangeAtt.MinRange) == -1) - { - enumValues.RemoveAt(i); - } - } + enumValues = rangeAtt.FilterElements(enumValues); } } From 17ec50953422932b303ba572e5058ab05a19a569 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Sun, 24 Jul 2022 17:21:04 +0000 Subject: [PATCH 05/11] add test for rangekind --- .../Host/TabCompletion/TabCompletion.Tests.ps1 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 59e28744110..e0d49ca9fcb 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1347,7 +1347,7 @@ ConstructorTestClass(int i, bool b) 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 enum-values'; Attribute = '[ValidateRange(9, 11)]' } + @{ 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) @@ -1361,6 +1361,15 @@ ConstructorTestClass(int i, bool b) $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 From 449b305476afbb985c1902b2ca87e07cc9e5404f Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:49:47 +0000 Subject: [PATCH 06/11] add helper to EnumSingleTypeConverter --- .../engine/LanguagePrimitives.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index b697e8e750f..093c8c78ea5 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -2085,6 +2085,13 @@ internal static string EnumValues(Type enumType) return string.Join(CultureInfo.CurrentUICulture.TextInfo.ListSeparator, enumHashEntry.names); } + /// + /// Returns all values for the provided enum type. + /// + /// The enum type to retrieve values from. + 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); From 30c43e67ac66602c6cd96a618083573ef9e927ff Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:50:50 +0000 Subject: [PATCH 07/11] rename FilterElements() to GetValidatedElements() --- .../engine/Attributes.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 26277a7dad1..858471a003a 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1312,18 +1312,26 @@ private static Type GetCommonType(Type minType, Type maxType) return resultType; } - internal List FilterElements(IEnumerable elements) + /// + /// Returns a list containing only the elements that passed the attribute's validation. + /// + /// The objects to validate. + internal IList GetValidatedElements(IList elementsToValidate) { - var result = new List(); - foreach (var value in elements) + var result = new List(elementsToValidate.Count); + foreach (var el in elementsToValidate) { try { - ValidateElement(value); - result.Add(value); + ValidateElement(el); + result.Add(el); + } + catch (ValidationMetadataException) + { + // Element was not in range - drop } - catch { } } + return result; } } From ce14d1ebb99fce72a5f58286c1e23217245468d2 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:51:19 +0000 Subject: [PATCH 08/11] use EnumSingleTypeConverter to get enum values --- .../CommandCompletion/CompletionCompleters.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 6a7df0ea710..6323e8fa8e7 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1770,13 +1770,13 @@ private static void ProcessParameter( { RemoveLastNullCompletionResult(result); - var enumValues = parameterType.GetEnumValues().Cast().ToList(); + IList enumValues = LanguagePrimitives.EnumSingleTypeConverter.GetEnumValues(parameterType).Cast().ToList(); // Exclude values not accepted by ValidateRange-attributes foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) { if (att is ValidateRangeAttribute rangeAtt) { - enumValues = rangeAtt.FilterElements(enumValues); + enumValues = rangeAtt.GetValidatedElements(enumValues); } } @@ -1786,18 +1786,19 @@ private static void ProcessParameter( var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); var enumList = new List(); - foreach (string value in enumValues.ConvertAll(v => v.ToString())) + 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); } } From 3650c609762e52042eef15ba03c67ba4ec910443 Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Wed, 27 Jul 2022 17:05:47 +0000 Subject: [PATCH 09/11] add missing returns comment to GetEnumValues() --- src/System.Management.Automation/engine/LanguagePrimitives.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 093c8c78ea5..f669f02e5bb 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -2089,6 +2089,7 @@ internal static string EnumValues(Type enumType) /// 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; From 7f51f41c0787e123a1280ae04696e0c09b45ee4c Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Fri, 29 Jul 2022 16:48:23 +0000 Subject: [PATCH 10/11] changes based on review --- src/System.Management.Automation/engine/Attributes.cs | 10 ++++------ .../engine/CommandCompletion/CompletionCompleters.cs | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 858471a003a..38f79e59d5d 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1313,26 +1313,24 @@ private static Type GetCommonType(Type minType, Type maxType) } /// - /// Returns a list containing only the elements that passed the attribute's validation. + /// Returns only the elements that passed the attribute's validation. /// /// The objects to validate. - internal IList GetValidatedElements(IList elementsToValidate) + internal IEnumerable GetValidatedElements(IEnumerable elementsToValidate) { - var result = new List(elementsToValidate.Count); foreach (var el in elementsToValidate) { try { ValidateElement(el); - result.Add(el); } catch (ValidationMetadataException) { // Element was not in range - drop + continue; } + yield return el; } - - return result; } } diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 6323e8fa8e7..a59b99b0662 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1770,7 +1770,7 @@ private static void ProcessParameter( { RemoveLastNullCompletionResult(result); - IList enumValues = LanguagePrimitives.EnumSingleTypeConverter.GetEnumValues(parameterType).Cast().ToList(); + IEnumerable enumValues = LanguagePrimitives.EnumSingleTypeConverter.GetEnumValues(parameterType); // Exclude values not accepted by ValidateRange-attributes foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) { From 866be5c238cce3d9a6fea1e6996248aec45e05ed Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Wed, 3 Aug 2022 13:34:17 +0000 Subject: [PATCH 11/11] cleanup after review --- src/System.Management.Automation/engine/Attributes.cs | 1 + .../engine/CommandCompletion/CompletionCompleters.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 38f79e59d5d..7d4b2b51f2a 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1329,6 +1329,7 @@ internal IEnumerable GetValidatedElements(IEnumerable elementsToValidate) // 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 a59b99b0662..5e56ae2de94 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1771,6 +1771,7 @@ private static void ProcessParameter( RemoveLastNullCompletionResult(result); IEnumerable enumValues = LanguagePrimitives.EnumSingleTypeConverter.GetEnumValues(parameterType); + // Exclude values not accepted by ValidateRange-attributes foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) {