Skip to content

Commit

Permalink
Lexically scoped child resources
Browse files Browse the repository at this point in the history
In progress for #1363

This is an early preview of the work for allowing lexical scoping
(nesting) of child resources. This changeset doesn't totally match the
current proposal. It will need to be updated based on decisions tracker
there and the loops/scoping work that is happening right now.
  • Loading branch information
rynowak committed Feb 10, 2021
1 parent 14615ad commit 8362edc
Show file tree
Hide file tree
Showing 21 changed files with 21,619 additions and 9,432 deletions.
109 changes: 109 additions & 0 deletions src/Bicep.Core.IntegrationTests/ResourceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Linq;
using Bicep.Core.Diagnostics;
using Bicep.Core.Semantics;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Utils;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Bicep.Core.IntegrationTests
{
[TestClass]
public class ResourceTests
{
[TestMethod]
public void NestedResources_child_cycle_is_detected_correctly()
{
var program = @"
resource parent 'My.RP/parentType@2020-01-01' = {
name: 'parent'
properties: {
style: child.properties.style
}
resource child 'My.RP/parentType/childType@2020-01-01' = {
name: 'child'
properties: {
style: 'very cool'
}
}
}
";

var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program));
compilation.GetEntrypointSemanticModel().GetAllDiagnostics().Should().HaveDiagnostics(new[] {
("BCP080", DiagnosticLevel.Error, "The expression is involved in a cycle (\"child\" -> \"parent\")."),
});
}

[TestMethod]
public void NestedResources_grandchild_cycle_is_detected_correctly()
{
var program = @"
resource parent 'My.RP/parentType@2020-01-01' = {
name: 'parent'
properties: {
style: grandchild.properties.style
}
resource child 'My.RP/parentType/childType@2020-01-01' = {
name: 'child'
properties: {
}
resource grandchild 'My.RP/parentType/childType/grandchildType@2020-01-01' = {
name: 'grandchild'
properties: {
style: 'very cool'
}
}
}
}
";

var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program));
compilation.GetEntrypointSemanticModel().GetAllDiagnostics().Should().HaveDiagnostics(new[] {
("BCP080", DiagnosticLevel.Error, "The expression is involved in a cycle (\"grandchild\" -> \"parent\")."),
});
}

[TestMethod]
public void NestedResources_ancestors_are_detected()
{
var program = @"
resource parent 'My.RP/parentType@2020-01-01' = {
name: 'parent'
properties: {
}
resource child 'My.RP/parentType/childType@2020-01-01' = {
name: 'child'
properties: {
}
resource grandchild 'My.RP/parentType/childType/grandchildType@2020-01-01' = {
name: 'grandchild'
properties: {
}
}
}
}
";

var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program));
var model = compilation.GetEntrypointSemanticModel();
model.GetAllDiagnostics().Should().BeEmpty();

var parent = model.Root.ResourceDeclarations.Single(r => r.Name == "parent");
model.ResourceAncestors.GetAncestors(parent).Should().BeEmpty();

var child = model.Root.ResourceDeclarations.Single(r => r.Name == "child");
model.ResourceAncestors.GetAncestors(child).Should().Equal(new []{ parent, });

var grandchild = model.Root.ResourceDeclarations.Single(r => r.Name == "grandchild");
model.ResourceAncestors.GetAncestors(grandchild).Should().Equal(new []{ parent, child, }); // order matters
}
}
}
48 changes: 48 additions & 0 deletions src/Bicep.Core.IntegrationTests/TypeSystem/TypeValidationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -347,5 +347,53 @@ public void Type_validation_narrowing_on_discriminated_object_types(TypeSymbolVa
);
}
}

