Skip to content

Commit

Permalink
Property value flag should flow through ternary and parenthesized exp…
Browse files Browse the repository at this point in the history
…ressions (#6231)

* Property types should flow through expressions

* Update tests
  • Loading branch information
shenglol committed Mar 19, 2022
1 parent b19a749 commit cf79fed
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 17 deletions.
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

0 comments on commit cf79fed

Please sign in to comment.