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

Property value flag should flow through ternary and parenthesized expressions #6231

Merged
merged 2 commits into from
Mar 19, 2022
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 @@ -4,7 +4,6 @@
using System.Linq;
using Azure.Deployments.Core.Extensions;
using Bicep.Core.Analyzers.Linter.Rules;
using Bicep.Core.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;

// TODO: Test with different configs
Expand Down
1 change: 0 additions & 1 deletion src/Bicep.Decompiler/TemplateConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
Expand Down
105 changes: 90 additions & 15 deletions src/Bicep.LangServer.IntegrationTests/CompletionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1375,10 +1375,8 @@ public async Task List_functions_accepting_inputs_permit_object_key_completions(
");
}

[TestMethod]
public async Task List_functions_accepting_inputs_permit_object_value_completions()
{
var fileWithCursors = @"
[DataTestMethod]
[DataRow(@"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}
Expand All @@ -1387,8 +1385,94 @@ public async Task List_functions_accepting_inputs_permit_object_value_completion
withInputInputVal: 'hello'
optionalLiteralVal: |
})
";
", @"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}

var outTest = abc.listWithInput('2020-01-01', {
withInputInputVal: 'hello'
optionalLiteralVal: 'either'|
})
")]
[DataRow(@"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}

var outTest = abc.listWithInput('2020-01-01', {
withInputInputVal: 'hello'
optionalLiteralVal: (|)
})
", @"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}

var outTest = abc.listWithInput('2020-01-01', {
withInputInputVal: 'hello'
optionalLiteralVal: ('either'|)
})
")]
[DataRow(@"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}

var outTest = abc.listWithInput('2020-01-01', {
withInputInputVal: 'hello'
optionalLiteralVal: true ? | : 'or'
})
", @"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}

var outTest = abc.listWithInput('2020-01-01', {
withInputInputVal: 'hello'
optionalLiteralVal: true ? 'either'| : 'or'
})
")]
[DataRow(@"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}

var outTest = abc.listWithInput('2020-01-01', {
withInputInputVal: 'hello'
optionalLiteralVal: true ? 'or' : |
})
", @"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}

var outTest = abc.listWithInput('2020-01-01', {
withInputInputVal: 'hello'
optionalLiteralVal: true ? 'or' : 'either'|
})
")]
[DataRow(@"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}

var outTest = abc.listWithInput('2020-01-01', {
withInputInputVal: 'hello'
optionalLiteralVal: true ? 'or' : (true ? |)
})
", @"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}

var outTest = abc.listWithInput('2020-01-01', {
withInputInputVal: 'hello'
optionalLiteralVal: true ? 'or' : (true ? 'either'|)
})
")]
public async Task List_functions_accepting_inputs_permit_object_value_completions(string fileWithCursors, string updatedFileWithCursors)
{
var (file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors);
var bicepFile = SourceFileFactory.CreateBicepFile(new Uri("file:///main.bicep"), file);
using var helper = await LanguageServerHelper.StartServerWithTextAsync(TestContext, file, bicepFile.FileUri, creationOptions: new LanguageServer.Server.CreationOptions(NamespaceProvider: BuiltInTestTypes.Create()));
Expand All @@ -1398,16 +1482,7 @@ public async Task List_functions_accepting_inputs_permit_object_value_completion
completions.Should().Contain(x => x.Label == "'or'");

var updatedFile = ApplyCompletion(bicepFile, completions.Single(x => x.Label == "'either'"));
updatedFile.Should().HaveSourceText(@"
resource abc 'Test.Rp/listFuncTests@2020-01-01' existing = {
name: 'abc'
}

var outTest = abc.listWithInput('2020-01-01', {
withInputInputVal: 'hello'
optionalLiteralVal: 'either'|
})
");
updatedFile.Should().HaveSourceText(updatedFileWithCursors);
}

[TestMethod]
Expand Down
66 changes: 66 additions & 0 deletions src/Bicep.LangServer/Completions/BicepCompletionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ public static BicepCompletionContext Create(IFeatureProvider featureProvider, Co
// previous processing hasn't identified a completion context kind
// check if we're inside an expression
kind |= ConvertFlag(IsInnerExpressionContext(matchingNodes, offset), BicepCompletionContextKind.Expression);

if (kind.HasFlag(BicepCompletionContextKind.Expression) &&
PropertyTypeShouldFlowThrough(matchingNodes, propertyInfo, offset))
{
kind |= BicepCompletionContextKind.PropertyValue;
}
}

return new BicepCompletionContext(
Expand Down Expand Up @@ -546,6 +552,37 @@ private static BicepCompletionContextKind GetPropertyValueFlags(List<SyntaxBase>
return BicepCompletionContextKind.None;
}

private static bool PropertyTypeShouldFlowThrough(List<SyntaxBase> matchingNodes, (ObjectPropertySyntax? node, int index) propertyInfo, int offset)
{
if (propertyInfo.node is null)
{
return false;
}

// Property types should flow through parenthesized and ternary expressions. For examples:
// {
// key: (( | ))
// }
// {
// key: conditionA ? | : false
// }
// {
// key: conditionA ? (conditionB ? true : |) : false
// }
if ((SyntaxMatcher.IsTailMatch<TernaryOperationSyntax>(matchingNodes) ||
SyntaxMatcher.IsTailMatch<TernaryOperationSyntax, Token>(matchingNodes) ||
SyntaxMatcher.IsTailMatch<ParenthesizedExpressionSyntax>(matchingNodes) ||
SyntaxMatcher.IsTailMatch<ParenthesizedExpressionSyntax, Token>(matchingNodes) ||
SyntaxMatcher.IsTailMatch<ParenthesizedExpressionSyntax, SkippedTriviaSyntax>(matchingNodes, (parenthesizedExpression, _) =>
parenthesizedExpression.Expression is SkippedTriviaSyntax)) &&
matchingNodes.Skip(propertyInfo.index + 1).SkipLast(1).All(node => node is TernaryOperationSyntax or ParenthesizedExpressionSyntax))
{
return true;
}

return false;
}

private static bool IsArrayItemContext(List<SyntaxBase> matchingNodes, (ArraySyntax? node, int index) arrayInfo, int offset)
{
if (arrayInfo.node == null)
Expand Down Expand Up @@ -762,6 +799,35 @@ TokenType.StringMiddlePiece when IsOffsetImmediatlyAfterNode(offset, token) => f
return false;
}

// var foo = true ?|:
// var foo = true ?| :
// var foo = true ? |:
// var foo = true ? | :
var isInEmptyTrueExpression = SyntaxMatcher.IsTailMatch<TernaryOperationSyntax>(
matchingNodes,
ternaryOperation =>
ternaryOperation.Question.GetPosition() <= offset &&
ternaryOperation.Colon.GetPosition() >= offset &&
ternaryOperation.TrueExpression is SkippedTriviaSyntax);

if (isInEmptyTrueExpression)
{
return true;
}

// var foo = true ? : | <white spaces>
var isInEmptyFalseExpression = SyntaxMatcher.IsTailMatch<TernaryOperationSyntax>(
matchingNodes,
ternaryOperation =>
ternaryOperation.Colon.GetPosition() <= offset &&
ternaryOperation.FalseExpression.GetPosition() >= offset &&
ternaryOperation.FalseExpression is SkippedTriviaSyntax);

if (isInEmptyFalseExpression)
{
return true;
}

// It does not make sense to insert expressions at the cursor positions shown in the comments below.
return !(
//║{ ║{ ║|{| ║{ ║{
Expand Down