[TestMethod]
public void Type_validation_prevents_invalid_nesting()
{
var customTypes = new []
{
ResourceTypeProviderHelper.CreateCustomResourceType("My.Rp/nonNestedType", "2020-01-01", TypeSymbolValidationFlags.Default),
ResourceTypeProviderHelper.CreateCustomResourceType("My.Rp/nestedType", "2020-01-01", TypeSymbolValidationFlags.Default),
ResourceTypeProviderHelper.CreateCustomResourceType("My.Rp/nestedType/childType", "2020-01-01", TypeSymbolValidationFlags.Default),
ResourceTypeProviderHelper.CreateCustomResourceType("My.Rp/nestedType/childType/grandchildType", "2020-01-01", TypeSymbolValidationFlags.Default),
};

// Nesting inside the wrong type
var program = @"
resource parent 'My.Rp/nonNestedType@2020-01-01' = {
name: 'top'
properties: { }
resource child 'My.Rp/nestedType/childType@2020-01-01' = {
name: 'child'
properties: { }
}
}
";

var model = GetSemanticModelForTest(program, customTypes);
model.GetAllDiagnostics().Should().SatisfyRespectively(
x => x.Should().HaveCodeAndSeverity("BCP136", DiagnosticLevel.Error).And.HaveMessage("The resource type \"My.Rp/nestedType/childType\" cannot be nested inside a resource of type \"My.Rp/nonNestedType\".")
);

// Nesting more than one level deep
program = @"
resource parent 'My.Rp/nestedType@2020-01-01' = {
name: 'top'
properties: { }
resource child 'My.Rp/nestedType/childType/grandchildType@2020-01-01' = {
name: 'child'
properties: { }
}
}
";

model = GetSemanticModelForTest(program, customTypes);
model.GetAllDiagnostics().Should().SatisfyRespectively(
x => x.Should().HaveCodeAndSeverity("BCP136", DiagnosticLevel.Error).And.HaveMessage("The resource type \"My.Rp/nestedType/childType/grandchildType\" cannot be nested inside a resource of type \"My.Rp/nestedType\".")
);
}
}
}
19 changes: 19 additions & 0 deletions src/Bicep.Core.Samples/Files/Resources_CRLF/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -265,4 +265,23 @@ resource existing2 'Mock.Rp/existingExtensionResource@2020-01-01' existing = {
resource extension3 'My.Rp/extensionResource@2020-12-01' = {
name: 'extension3'
scope: existing1
}

param shouldDeployChildAndGrandChild bool = true
resource nestedA 'My.Rp/nestedResource@2020-01-01' existing = {
name: 'nestedA'

resource nestedB 'My.Rp/nestedResource/childType@2020-01-01' = if (shouldDeployChildAndGrandChild) {
name: 'nestedB'

resource nestedC 'My.Rp/nestedResource/childType/grandchildType@2020-01-01' = {
name: 'nestedC'
properties: {
style: nestedA.properties.style
}
}

properties: {
}
}
}
19 changes: 19 additions & 0 deletions src/Bicep.Core.Samples/Files/Resources_CRLF/main.diagnostics.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,22 @@ resource extension3 'My.Rp/extensionResource@2020-12-01' = {
name: 'extension3'
scope: existing1
}

param shouldDeployChildAndGrandChild bool = true
resource nestedA 'My.Rp/nestedResource@2020-01-01' existing = {
name: 'nestedA'

resource nestedB 'My.Rp/nestedResource/childType@2020-01-01' = if (shouldDeployChildAndGrandChild) {
name: 'nestedB'

resource nestedC 'My.Rp/nestedResource/childType/grandchildType@2020-01-01' = {
name: 'nestedC'
properties: {
style: nestedA.properties.style
}
}

properties: {
}
}
}
18 changes: 18 additions & 0 deletions src/Bicep.Core.Samples/Files/Resources_CRLF/main.formatted.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,21 @@ resource extension3 'My.Rp/extensionResource@2020-12-01' = {
name: 'extension3'
scope: existing1
}

param shouldDeployChildAndGrandChild bool = true
resource nestedA 'My.Rp/nestedResource@2020-01-01' existing = {
name: 'nestedA'

resource nestedB 'My.Rp/nestedResource/childType@2020-01-01' = if (shouldDeployChildAndGrandChild) {
name: 'nestedB'

resource nestedC 'My.Rp/nestedResource/childType/grandchildType@2020-01-01' = {
name: 'nestedC'
properties: {
style: nestedA.properties.style
}
}

properties: {}
}
}
24 changes: 24 additions & 0 deletions src/Bicep.Core.Samples/Files/Resources_CRLF/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
"shouldDeployVm": {
"type": "bool",
"defaultValue": true
},
"shouldDeployChildAndGrandChild": {
"type": "bool",
"defaultValue": true
}
},
"functions": [],
Expand Down Expand Up @@ -300,6 +304,26 @@
"scope": "[extensionResourceId(extensionResourceId(resourceId('Microsoft.Compute/virtualMachines', 'vmName'), 'My.Rp/extensionResource', 'extension'), 'Mock.Rp/existingExtensionResource', 'existing1')]",
"name": "extension3",
"dependsOn": []
},
{
"condition": "[parameters('shouldDeployChildAndGrandChild')]",
"type": "My.Rp/nestedResource/childType/grandchildType",
"apiVersion": "2020-01-01",
"name": "[format('{0}/{1}', 'nestedA', 'nestedB')]",
"properties": {
"style": "[reference(resourceId('My.Rp/nestedResource', 'nestedA'), '2020-01-01').style]"
},
"dependsOn": [
"[resourceId('My.Rp/nestedResource/childType', split(format('{0}', 'nestedA'), '/')[0], split(format('{0}', 'nestedA'), '/')[1])]"
]
},
{
"condition": "[parameters('shouldDeployChildAndGrandChild')]",
"type": "My.Rp/nestedResource/childType",
"apiVersion": "2020-01-01",
"name": "[format('{0}', 'nestedA')]",
"properties": {},
"dependsOn": []
}
],
"outputs": {
Expand Down
23 changes: 23 additions & 0 deletions src/Bicep.Core.Samples/Files/Resources_CRLF/main.symbols.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,26 @@ resource extension3 'My.Rp/extensionResource@2020-12-01' = {
name: 'extension3'
scope: existing1
}

param shouldDeployChildAndGrandChild bool = true
//@[6:36) Parameter shouldDeployChildAndGrandChild. Type: bool. Declaration start char: 0, length: 48
resource nestedA 'My.Rp/nestedResource@2020-01-01' existing = {
//@[9:16) Resource nestedA. Type: My.Rp/nestedResource@2020-01-01. Declaration start char: 0, length: 433
name: 'nestedA'

resource nestedB 'My.Rp/nestedResource/childType@2020-01-01' = if (shouldDeployChildAndGrandChild) {
//@[11:18) Resource nestedB. Type: My.Rp/nestedResource/childType@2020-01-01. Declaration start char: 2, length: 342
name: 'nestedB'

resource nestedC 'My.Rp/nestedResource/childType/grandchildType@2020-01-01' = {
//@[13:20) Resource nestedC. Type: My.Rp/nestedResource/childType/grandchildType@2020-01-01. Declaration start char: 4, length: 180
name: 'nestedC'
properties: {
style: nestedA.properties.style
}
}

properties: {
}
}
}
Loading

0 comments on commit 8362edc

Please sign in to comment.