diff --git a/src/Bicep.Core.IntegrationTests/NestedResourceTests.cs b/src/Bicep.Core.IntegrationTests/NestedResourceTests.cs index d51f321f74c..7bca30667fb 100644 --- a/src/Bicep.Core.IntegrationTests/NestedResourceTests.cs +++ b/src/Bicep.Core.IntegrationTests/NestedResourceTests.cs @@ -653,7 +653,7 @@ public void Nested_resource_formats_names_and_dependsOn_correctly() [TestMethod] public void Nested_resource_works_with_extension_resources() { - var (template, diags, _) = CompilationHelper.Compile(@" + var result = CompilationHelper.Compile(@" resource res1 'Microsoft.Rp1/resource1@2020-06-01' = { name: 'res1' @@ -677,31 +677,28 @@ public void Nested_resource_works_with_extension_resources() output res2childid string = res2::child.id "); - using (new AssertionScope()) - { - diags.ExcludingMissingTypes().Should().BeEmpty(); + result.Diagnostics.ExcludingMissingTypes().Should().BeEmpty(); - // res1 - template.Should().HaveValueAtPath("$.resources[2].name", "res1"); - template.Should().NotHaveValueAtPath("$.resources[2].dependsOn"); + // res1 + result.Template.Should().HaveValueAtPath("$.resources[2].name", "res1"); + result.Template.Should().NotHaveValueAtPath("$.resources[2].dependsOn"); - // res1::child1 - template.Should().HaveValueAtPath("$.resources[0].name", "[format('{0}/{1}', 'res1', 'child1')]"); - template.Should().HaveValueAtPath("$.resources[0].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1', 'res1')]" }); + // res1::child1 + result.Template.Should().HaveValueAtPath("$.resources[0].name", "[format('{0}/{1}', 'res1', 'child1')]"); + result.Template.Should().HaveValueAtPath("$.resources[0].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1', 'res1')]" }); - // res2 - template.Should().HaveValueAtPath("$.resources[3].name", "res2"); - template.Should().HaveValueAtPath("$.resources[3].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1')]" }); + // res2 + result.Template.Should().HaveValueAtPath("$.resources[3].name", "res2"); + result.Template.Should().HaveValueAtPath("$.resources[3].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1')]" }); - // res2::child2 - template.Should().HaveValueAtPath("$.resources[1].name", "[format('{0}/{1}', 'res2', 'child2')]"); - template.Should().HaveValueAtPath("$.resources[1].dependsOn", new JArray { "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2', 'res2')]" }); + // res2::child2 + result.Template.Should().HaveValueAtPath("$.resources[1].name", "[format('{0}/{1}', 'res2', 'child2')]"); + result.Template.Should().HaveValueAtPath("$.resources[1].dependsOn", new JArray { "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2', 'res2')]" }); - template.Should().HaveValueAtPath("$.outputs['res2childprop'].value", "[reference(extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')).someProp]"); - template.Should().HaveValueAtPath("$.outputs['res2childname'].value", "child2"); - template.Should().HaveValueAtPath("$.outputs['res2childtype'].value", "Microsoft.Rp2/resource2/child2"); - template.Should().HaveValueAtPath("$.outputs['res2childid'].value", "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')]"); - } + result.Template.Should().HaveValueAtPath("$.outputs['res2childprop'].value", "[reference(extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')).someProp]"); + result.Template.Should().HaveValueAtPath("$.outputs['res2childname'].value", "child2"); + result.Template.Should().HaveValueAtPath("$.outputs['res2childtype'].value", "Microsoft.Rp2/resource2/child2"); + result.Template.Should().HaveValueAtPath("$.outputs['res2childid'].value", "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')]"); } [TestMethod] @@ -771,10 +768,32 @@ public void Nested_resource_formats_references_correctly_for_existing_resources( } } + [DataTestMethod] + [DataRow("resourceGroup('other')")] + [DataRow("subscription()")] + [DataRow("managementGroup('abcdef')")] + public void Nested_resource_blocks_existing_parents_at_different_scopes(string parentScope) + { + var result = CompilationHelper.Compile(@" +resource res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { + scope: " + parentScope + @" + name: 'res1' + + resource child 'child1' = { + name: 'child1' + } +} +"); + + result.Diagnostics.ExcludingMissingTypes().Should().HaveDiagnostics(new[] { + ("BCP165", DiagnosticLevel.Error, "Cannot deploy a resource with ancestor under a different scope. Resource \"res1\" has the \"scope\" property set."), + }); + } + [TestMethod] - public void Nested_resource_blocks_existing_parents_at_different_scopes() + public void Nested_resource_allows_existing_parents_at_tenant_scope() { - var (template, diags, _) = CompilationHelper.Compile(@" + var result = CompilationHelper.Compile(@" resource res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { scope: tenant() name: 'res1' @@ -785,13 +804,9 @@ public void Nested_resource_blocks_existing_parents_at_different_scopes() } "); - using (new AssertionScope()) - { - template.Should().NotHaveValue(); - diags.ExcludingMissingTypes().Should().HaveDiagnostics(new[] { - ("BCP165", DiagnosticLevel.Error, "Cannot deploy a resource with ancestor under a different scope. Resource \"res1\" has the \"scope\" property set."), - }); - } + result.Diagnostics.ExcludingMissingTypes().Should().BeEmpty(); + result.Template.Should().HaveValueAtPath("$.resources[0].scope", "/"); + result.Template.Should().HaveValueAtPath("$.resources[0].name", "[format('{0}/{1}', 'res1', 'child1')]"); } [TestMethod] diff --git a/src/Bicep.Core.IntegrationTests/ParentPropertyResourceTests.cs b/src/Bicep.Core.IntegrationTests/ParentPropertyResourceTests.cs index 178ac0f6485..60610b615c1 100644 --- a/src/Bicep.Core.IntegrationTests/ParentPropertyResourceTests.cs +++ b/src/Bicep.Core.IntegrationTests/ParentPropertyResourceTests.cs @@ -76,7 +76,7 @@ public void Parent_property_formats_names_and_dependsOn_correctly() [TestMethod] public void Parent_property_works_with_extension_resources() { - var (template, diags, _) = CompilationHelper.Compile(@" + var result = CompilationHelper.Compile(@" resource res1 'Microsoft.Rp1/resource1@2020-06-01' = { name: 'res1' } @@ -102,25 +102,24 @@ public void Parent_property_works_with_extension_resources() output res2childid string = res2child.id "); - using (new AssertionScope()) - { - template.Should().HaveValueAtPath("$.resources[0].name", "res1"); - template.Should().NotHaveValueAtPath("$.resources[0].dependsOn"); + result.Diagnostics.ExcludingMissingTypes().Should().BeEmpty(); - template.Should().HaveValueAtPath("$.resources[1].name", "[format('{0}/{1}', 'res1', 'child1')]"); - template.Should().HaveValueAtPath("$.resources[1].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1', 'res1')]" }); + result.Template.Should().HaveValueAtPath("$.resources[0].name", "res1"); + result.Template.Should().NotHaveValueAtPath("$.resources[0].dependsOn"); - template.Should().HaveValueAtPath("$.resources[2].name", "res2"); - template.Should().HaveValueAtPath("$.resources[2].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1')]" }); + result.Template.Should().HaveValueAtPath("$.resources[1].name", "[format('{0}/{1}', 'res1', 'child1')]"); + result.Template.Should().HaveValueAtPath("$.resources[1].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1', 'res1')]" }); - template.Should().HaveValueAtPath("$.resources[3].name", "[format('{0}/{1}', 'res2', 'child2')]"); - template.Should().HaveValueAtPath("$.resources[3].dependsOn", new JArray { "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2', 'res2')]" }); + result.Template.Should().HaveValueAtPath("$.resources[2].name", "res2"); + result.Template.Should().HaveValueAtPath("$.resources[2].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1')]" }); - template.Should().HaveValueAtPath("$.outputs['res2childprop'].value", "[reference(extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')).someProp]"); - template.Should().HaveValueAtPath("$.outputs['res2childname'].value", "child2"); - template.Should().HaveValueAtPath("$.outputs['res2childtype'].value", "Microsoft.Rp2/resource2/child2"); - template.Should().HaveValueAtPath("$.outputs['res2childid'].value", "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')]"); - } + result.Template.Should().HaveValueAtPath("$.resources[3].name", "[format('{0}/{1}', 'res2', 'child2')]"); + result.Template.Should().HaveValueAtPath("$.resources[3].dependsOn", new JArray { "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2', 'res2')]" }); + + result.Template.Should().HaveValueAtPath("$.outputs['res2childprop'].value", "[reference(extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')).someProp]"); + result.Template.Should().HaveValueAtPath("$.outputs['res2childname'].value", "child2"); + result.Template.Should().HaveValueAtPath("$.outputs['res2childtype'].value", "Microsoft.Rp2/resource2/child2"); + result.Template.Should().HaveValueAtPath("$.outputs['res2childid'].value", "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')]"); } [TestMethod] @@ -192,10 +191,33 @@ public void Parent_property_formats_references_correctly_for_existing_resources( } } + [DataTestMethod] + [DataRow("resourceGroup('other')")] + [DataRow("subscription()")] + [DataRow("managementGroup('abcdef')")] + public void Parent_property_blocks_existing_parents_at_different_scopes(string parentScope) + { + var result = CompilationHelper.Compile(@" +resource res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { + scope: " + parentScope + @" + name: 'res1' +} + +resource child1 'Microsoft.Rp1/resource1/child1@2020-06-01' = { + parent: res1 + name: 'child1' +} +"); + + result.Diagnostics.ExcludingMissingTypes().Should().HaveDiagnostics(new[] { + ("BCP165", DiagnosticLevel.Error, "Cannot deploy a resource with ancestor under a different scope. Resource \"res1\" has the \"scope\" property set."), + }); + } + [TestMethod] - public void Parent_property_blocks_existing_parents_at_different_scopes() + public void Parent_property_allows_existing_parents_at_tenant_scope() { - var (template, diags, _) = CompilationHelper.Compile(@" + var result = CompilationHelper.Compile(@" resource res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { scope: tenant() name: 'res1' @@ -207,13 +229,9 @@ public void Parent_property_blocks_existing_parents_at_different_scopes() } "); - using (new AssertionScope()) - { - template.Should().NotHaveValue(); - diags.ExcludingMissingTypes().Should().HaveDiagnostics(new[] { - ("BCP165", DiagnosticLevel.Error, "Cannot deploy a resource with ancestor under a different scope. Resource \"res1\" has the \"scope\" property set."), - }); - } + result.Diagnostics.ExcludingMissingTypes().Should().BeEmpty(); + result.Template.Should().HaveValueAtPath("$.resources[0].scope", "/"); + result.Template.Should().HaveValueAtPath("$.resources[0].name", "[format('{0}/{1}', 'res1', 'child1')]"); } [TestMethod] diff --git a/src/Bicep.Core.IntegrationTests/ScenarioTests.cs b/src/Bicep.Core.IntegrationTests/ScenarioTests.cs index 861a621c056..4c3c92ab785 100644 --- a/src/Bicep.Core.IntegrationTests/ScenarioTests.cs +++ b/src/Bicep.Core.IntegrationTests/ScenarioTests.cs @@ -2439,6 +2439,30 @@ param defaultAdvancedFilterObject object result.Should().NotHaveAnyDiagnostics(); } + [TestMethod] + // https://github.com/Azure/bicep/issues/2990 + public void Test_Issue2990() + { + var result = CompilationHelper.Compile(@" +targetScope = 'managementGroup' + +param managementGroupName string +param subscriptionId string + +resource myManagementGroup 'Microsoft.Management/managementGroups@2021-04-01' existing = { + scope: tenant() + name: managementGroupName +} + +resource subscriptionAssociation 'Microsoft.Management/managementGroups/subscriptions@2021-04-01' = { + parent: myManagementGroup + name: subscriptionId +} +"); + + result.Should().NotHaveAnyDiagnostics(); + } + [TestMethod] // https://github.com/Azure/bicep/issues/4007 public void Test_Issue4007() diff --git a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.bicep b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.bicep index 35d4d78bc4f..d347b63f128 100644 --- a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.bicep @@ -1297,7 +1297,7 @@ resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = { // parent property with 'existing' resource at different scope resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { - scope: tenant() + scope: subscription() name: 'res1' } diff --git a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.diagnostics.bicep index c81141a5f8a..cbf3e3c614c 100644 --- a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.diagnostics.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.diagnostics.bicep @@ -1806,7 +1806,7 @@ resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = { // parent property with 'existing' resource at different scope resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { //@[17:53) [BCP081 (Warning)] Resource type "Microsoft.Rp1/resource1@2020-06-01" does not have types available. (CodeDescription: none) |'Microsoft.Rp1/resource1@2020-06-01'| - scope: tenant() + scope: subscription() name: 'res1' } diff --git a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.formatted.bicep index 48573cede6d..53631b4ccb0 100644 --- a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.formatted.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.formatted.bicep @@ -1237,7 +1237,7 @@ resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = { // parent property with 'existing' resource at different scope resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { - scope: tenant() + scope: subscription() name: 'res1' } diff --git a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.symbols.bicep index 73697f4dda4..a893f53459d 100644 --- a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.symbols.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.symbols.bicep @@ -1673,8 +1673,8 @@ resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = { // parent property with 'existing' resource at different scope resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { -//@[9:16) Resource p1_res1. Type: Microsoft.Rp1/resource1@2020-06-01. Declaration start char: 0, length: 104 - scope: tenant() +//@[9:16) Resource p1_res1. Type: Microsoft.Rp1/resource1@2020-06-01. Declaration start char: 0, length: 110 + scope: subscription() name: 'res1' } diff --git a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.syntax.bicep index b43a8c84748..f593e4ee98d 100644 --- a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.syntax.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.syntax.bicep @@ -9736,7 +9736,7 @@ resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = { // parent property with 'existing' resource at different scope //@[62:64) NewLine |\r\n| resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { -//@[0:104) ResourceDeclarationSyntax +//@[0:110) ResourceDeclarationSyntax //@[0:8) Identifier |resource| //@[9:16) IdentifierSyntax //@[9:16) Identifier |p1_res1| @@ -9744,20 +9744,20 @@ resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { //@[17:53) StringComplete |'Microsoft.Rp1/resource1@2020-06-01'| //@[54:62) Identifier |existing| //@[63:64) Assignment |=| -//@[65:104) ObjectSyntax +//@[65:110) ObjectSyntax //@[65:66) LeftBrace |{| //@[66:68) NewLine |\r\n| - scope: tenant() -//@[2:17) ObjectPropertySyntax + scope: subscription() +//@[2:23) ObjectPropertySyntax //@[2:7) IdentifierSyntax //@[2:7) Identifier |scope| //@[7:8) Colon |:| -//@[9:17) FunctionCallSyntax -//@[9:15) IdentifierSyntax -//@[9:15) Identifier |tenant| -//@[15:16) LeftParen |(| -//@[16:17) RightParen |)| -//@[17:19) NewLine |\r\n| +//@[9:23) FunctionCallSyntax +//@[9:21) IdentifierSyntax +//@[9:21) Identifier |subscription| +//@[21:22) LeftParen |(| +//@[22:23) RightParen |)| +//@[23:25) NewLine |\r\n| name: 'res1' //@[2:14) ObjectPropertySyntax //@[2:6) IdentifierSyntax diff --git a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.tokens.bicep index 2f60c9f0fb9..dc369f1643e 100644 --- a/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.tokens.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidResources_CRLF/main.tokens.bicep @@ -6292,13 +6292,13 @@ resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = { //@[63:64) Assignment |=| //@[65:66) LeftBrace |{| //@[66:68) NewLine |\r\n| - scope: tenant() + scope: subscription() //@[2:7) Identifier |scope| //@[7:8) Colon |:| -//@[9:15) Identifier |tenant| -//@[15:16) LeftParen |(| -//@[16:17) RightParen |)| -//@[17:19) NewLine |\r\n| +//@[9:21) Identifier |subscription| +//@[21:22) LeftParen |(| +//@[22:23) RightParen |)| +//@[23:25) NewLine |\r\n| name: 'res1' //@[2:6) Identifier |name| //@[6:7) Colon |:| diff --git a/src/Bicep.Core/Emit/ScopeHelper.cs b/src/Bicep.Core/Emit/ScopeHelper.cs index 209c7da62f9..f6a49502403 100644 --- a/src/Bicep.Core/Emit/ScopeHelper.cs +++ b/src/Bicep.Core/Emit/ScopeHelper.cs @@ -404,10 +404,18 @@ private static void ValidateResourceScopeRestrictions(SemanticModel semanticMode return; } - if(scopeData.RequestedScope == ResourceScope.Tenant) + if (!IsDeployableResourceScope(semanticModel, scopeData)) + { + writeScopeDiagnostic(x => x.InvalidCrossResourceScope()); + } + } + + private static bool IsDeployableResourceScope(SemanticModel semanticModel, ScopeData scopeData) + { + if (scopeData.RequestedScope == ResourceScope.Tenant) { // tenant resources can be deployed cross-scope - return; + return true; } // we only allow resources to be deployed at the target scope @@ -417,10 +425,7 @@ scopeData.SubscriptionIdProperty is null && scopeData.ResourceGroupProperty is null && scopeData.ResourceScope is null); - if (!matchesTargetScope) - { - writeScopeDiagnostic(x => x.InvalidCrossResourceScope()); - } + return matchesTargetScope; } public static ImmutableDictionary GetResourceScopeInfo(SemanticModel semanticModel, IDiagnosticWriter diagnosticWriter) @@ -434,6 +439,8 @@ void logInvalidScopeDiagnostic(IPositionable positionable, ResourceScope supplie x => x, x => semanticModel.ResourceAncestors.GetAncestors(x)); + var defaultScopeData = new ScopeData { RequestedScope = semanticModel.TargetScope }; + // process symbols in order of ancestor depth. // this is because we want to avoid recomputing the scope for child resources which inherit it from their parents. foreach (var (resource, ancestors) in ancestorsLookup.OrderBy(kvp => kvp.Value.Length)) @@ -445,22 +452,26 @@ void logInvalidScopeDiagnostic(IPositionable positionable, ResourceScope supplie // it doesn't make sense to have scope on a descendent resource; it should be inherited from the oldest ancestor. diagnosticWriter.Write(resource.ScopeSyntax, x => x.ScopeUnsupportedOnChildResource(ancestors.Last().Resource.Symbol.Name)); // TODO: format the ancestor name using the resource accessor (::) for nested resources + scopeInfo[resource] = defaultScopeData; continue; } var firstAncestor = ancestors.First(); if (!resource.IsExistingResource && - firstAncestor.Resource.IsExistingResource && - firstAncestor.Resource.ScopeSyntax is {} firstAncestorScope) + firstAncestor.Resource.IsExistingResource && + !IsDeployableResourceScope(semanticModel, scopeInfo[firstAncestor.Resource])) { - // it doesn't make sense to have scope on a descendent resource; it should be inherited from the oldest ancestor. + // Setting 'scope' is blocked for child resources, so we just need to check whether the root ancestor has 'scope' set. + // If it does, it could be an 'existing' resource - which can be assigned any scope - so we need to ensure the assigned scope can be used to deploy. diagnosticWriter.Write(resource.Symbol.DeclaringResource.Value, x => x.ScopeDisallowedForAncestorResource(firstAncestor.Resource.Symbol.Name)); // TODO: format the ancestor name using the resource accessor (::) for nested resources + scopeInfo[resource] = defaultScopeData; continue; } if (semanticModel.Binder.TryGetCycle(resource.Symbol) is not null) { + scopeInfo[resource] = defaultScopeData; continue; } @@ -470,14 +481,9 @@ void logInvalidScopeDiagnostic(IPositionable positionable, ResourceScope supplie continue; } - var scopeData = ScopeHelper.ValidateScope(semanticModel, logInvalidScopeDiagnostic, resource.Type.ValidParentScopes, resource.Symbol.DeclaringResource.Value, resource.ScopeSyntax); - - if (scopeData is null) - { - scopeData = new ScopeData { RequestedScope = semanticModel.TargetScope }; - } + var validatedScopeData = ScopeHelper.ValidateScope(semanticModel, logInvalidScopeDiagnostic, resource.Type.ValidParentScopes, resource.Symbol.DeclaringResource.Value, resource.ScopeSyntax); - scopeInfo[resource] = scopeData; + scopeInfo[resource] = validatedScopeData ?? defaultScopeData; } foreach (var resourceToValidate in semanticModel.AllResources)