Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add completion for values in comparisons when comparing Enums #17654

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,15 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont
return completions;
}
}
else if (lastAst.Parent is BinaryExpressionAst binaryExpression)
{
completionContext.WordToComplete = (tokenAtCursor as StringToken).Value;
result = CompletionCompleters.CompleteComparisonOperatorValues(completionContext, binaryExpression.Left);
if (result.Count > 0)
{
return result;
}
}
else if (lastAst.Parent is IndexExpressionAst indexExpressionAst)
{
// Handles quoted string inside index expression like: $PSVersionTable["<Tab>"]
Expand Down Expand Up @@ -914,8 +923,27 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont
{
result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength);
}
break;

case TokenKind.Ieq:
case TokenKind.Ceq:
case TokenKind.Ine:
case TokenKind.Cne:
case TokenKind.Ilike:
case TokenKind.Clike:
case TokenKind.Inotlike:
case TokenKind.Cnotlike:
case TokenKind.Imatch:
case TokenKind.Cmatch:
case TokenKind.Inotmatch:
case TokenKind.Cnotmatch:
if (lastAst is BinaryExpressionAst binaryExpression)
{
completionContext.WordToComplete = string.Empty;
result = CompletionCompleters.CompleteComparisonOperatorValues(completionContext, binaryExpression.Left);
}
break;

case TokenKind.LBracket:
if (lastAst.Parent is IndexExpressionAst indexExpression)
{
Expand All @@ -924,6 +952,7 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont
result = CompletionCompleters.CompleteIndexExpression(completionContext, indexExpression.Target);
}
break;

default:
break;
}
Expand Down Expand Up @@ -994,6 +1023,26 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont
return result;
}
break;

case TokenKind.Ieq:
case TokenKind.Ceq:
case TokenKind.Ine:
case TokenKind.Cne:
case TokenKind.Ilike:
case TokenKind.Clike:
case TokenKind.Inotlike:
case TokenKind.Cnotlike:
case TokenKind.Imatch:
case TokenKind.Cmatch:
case TokenKind.Inotmatch:
case TokenKind.Cnotmatch:
if (lastAst is BinaryExpressionAst binaryExpression)
{
completionContext.WordToComplete = string.Empty;
result = CompletionCompleters.CompleteComparisonOperatorValues(completionContext, binaryExpression.Left);
}
break;

case TokenKind.LBracket:
if (lastAst.Parent is IndexExpressionAst indexExpression)
{
Expand All @@ -1002,6 +1051,7 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont
result = CompletionCompleters.CompleteIndexExpression(completionContext, indexExpression.Target);
}
break;

default:
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2307,6 +2307,11 @@ private static void NativeCommandArgumentCompletion(
{
NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName]);
}
else if (parameterName.Equals("Value", StringComparison.OrdinalIgnoreCase)
&& boundArguments?["Property"] is AstPair pair && pair.Argument is StringConstantExpressionAst stringAst)
{
NativeCompletionMemberValue(context, result, commandAst, stringAst.Value);
}

break;
}
Expand Down Expand Up @@ -3984,6 +3989,38 @@ private static void NativeCompletionMemberName(CompletionContext context, List<C
result.Add(CompletionResult.Null);
}

private static void NativeCompletionMemberValue(CompletionContext context, List<CompletionResult> result, CommandAst commandAst, string propertyName)
{
string wordToComplete = context.WordToComplete.Trim('"', '\'');
IEnumerable<PSTypeName> prevTypes = GetInferenceTypes(context, commandAst);
if (prevTypes is not null)
{
foreach (var type in prevTypes)
{
if (type.Type is null)
{
continue;
}

PropertyInfo property = type.Type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (property is not null && property.PropertyType.IsEnum)
{
foreach (var value in property.PropertyType.GetEnumNames())
{
if (value.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase))
{
result.Add(new CompletionResult(value, value, CompletionResultType.ParameterValue, value));
}
}

break;
}
}
}

result.Add(CompletionResult.Null);
}

/// <summary>
/// Returns all string values bound to a parameter except the one the cursor is currently at.
/// </summary>
Expand Down Expand Up @@ -5902,6 +5939,46 @@ internal static List<CompletionResult> CompleteMember(CompletionContext context,
return results;
}

internal static List<CompletionResult> CompleteComparisonOperatorValues(CompletionContext context, ExpressionAst operatorLeftValue)
{
var result = new List<CompletionResult>();
var resolvedTypes = new List<Type>();

if (SafeExprEvaluator.TrySafeEval(operatorLeftValue, context.ExecutionContext, out object value) && value is not null)
{
resolvedTypes.Add(value.GetType());
}
else
{
var inferredTypes = AstTypeInference.InferTypeOf(operatorLeftValue, context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval);
foreach (var type in inferredTypes)
{
if (type.Type is not null)
{
resolvedTypes.Add(type.Type);
}
}
}

foreach (var type in resolvedTypes)
{
if (type.IsEnum)
{
foreach (var name in type.GetEnumNames())
{
if (name.StartsWith(context.WordToComplete, StringComparison.OrdinalIgnoreCase))
{
result.Add(new CompletionResult($"'{name}'", name, CompletionResultType.ParameterValue, name));
}
}

break;
}
}

return result;
}

/// <summary>
/// Complete members against extension methods 'Where' and 'ForEach'
/// </summary>
Expand Down
26 changes: 26 additions & 0 deletions test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,32 @@ ConstructorTestClass(int i, bool b)
$res.CompletionMatches[0].CompletionText | Should -BeExactly Cat
}

it 'Should complete "Value" parameter value in "Where-Object" for Enum property with no input' {
$res = TabExpansion2 -inputScript 'Get-Command | where-Object CommandType -eq '
$res.CompletionMatches[0].CompletionText | Should -BeExactly Alias
}

it 'Should complete "Value" parameter value in "Where-Object" for Enum property with partial input' {
$res = TabExpansion2 -inputScript 'Get-Command | where-Object CommandType -ne Ali'
$res.CompletionMatches[0].CompletionText | Should -BeExactly Alias
}

it 'Should complete the right hand side of a comparison operator when left is an Enum with no input' {
$res = TabExpansion2 -inputScript 'Get-Command | Where-Object -FilterScript {$_.CommandType -like '
$res.CompletionMatches[0].CompletionText | Should -BeExactly "'Alias'"
}

it 'Should complete the right hand side of a comparison operator when left is an Enum with partial input' {
$TempVar = Get-Command
$res = TabExpansion2 -inputScript '$tempVar[0].CommandType -notlike "Ali"'
$res.CompletionMatches[0].CompletionText | Should -BeExactly "'Alias'"
}

it 'Should complete the right hand side of a comparison operator when left is an Enum when cursor is on a newline' {
$res = TabExpansion2 -inputScript "Get-Command | Where-Object -FilterScript {`$_.CommandType -like`n"
$res.CompletionMatches[0].CompletionText | Should -BeExactly "'Alias'"
}

it 'Should complete provider dynamic parameters with quoted path' {
$Script = if ($IsWindows)
{
Expand Down