Skip to content

Commit

Permalink
Addressed code review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
bhsubra committed Apr 21, 2021
1 parent 8ce87e0 commit 9b3995e
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 68 deletions.
59 changes: 59 additions & 0 deletions src/Bicep.LangServer.IntegrationTests/CompletionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,65 @@ public async Task String_segments_do_not_return_completions()
}
}

[TestMethod]
public async Task VerifyResourceBodyCompletionSnippetSourcedFromSwaggerSpecHasAllRequiredProperties()
{
string text = "resource Identifier 'Microsoft.Storage/storageAccounts@2019-06-01' =";

var syntaxTree = SyntaxTree.Create(new Uri("file:///main.bicep"), text);
using var client = await IntegrationTestHelper.StartServerWithTextAsync(text, syntaxTree.FileUri, resourceTypeProvider: TypeProvider);

var completions = await client.RequestCompletion(new CompletionParams
{
TextDocument = new TextDocumentIdentifier(syntaxTree.FileUri),
Position = TextCoordinateConverter.GetPosition(syntaxTree.LineStarts, text.Length),
});

completions.Should().NotBeEmpty();

var completion = completions.Where(x => x.Label == "{}").First();

completion.Detail.Should().Be("{}");
completion.Preselect.Should().BeTrue();
completion.TextEdit!.NewText.Should().BeEquivalentToIgnoringNewlines(@"{
kind: $1
location: $2
name: $3
sku: {
name: $4
}
$0
}
");
}

[TestMethod]
public async Task VerifyResourceBodyCompletionSnippetSourcedFromStaticTemplateFile()
{
string text = "resource Identifier 'Microsoft.Network/dnsZones@2018-05-01' =";

var syntaxTree = SyntaxTree.Create(new Uri("file:///main.bicep"), text);
using var client = await IntegrationTestHelper.StartServerWithTextAsync(text, syntaxTree.FileUri, resourceTypeProvider: TypeProvider);

var completions = await client.RequestCompletion(new CompletionParams
{
TextDocument = new TextDocumentIdentifier(syntaxTree.FileUri),
Position = TextCoordinateConverter.GetPosition(syntaxTree.LineStarts, text.Length),
});

completions.Should().NotBeEmpty();

var completion = completions.Where(x => x.Label == "{}").First();

completion.Detail.Should().Be("{}");
completion.Preselect.Should().BeTrue();
completion.TextEdit!.NewText.Should().BeEquivalentToIgnoringNewlines(@"{
name: ${1:'dnsZone'}
location: 'global'
}
");
}

[TestMethod]
public async Task Completions_are_offered_immediately_before_and_after_comments()
{
Expand Down
115 changes: 94 additions & 21 deletions src/Bicep.LangServer.UnitTests/Snippets/SnippetsProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

using System.Collections.Generic;
using System.Linq;
using Bicep.Core;
using Bicep.Core.Resources;
using Bicep.Core.TypeSystem;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Utils;
using Bicep.LanguageServer.Completions;
using Bicep.LanguageServer.Snippets;
using FluentAssertions;
Expand Down Expand Up @@ -144,14 +148,21 @@ public void CompletionPriorityOfNonResourceSnippets_ShouldBeMedium()
}

