From 9105321172decbdce2d3f25dd2d24429bbeb25eb Mon Sep 17 00:00:00 2001 From: dkattan <1424395+dkattan@users.noreply.github.com> Date: Mon, 30 May 2022 11:06:01 -0500 Subject: [PATCH 1/6] Implemented ScriptBlock support for ValidateSet --- .../engine/Attributes.cs | 37 +++++++++++++++++-- .../engine/parser/Compiler.cs | 4 ++ .../TabCompletion/TabCompletion.Tests.ps1 | 9 +++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 9965824a927..e2a5963376c 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1595,7 +1595,7 @@ public abstract class CachedValidValuesGeneratorBase : IValidateSetValuesGenerat // Cached valid values. private string[] _validValues; private readonly int _validValuesCacheExpiration; - + /// /// Initializes a new instance of the class. /// @@ -1655,7 +1655,7 @@ public sealed class ValidateSetAttribute : ValidateEnumeratedArgumentsAttribute // of 'validValuesGenerator'. private readonly string[] _validValues; private readonly IValidateSetValuesGenerator validValuesGenerator = null; - + private readonly ScriptBlock validValuesScript = null; // The valid values generator cache works across 'ValidateSetAttribute' instances. private static readonly ConcurrentDictionary s_ValidValuesGeneratorCache = new ConcurrentDictionary(); @@ -1686,11 +1686,32 @@ public IList ValidValues { get { - if (validValuesGenerator == null) + if (validValuesGenerator == null && validValuesScript == null) { return _validValues; } + if (validValuesScript is not null) + { + var validValuesFromScript = new List(); + var customResults = validValuesScript.Invoke(); + if (customResults == null || customResults.Count == 0) + { + validValuesFromScript = null; + } + foreach (var customResult in customResults) + { + var resultAsString = customResult.BaseObject as string; + if (resultAsString != null) + { + validValuesFromScript.Add(resultAsString); + continue; + } + var resultToString = customResult.ToString(); + validValuesFromScript.Add(resultToString); + } + return validValuesFromScript; + } var validValuesLocal = validValuesGenerator.GetValidValues(); if (validValuesLocal == null) @@ -1790,6 +1811,16 @@ public ValidateSetAttribute(Type valuesGeneratorType) validValuesGenerator = s_ValidValuesGeneratorCache.GetOrAdd( valuesGeneratorType, static (key) => (IValidateSetValuesGenerator)Activator.CreateInstance(key)); } + + /// + /// Initializes a new instance of the class. + /// + /// ScriptBlock that generates list of valid values + /// For null arguments. + public ValidateSetAttribute(ScriptBlock scriptBlock) + { + validValuesScript = scriptBlock; + } } /// diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 68bb523427b..cf768b9c68b 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -1645,6 +1645,10 @@ private static Attribute NewValidateSetAttribute(AttributeAst ast) typeof(System.Management.Automation.IValidateSetValuesGenerator).FullName); } } + else if (ast.PositionalArguments.Count == 1 && ast.PositionalArguments[0] is ScriptBlockExpressionAst scriptBlockAst) + { + result = new ValidateSetAttribute(scriptBlockAst.ScriptBlock.GetScriptBlock()); + } else { // 'ValidateSet("value1","value2", IgnoreCase=$false)' is supported in scripts. diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index f984e3b72c0..4521f458ef0 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1096,6 +1096,15 @@ switch ($x) $res.CompletionMatches[1].CompletionText | Should -BeExactly 'dog' } + It "Tab completion for validateSet attribute with ScriptBlock" { + function foo { param([ValidateSet({'cat','dog'})]$p) } + $inputStr = "foo " + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'cat' + $res.CompletionMatches[1].CompletionText | Should -BeExactly 'dog' + } + It "Tab completion for validateSet attribute takes precedence over enums" { function foo { param([ValidateSet('DarkBlue','DarkCyan')][ConsoleColor]$p) } $inputStr = "foo " From f861848e21010de8eb68b8a277e479d8616f8837 Mon Sep 17 00:00:00 2001 From: Darren Kattan <1424395+dkattan@users.noreply.github.com> Date: Tue, 31 May 2022 08:28:06 -0500 Subject: [PATCH 2/6] Update src/System.Management.Automation/engine/Attributes.cs Co-authored-by: Staffan Gustafsson --- src/System.Management.Automation/engine/Attributes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index e2a5963376c..70baadf98a9 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1701,7 +1701,7 @@ public IList ValidValues foreach (var customResult in customResults) { - var resultAsString = customResult.BaseObject as string; + var resultAsString = LanguagePrimitives.ConvertTo(customResult); if (resultAsString != null) { validValuesFromScript.Add(resultAsString); From 40a1e9365cc2a8372de8f2f5b084d735d53305e5 Mon Sep 17 00:00:00 2001 From: Darren Kattan <1424395+dkattan@users.noreply.github.com> Date: Tue, 31 May 2022 08:28:21 -0500 Subject: [PATCH 3/6] Update src/System.Management.Automation/engine/Attributes.cs Co-authored-by: Ilya --- src/System.Management.Automation/engine/Attributes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 70baadf98a9..058f85699fc 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1690,6 +1690,7 @@ public IList ValidValues { return _validValues; } + if (validValuesScript is not null) { var validValuesFromScript = new List(); From 12420e2818b96ff80bc8f4a1ee39d89b9d35403d Mon Sep 17 00:00:00 2001 From: Darren Kattan <1424395+dkattan@users.noreply.github.com> Date: Tue, 31 May 2022 08:28:33 -0500 Subject: [PATCH 4/6] Update src/System.Management.Automation/engine/Attributes.cs Co-authored-by: Ilya --- src/System.Management.Automation/engine/Attributes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 058f85699fc..9f9b0e3da20 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1655,7 +1655,7 @@ public sealed class ValidateSetAttribute : ValidateEnumeratedArgumentsAttribute // of 'validValuesGenerator'. private readonly string[] _validValues; private readonly IValidateSetValuesGenerator validValuesGenerator = null; - private readonly ScriptBlock validValuesScript = null; + private readonly ScriptBlock validValuesScript; // The valid values generator cache works across 'ValidateSetAttribute' instances. private static readonly ConcurrentDictionary s_ValidValuesGeneratorCache = new ConcurrentDictionary(); From 8f236ec49e2294afc25b7bdc8d906f2e94175b17 Mon Sep 17 00:00:00 2001 From: Darren Kattan <1424395+dkattan@users.noreply.github.com> Date: Tue, 31 May 2022 08:29:05 -0500 Subject: [PATCH 5/6] Update src/System.Management.Automation/engine/Attributes.cs Co-authored-by: Ilya --- src/System.Management.Automation/engine/Attributes.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 9f9b0e3da20..fb9d231dce4 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1708,11 +1708,14 @@ public IList ValidValues validValuesFromScript.Add(resultAsString); continue; } + var resultToString = customResult.ToString(); validValuesFromScript.Add(resultToString); } + return validValuesFromScript; } + var validValuesLocal = validValuesGenerator.GetValidValues(); if (validValuesLocal == null) From e903d4191c507dcd1cfc9866695962a6d620f40e Mon Sep 17 00:00:00 2001 From: Ilya Date: Tue, 31 May 2022 22:09:42 +0500 Subject: [PATCH 6/6] Update src/System.Management.Automation/engine/Attributes.cs --- src/System.Management.Automation/engine/Attributes.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index fb9d231dce4..4a0cd1e983d 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1595,7 +1595,6 @@ public abstract class CachedValidValuesGeneratorBase : IValidateSetValuesGenerat // Cached valid values. private string[] _validValues; private readonly int _validValuesCacheExpiration; - /// /// Initializes a new instance of the class. ///