Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions src/System.Management.Automation/engine/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1595,7 +1595,6 @@ public abstract class CachedValidValuesGeneratorBase : IValidateSetValuesGenerat
// Cached valid values.
private string[] _validValues;
private readonly int _validValuesCacheExpiration;

/// <summary>
/// Initializes a new instance of the <see cref="CachedValidValuesGeneratorBase"/> class.
/// </summary>
Expand Down Expand Up @@ -1655,7 +1654,7 @@ public sealed class ValidateSetAttribute : ValidateEnumeratedArgumentsAttribute
// of 'validValuesGenerator'.
private readonly string[] _validValues;
private readonly IValidateSetValuesGenerator validValuesGenerator = null;

private readonly ScriptBlock validValuesScript;
// The valid values generator cache works across 'ValidateSetAttribute' instances.
private static readonly ConcurrentDictionary<Type, IValidateSetValuesGenerator> s_ValidValuesGeneratorCache =
new ConcurrentDictionary<Type, IValidateSetValuesGenerator>();
Expand Down Expand Up @@ -1686,11 +1685,36 @@ public IList<string> ValidValues
{
get
{
if (validValuesGenerator == null)
if (validValuesGenerator == null && validValuesScript == null)
{
return _validValues;
}

if (validValuesScript is not null)
{
var validValuesFromScript = new List<string>();
var customResults = validValuesScript.Invoke();
if (customResults == null || customResults.Count == 0)
{
validValuesFromScript = null;
}

foreach (var customResult in customResults)
{
var resultAsString = LanguagePrimitives.ConvertTo<string>(customResult);
if (resultAsString != null)
{
validValuesFromScript.Add(resultAsString);
continue;
}

var resultToString = customResult.ToString();
validValuesFromScript.Add(resultToString);
}

return validValuesFromScript;
}

var validValuesLocal = validValuesGenerator.GetValidValues();

if (validValuesLocal == null)
Expand Down Expand Up @@ -1790,6 +1814,16 @@ public ValidateSetAttribute(Type valuesGeneratorType)
validValuesGenerator = s_ValidValuesGeneratorCache.GetOrAdd(
valuesGeneratorType, static (key) => (IValidateSetValuesGenerator)Activator.CreateInstance(key));
}

/// <summary>
/// Initializes a new instance of the <see cref="ValidateSetAttribute"/> class.
/// </summary>
/// <param name="scriptBlock">ScriptBlock that generates list of valid values</param>
/// <exception cref="ArgumentNullException">For null arguments.</exception>
public ValidateSetAttribute(ScriptBlock scriptBlock)
{
validValuesScript = scriptBlock;
}
}

/// <summary>
Expand Down
4 changes: 4 additions & 0 deletions src/System.Management.Automation/engine/parser/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down