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

Support spread completions inside ternary and parenthesized expressions #14058

Merged
merged 1 commit into from
May 9, 2024
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
4 changes: 4 additions & 0 deletions src/Bicep.Core/Syntax/SyntaxBase.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics;
using Bicep.Core.Extensions;
using Bicep.Core.Parsing;
using JetBrains.Annotations;

namespace Bicep.Core.Syntax
{
[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:-)

public abstract class SyntaxBase : IPositionable
{
public abstract void Accept(ISyntaxVisitor visitor);
Expand Down Expand Up @@ -82,5 +84,7 @@ protected static void AssertSyntaxType(SyntaxBase? syntax, [InvokerParameterName
/// Returns a string that mirrors the original text of the syntax node.
/// </summary>
public override string ToString() => SyntaxStringifier.Stringify(this);

public string GetDebuggerDisplay() => ToString();
}
}
33 changes: 24 additions & 9 deletions src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1403,7 +1403,7 @@ AccessExpressionSyntax access when access.BaseExpression is ForSyntax

private DeclaredTypeAssignment? GetArrayType(ArraySyntax syntax)
{
var parent = this.binder.GetParent(syntax);
var parent = GetClosestMaybeTypedAncestor(syntax);

// we are only handling paths in the AST that are going to produce a declared type
// arrays can exist under a variable declaration, but variables don't have declared types,
Expand All @@ -1420,7 +1420,7 @@ AccessExpressionSyntax access when access.BaseExpression is ForSyntax
return GetNonNullableTypeAssignment(parameterDeclaration)?.ReplaceDeclaringSyntax(syntax);
case ParameterAssignmentSyntax:
return GetNonNullableTypeAssignment(parent)?.ReplaceDeclaringSyntax(syntax);
case SpreadExpressionSyntax when binder.GetParent(parent) is { } grandParent &&
case SpreadExpressionSyntax when GetClosestMaybeTypedAncestor(parent) is {} grandParent &&
GetDeclaredTypeAssignment(grandParent)?.Reference is ArrayType enclosingArrayType:

return TryCreateAssignment(enclosingArrayType, syntax);
Expand Down Expand Up @@ -1616,11 +1616,7 @@ AccessExpressionSyntax access when access.BaseExpression is ForSyntax

private DeclaredTypeAssignment? GetObjectType(ObjectSyntax syntax)
{
var parent = this.binder.GetParent(syntax);
if (parent is null)
{
return null;
}
var parent = GetClosestMaybeTypedAncestor(syntax);

switch (parent)
{
Expand Down Expand Up @@ -1745,9 +1741,9 @@ AccessExpressionSyntax access when access.BaseExpression is ForSyntax

return TryCreateAssignment(parameterAssignmentTypeAssignment.Reference.Type, syntax);

case SpreadExpressionSyntax when binder.GetParent(parent) is { } grandParent &&
GetDeclaredTypeAssignment(grandParent)?.Reference is ObjectType enclosingObjectType:

case SpreadExpressionSyntax when GetClosestMaybeTypedAncestor(parent) is {} grandParent &&
GetDeclaredTypeAssignment(grandParent)?.Reference is ObjectType enclosingObjectType:
var type = TypeHelper.MakeRequiredPropertiesOptional(enclosingObjectType);

return TryCreateAssignment(type, syntax);
Expand All @@ -1756,6 +1752,25 @@ AccessExpressionSyntax access when access.BaseExpression is ForSyntax
return null;
}

private SyntaxBase? GetClosestMaybeTypedAncestor(SyntaxBase syntax)
{
// to avoid infinite recursion, this method deliberately only searches UP the syntax hierarchy.
// otherwise, you can end up in an infinite loop e.g. trying to calculate the type of "foo" in "(foo).prop".
foreach (var parent in binder.EnumerateAncestorsUpwards(syntax))
{
switch (parent)
{
case ParenthesizedExpressionSyntax:
case TernaryOperationSyntax ternary when syntax == ternary.TrueExpression || syntax == ternary.FalseExpression:
continue;
default:
return parent;
}
}

return null;
}

private DeclaredTypeAssignment? GetObjectPropertyType(ObjectPropertySyntax syntax)
{
// `syntax.TryGetKeyText()` will only return a non-null value if the key is a bare identifier or a non-interpolated string
Expand Down
65 changes: 65 additions & 0 deletions src/Bicep.LangServer.IntegrationTests/CompletionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,71 @@ public async Task Spread_operator_supports_outer_object_property_completions()
");
}

[TestMethod]
public async Task Spread_object_property_completions_work_with_ternary()
{
//https://github.com/Azure/bicep/issues/14056
var fileWithCursors = """
var nsgDeploy = true

resource subnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = {
name: 'vnet/subnet2'
properties: {
addressPrefix: ''
...nsgDeploy ? {
|
}
}
}
""";

var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors);
var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text);

var completions = await file.RequestCompletion(cursor);
var updatedFile = file.ApplyCompletion(completions, "ipAllocations");
updatedFile.Should().HaveSourceText("""
var nsgDeploy = true

resource subnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = {
name: 'vnet/subnet2'
properties: {
addressPrefix: ''
...nsgDeploy ? {
ipAllocations:|
}
}
}
""");
}

[TestMethod]
public async Task Spread_array_completions_work_with_parentheses()
{
var fileWithCursors = """
param foo { foo: 'asdf' }[] = [
...[
(|)
]
]
""";

var (text, cursor) = ParserHelper.GetFileWithSingleCursor(fileWithCursors);
var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text);

var completions = await file.RequestCompletion(cursor);
var updatedFile = file.ApplyCompletion(completions, "required-properties");
updatedFile.Should().HaveSourceText("""
param foo { foo: 'asdf' }[] = [
...[
({
foo: $1
}|)
]
]
""");
}

[TestMethod]
public async Task PropertyNameCompletionsShouldNotIncludeTrailingColonIfItIsPresent()
{
Expand Down
Loading