[TestMethod]
public void GetResourceBodyCompletionSnippet_WithValidTypeAndNoDependencies_ShouldReturnSnippet()
public void GetResourceBodyCompletionSnippet_WithSnippetSourcedFromStaticTemplateAndNoResourceDependencies_ShouldReturnNonEmptySnippet()
{
SnippetsProvider snippetsProvider = new SnippetsProvider();
Snippet? snippet = snippetsProvider.GetResourceBodyCompletionSnippet("'Microsoft.Network/dnsZones@2018-05-01'");
TypeSymbol typeSymbol = new ResourceType(
ResourceTypeReference.Parse("Microsoft.Network/dnsZones@2018-05-01"),
ResourceScope.ResourceGroup,
TestTypeHelper.CreateObjectType(
"Microsoft.Network/dnsZones@2018-05-01",
("name", LanguageConstants.String)));

Snippet? snippet = snippetsProvider.GetResourceBodyCompletionSnippet(typeSymbol);

Assert.IsNotNull(snippet);
Assert.AreEqual("res-dns-zone", snippet.Prefix);
Assert.AreEqual("DNS Zone", snippet.Detail);
Assert.AreEqual("{}", snippet.Prefix);
Assert.AreEqual("{}", snippet.Detail);
Assert.AreEqual(CompletionPriority.Medium, snippet.CompletionPriority);
snippet.Text.Should().BeEquivalentToIgnoringNewlines(@"{
name: ${1:'dnsZone'}
Expand All @@ -160,28 +171,22 @@ public void GetResourceBodyCompletionSnippet_WithValidTypeAndNoDependencies_Shou
");
}

[DataTestMethod]
[DataRow("'invalid_type'")]
[DataRow(null)]
[DataRow(" ")]
[DataRow("")]
public void GetResourceBodyCompletionSnippet_WithInvalidType_ShouldReturnNull(string type)
{
SnippetsProvider snippetsProvider = new SnippetsProvider();
Snippet? snippet = snippetsProvider.GetResourceBodyCompletionSnippet(type);

Assert.IsNull(snippet);
}

[TestMethod]
public void GetResourceBodyCompletionSnippet_WithValidTypeAndDependencies_ShouldReturnSnippet()
public void GetResourceBodyCompletionSnippet_WithSnippetSourcedFromStaticTemplateAndResourceDependencies_ShouldReturnNonEmptySnippet()
{
SnippetsProvider snippetsProvider = new SnippetsProvider();
Snippet? snippet = snippetsProvider.GetResourceBodyCompletionSnippet("'Microsoft.Automation/automationAccounts/modules@2015-10-31'");
TypeSymbol typeSymbol = new ResourceType(
ResourceTypeReference.Parse("Microsoft.Automation/automationAccounts/modules@2015-10-31"),
ResourceScope.ResourceGroup,
TestTypeHelper.CreateObjectType(
"Microsoft.Automation/automationAccounts/modules@2015-10-31",
("name", LanguageConstants.String)));

Snippet snippet = snippetsProvider.GetResourceBodyCompletionSnippet(typeSymbol);

Assert.IsNotNull(snippet);
Assert.AreEqual("res-automation-module", snippet.Prefix);
Assert.AreEqual("Automation Module", snippet.Detail);
Assert.AreEqual("{}", snippet.Prefix);
Assert.AreEqual("{}", snippet.Detail);
Assert.AreEqual(CompletionPriority.Medium, snippet.CompletionPriority);
snippet.Text.Should().BeEquivalentToIgnoringNewlines(@"{
name: '${automationAccount.name}/${2:automationVariable}'
Expand All @@ -196,5 +201,73 @@ public void GetResourceBodyCompletionSnippet_WithValidTypeAndDependencies_Should
}
");
}

[TestMethod]
public void GetResourceBodyCompletionSnippet_WithPropertiesFromSwaggerSpec_ShouldReturnSnippetWithRequiredProperties()
{
SnippetsProvider snippetsProvider = new SnippetsProvider();
TypeSymbol typeSymbol = new ResourceType(
ResourceTypeReference.Parse("microsoft.aadiam/azureADMetrics@2020-07-01-preview"),
ResourceScope.ResourceGroup,
CreateObjectType("microsoft.aadiam/azureADMetrics@2020-07-01-preview",
("name", LanguageConstants.String, TypePropertyFlags.Required),
("location", LanguageConstants.String, TypePropertyFlags.Required),
("kind", LanguageConstants.String, TypePropertyFlags.Required),
("id", LanguageConstants.String, TypePropertyFlags.ReadOnly),
("hostPoolType", LanguageConstants.String, TypePropertyFlags.Required),
("sku", CreateObjectType("applicationGroup",
("name", LanguageConstants.String, TypePropertyFlags.Required),
("friendlyName", LanguageConstants.String, TypePropertyFlags.None),
("properties", CreateObjectType("properties",
("loadBalancerType", LanguageConstants.String, TypePropertyFlags.Required),
("preferredAppGroupType", LanguageConstants.String, TypePropertyFlags.WriteOnly)),
TypePropertyFlags.Required)),
TypePropertyFlags.Required)));

Snippet snippet = snippetsProvider.GetResourceBodyCompletionSnippet(typeSymbol);

Assert.IsNotNull(snippet);
Assert.AreEqual("{}", snippet.Prefix);
Assert.AreEqual("{}", snippet.Detail);
Assert.AreEqual(CompletionPriority.Medium, snippet.CompletionPriority);
snippet.Text.Should().BeEquivalentToIgnoringNewlines(@"{
hostPoolType: $1
kind: $2
location: $3
name: $4
sku: {
name: $5
properties: {
loadBalancerType: $6
}
}
$0
}");
}

[TestMethod]
public void GetResourceBodyCompletionSnippet_WithNoRequiredProperties_ShouldReturnEmptySnippet()
{
SnippetsProvider snippetsProvider = new SnippetsProvider();
TypeSymbol typeSymbol = new ResourceType(
ResourceTypeReference.Parse("microsoft.aadiam/azureADMetrics@2020-07-01-preview"),
ResourceScope.ResourceGroup,
CreateObjectType("microsoft.aadiam/azureADMetrics@2020-07-01-preview"));

Snippet snippet = snippetsProvider.GetResourceBodyCompletionSnippet(typeSymbol);

Assert.IsNotNull(snippet);
Assert.AreEqual("{}", snippet.Prefix);
Assert.AreEqual("{}", snippet.Detail);
Assert.AreEqual(CompletionPriority.Medium, snippet.CompletionPriority);
snippet.Text.Should().BeEquivalentToIgnoringNewlines("{\n\t$0\n}");
}

private static ObjectType CreateObjectType(string name, params (string name, ITypeReference type, TypePropertyFlags typePropertyFlags)[] properties)
=> new(
name,
TypeSymbolValidationFlags.Default,
properties.Select(val => new TypeProperty(val.name, val.type, val.typePropertyFlags)),
null);
}
}
33 changes: 14 additions & 19 deletions src/Bicep.LangServer/Completions/BicepCompletionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Bicep.Core.Semantics;
using Bicep.Core.Syntax;
using Bicep.Core.TypeSystem;
using Bicep.Core.TypeSystem.Az;
using Bicep.LanguageServer.Extensions;
using Bicep.LanguageServer.Snippets;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
Expand Down Expand Up @@ -59,7 +60,7 @@ public IEnumerable<CompletionItem> GetFilteredCompletions(Compilation compilatio
.Concat(GetResourceTypeFollowerCompletions(context))
.Concat(GetModulePathCompletions(model, context))
.Concat(GetModuleBodyCompletions(context))
.Concat(GetResourceBodyCompletions(context))
.Concat(GetResourceBodyCompletions(model, context))
.Concat(GetParameterDefaultValueCompletions(model, context))
.Concat(GetVariableValueCompletions(context))
.Concat(GetOutputValueCompletions(model, context))
Expand Down Expand Up @@ -348,11 +349,11 @@ private IEnumerable<CompletionItem> GetOutputValueCompletions(SemanticModel mode
return GetValueCompletionsForType(declaredType, context.ReplacementRange, model, context, loopsAllowed: true);
}

private IEnumerable<CompletionItem> GetResourceBodyCompletions(BicepCompletionContext context)
private IEnumerable<CompletionItem> GetResourceBodyCompletions(SemanticModel model, BicepCompletionContext context)
{
if (context.Kind.HasFlag(BicepCompletionContextKind.ResourceBody))
{
yield return CreateResourceBodyCompletion(context);
yield return CreateResourceBodyCompletion(model, context);

yield return CreateResourceOrModuleConditionCompletion(context.ReplacementRange);

Expand All @@ -364,25 +365,19 @@ private IEnumerable<CompletionItem> GetResourceBodyCompletions(BicepCompletionCo
}
}

private CompletionItem CreateResourceBodyCompletion(BicepCompletionContext context)
private CompletionItem CreateResourceBodyCompletion(SemanticModel model, BicepCompletionContext context)
{
StringSyntax? stringSyntax = (context.EnclosingDeclaration as ResourceDeclarationSyntax)?.TypeString;

if (stringSyntax is not null)
if (context.EnclosingDeclaration is ResourceDeclarationSyntax resourceDeclarationSyntax)
{
string type = stringSyntax.StringTokens[0].Text;

Snippet? snippet = SnippetsProvider.GetResourceBodyCompletionSnippet(type);
TypeSymbol typeSymbol = resourceDeclarationSyntax.GetDeclaredType(model.Binder, AzResourceTypeProvider.CreateWithAzTypes());

if (snippet is not null)
{
return CreateContextualSnippetCompletion(snippet.Prefix,
snippet.Detail,
snippet.Text,
context.ReplacementRange,
snippet.CompletionPriority,
preselect: true);
}
Snippet snippet = SnippetsProvider.GetResourceBodyCompletionSnippet(typeSymbol);
return CreateContextualSnippetCompletion(snippet.Prefix,
snippet.Detail,
snippet.Text,
context.ReplacementRange,
snippet.CompletionPriority,
preselect: true);
}

return CreateObjectBodyCompletion(context.ReplacementRange);
Expand Down
3 changes: 2 additions & 1 deletion src/Bicep.LangServer/Snippets/ISnippetsProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Bicep.Core.TypeSystem;
using System.Collections.Generic;

namespace Bicep.LanguageServer.Snippets
Expand All @@ -9,6 +10,6 @@ public interface ISnippetsProvider
{
IEnumerable<Snippet> GetTopLevelNamedDeclarationSnippets();

Snippet? GetResourceBodyCompletionSnippet(string type);
Snippet GetResourceBodyCompletionSnippet(TypeSymbol typeSymbol);
}
}
Loading

0 comments on commit 9b3995e

Please sign in to comment.