Skip to content

Commit

Permalink
Tidy-up
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-c-martin committed Jan 29, 2021
1 parent 54b749b commit f27c21f
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ Compilation createCompilation(string program)
}
");
compilation.Should().HaveDiagnostics(new [] {
("BCP037", DiagnosticLevel.Error, "No other properties are allowed on objects of type \"Mock.Rp/mockType@2020-01-01\"."),
("BCP038", DiagnosticLevel.Error, "The property \"madeUpProperty\" is not allowed on objects of type \"Mock.Rp/mockType@2020-01-01\". Permissible properties include \"dependsOn\"."),
});

// Missing non top-level properties - should be a warning
Expand Down
1 change: 0 additions & 1 deletion src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,6 @@ public ErrorDiagnostic ArgumentCountMismatch(int argumentCount, int mininumArgum
TextSpan,
DiagnosticLevel.Error,
"BCP113",
// ensure that this is kept up-to-date with the logic in
$"Unsupported scope for module deployment in a \"{LanguageConstants.TargetScopeTypeTenant}\" target scope. Omit this property to inherit the current scope, or specify a valid scope. " +
$"Permissible scopes include tenant: tenant(), named management group: managementGroup(<name>), named subscription: subscription(<subId>), or named resource group in a named subscription: resourceGroup(<subId>, <name>).");

Expand Down
101 changes: 8 additions & 93 deletions src/Bicep.Core/Emit/EmitLimitationCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,102 +19,15 @@ public static EmitLimitationInfo Calculate(SemanticModel model)
{
var diagnosticWriter = ToListDiagnosticWriter.Create();

var moduleScopeData = GetModuleScopeInfo(model, diagnosticWriter);
var resourceScopeData = GetResoureScopeInfo(model, diagnosticWriter);
var moduleScopeData = ScopeHelper.GetModuleScopeInfo(model, diagnosticWriter);
var resourceScopeData = ScopeHelper.GetResoureScopeInfo(model, diagnosticWriter);
DeployTimeConstantVisitor.ValidateDeployTimeConstants(model, diagnosticWriter);

diagnosticWriter.WriteMultiple(DetectDuplicateNames(model, resourceScopeData, moduleScopeData));

return new EmitLimitationInfo(diagnosticWriter.GetDiagnostics(), moduleScopeData, resourceScopeData);
}

public static ImmutableDictionary<ResourceSymbol, ScopeHelper.ScopeData> GetResoureScopeInfo(SemanticModel semanticModel, IDiagnosticWriter diagnosticWriter)
{
void logInvalidScopeDiagnostic(IPositionable positionable, ResourceScope suppliedScope, ResourceScope supportedScopes)
=> diagnosticWriter.Write(positionable, x => x.UnsupportedResourceScope(suppliedScope, supportedScopes));

var scopeInfo = new Dictionary<ResourceSymbol, ScopeHelper.ScopeData>();

foreach (var resourceSymbol in semanticModel.Root.ResourceDeclarations)
{
if (resourceSymbol.Type is not ResourceType resourceType)
{
// missing type should be caught during type validation
continue;
}

var scopeProperty = resourceSymbol.SafeGetBodyProperty(LanguageConstants.ResourceScopePropertyName);
var scopeData = ScopeHelper.ValidateScope(semanticModel, logInvalidScopeDiagnostic, resourceType.ValidParentScopes, resourceSymbol.DeclaringResource.Body, scopeProperty);

if (scopeData is null)
{
continue;
}

scopeInfo[resourceSymbol] = scopeData;
}

return scopeInfo.ToImmutableDictionary();
}

private static ImmutableDictionary<ModuleSymbol, ScopeHelper.ScopeData> GetModuleScopeInfo(SemanticModel semanticModel, IDiagnosticWriter diagnosticWriter)
{
void logInvalidScopeDiagnostic(IPositionable positionable, ResourceScope suppliedScope, ResourceScope supportedScopes)
=> diagnosticWriter.Write(positionable, x => x.UnsupportedModuleScope(suppliedScope, supportedScopes));

var scopeInfo = new Dictionary<ModuleSymbol, ScopeHelper.ScopeData>();

foreach (var moduleSymbol in semanticModel.Root.ModuleDeclarations)
{
if (moduleSymbol.Type is not ModuleType moduleType)
{
// missing type should be caught during type validation
continue;
}

var scopeProperty = moduleSymbol.SafeGetBodyProperty(LanguageConstants.ResourceScopePropertyName);
var scopeData = ScopeHelper.ValidateScope(semanticModel, logInvalidScopeDiagnostic, moduleType.ValidParentScopes, moduleSymbol.DeclaringModule.Body, scopeProperty);

if (scopeData is null)
{
scopeData = new ScopeHelper.ScopeData { RequestedScope = semanticModel.TargetScope };
}

if (!ScopeHelper.ValidateNestedTemplateScopeRestrictions(semanticModel, scopeData))
{
if (scopeProperty is null)
{
// if there's a scope mismatch, the scope property will be required.
// this means a missing scope property will have already been flagged as an error by type validation.
continue;
}

switch (semanticModel.TargetScope)
{
case ResourceScope.Tenant:
diagnosticWriter.Write(scopeProperty.Value, x => x.InvalidModuleScopeForTenantScope());
break;
case ResourceScope.ManagementGroup:
diagnosticWriter.Write(scopeProperty.Value, x => x.InvalidModuleScopeForManagementScope());
break;
case ResourceScope.Subscription:
diagnosticWriter.Write(scopeProperty.Value, x => x.InvalidModuleScopeForSubscriptionScope());
break;
case ResourceScope.ResourceGroup:
diagnosticWriter.Write(scopeProperty.Value, x => x.InvalidModuleScopeForResourceGroup());
break;
default:
throw new InvalidOperationException($"Unrecognized target scope {semanticModel.TargetScope}");
}
continue;
}

scopeInfo[moduleSymbol] = scopeData;
}

return scopeInfo.ToImmutableDictionary();
}

private static IEnumerable<Diagnostic> DetectDuplicateNames(SemanticModel semanticModel, ImmutableDictionary<ResourceSymbol, ScopeHelper.ScopeData> resourceScopeData, ImmutableDictionary<ModuleSymbol, ScopeHelper.ScopeData> moduleScopeData)
{
// This method only checks, if in one deployment we do not have 2 or more resources with this same name in one deployment to avoid template validation error
Expand Down Expand Up @@ -178,16 +91,18 @@ private static IEnumerable<ResourceDefinition> GetResourceDefinitions(SemanticMo
continue;
}

if (!resourceScopeData.TryGetValue(resource, out var scopeData))
{
scopeData = null;
}

if (resource.Type is not ResourceType resourceType || resource.SafeGetBodyPropertyValue(LanguageConstants.ResourceNamePropertyName) is not StringSyntax namePropertyValue)
{
//currently limiting check to 'name' property values that are strings, although it can be references or other syntaxes
continue;
}

var scopeProperty = resource.SafeGetBodyPropertyValue(LanguageConstants.ResourceScopePropertyName);
var resourceScope = scopeProperty != null ? semanticModel.GetSymbolInfo(scopeProperty) as ResourceSymbol : null;

yield return new ResourceDefinition(resource.Name, resourceScope, resourceType.TypeReference.FullyQualifiedType, namePropertyValue);
yield return new ResourceDefinition(resource.Name, scopeData?.ResourceScopeSymbol, resourceType.TypeReference.FullyQualifiedType, namePropertyValue);
}
}
}
Expand Down
152 changes: 121 additions & 31 deletions src/Bicep.Core/Emit/ScopeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Azure.Deployments.Expression.Expressions;
using Bicep.Core.Diagnostics;
using Bicep.Core.Extensions;
using Bicep.Core.Parsing;
using Bicep.Core.Semantics;
Expand All @@ -30,14 +32,15 @@ public class ScopeData

public delegate void LogInvalidScopeDiagnostic(IPositionable positionable, ResourceScope suppliedScope, ResourceScope supportedScopes);

public static ScopeData? ValidateScope(SemanticModel semanticModel, LogInvalidScopeDiagnostic logInvalidScopeFunc, ResourceScope supportedScopes, SyntaxBase bodySyntax, ObjectPropertySyntax? scopeProperty)
private static ScopeData? ValidateScope(SemanticModel semanticModel, LogInvalidScopeDiagnostic logInvalidScopeFunc, ResourceScope supportedScopes, SyntaxBase bodySyntax, ObjectPropertySyntax? scopeProperty)
{
if (scopeProperty is null)
{
// no scope provided - use the target scope for the file
if (!supportedScopes.HasFlag(semanticModel.TargetScope))
{
logInvalidScopeFunc(bodySyntax, semanticModel.TargetScope, supportedScopes);
return null;
}

return null;
Expand Down Expand Up @@ -104,36 +107,6 @@ public class ScopeData
return null;
}

public static bool ValidateNestedTemplateScopeRestrictions(SemanticModel semanticModel, ScopeData scopeData)
{
bool checkScopes(params ResourceScope[] scopes)
=> scopes.Contains(semanticModel.TargetScope);

switch (scopeData.RequestedScope)
{
// If you update this switch block to add new supported nested template scope combinations,
// please ensure you update the wording of error messages BCP113, BCP114, BCP115 & BCP116 to reflect this!
case ResourceScope.Tenant:
return checkScopes(ResourceScope.Tenant, ResourceScope.ManagementGroup, ResourceScope.Subscription, ResourceScope.ResourceGroup);
case ResourceScope.ManagementGroup when scopeData.ManagementGroupNameProperty is not null:
return checkScopes(ResourceScope.Tenant, ResourceScope.ManagementGroup);
case ResourceScope.ManagementGroup:
return checkScopes(ResourceScope.Tenant, ResourceScope.ManagementGroup);
case ResourceScope.Subscription when scopeData.SubscriptionIdProperty is not null:
return checkScopes(ResourceScope.Tenant, ResourceScope.ManagementGroup, ResourceScope.Subscription);
case ResourceScope.Subscription:
return checkScopes(ResourceScope.Subscription);
case ResourceScope.ResourceGroup when scopeData.SubscriptionIdProperty is not null && scopeData.ResourceGroupProperty is not null:
return checkScopes(ResourceScope.Tenant, ResourceScope.ManagementGroup, ResourceScope.ResourceGroup);
case ResourceScope.ResourceGroup when scopeData.ResourceGroupProperty is not null:
return checkScopes(ResourceScope.Subscription, ResourceScope.ResourceGroup);
case ResourceScope.ResourceGroup:
return checkScopes(ResourceScope.ResourceGroup);
}

return true;
}

public static LanguageExpression FormatCrossScopeResourceId(ExpressionConverter expressionConverter, ScopeData scopeData, string fullyQualifiedType, IEnumerable<LanguageExpression> nameSegments)
{
var arguments = new List<LanguageExpression>();
Expand Down Expand Up @@ -268,5 +241,122 @@ public static void EmitModuleScopeProperties(ResourceScope targetScope, ScopeDat
throw new InvalidOperationException($"Cannot format resourceId for scope {scopeData.RequestedScope}");
}
}

public static ImmutableDictionary<ResourceSymbol, ScopeData> GetResoureScopeInfo(SemanticModel semanticModel, IDiagnosticWriter diagnosticWriter)
{
void logInvalidScopeDiagnostic(IPositionable positionable, ResourceScope suppliedScope, ResourceScope supportedScopes)
=> diagnosticWriter.Write(positionable, x => x.UnsupportedResourceScope(suppliedScope, supportedScopes));

var scopeInfo = new Dictionary<ResourceSymbol, ScopeData>();

foreach (var resourceSymbol in semanticModel.Root.ResourceDeclarations)
{
if (resourceSymbol.Type is not ResourceType resourceType)
{
// missing type should be caught during type validation
continue;
}

var scopeProperty = resourceSymbol.SafeGetBodyProperty(LanguageConstants.ResourceScopePropertyName);
var scopeData = ScopeHelper.ValidateScope(semanticModel, logInvalidScopeDiagnostic, resourceType.ValidParentScopes, resourceSymbol.DeclaringResource.Body, scopeProperty);

if (scopeData is null)
{
continue;
}

scopeInfo[resourceSymbol] = scopeData;
}

return scopeInfo.ToImmutableDictionary();
}

private static bool ValidateNestedTemplateScopeRestrictions(SemanticModel semanticModel, ScopeData scopeData)
{
bool checkScopes(params ResourceScope[] scopes)
=> scopes.Contains(semanticModel.TargetScope);

switch (scopeData.RequestedScope)
{
// If you update this switch block to add new supported nested template scope combinations,
// please ensure you update the wording of error messages BCP113, BCP114, BCP115 & BCP116 to reflect this!
case ResourceScope.Tenant:
return checkScopes(ResourceScope.Tenant, ResourceScope.ManagementGroup, ResourceScope.Subscription, ResourceScope.ResourceGroup);
case ResourceScope.ManagementGroup when scopeData.ManagementGroupNameProperty is not null:
return checkScopes(ResourceScope.Tenant, ResourceScope.ManagementGroup);
case ResourceScope.ManagementGroup:
return checkScopes(ResourceScope.Tenant, ResourceScope.ManagementGroup);
case ResourceScope.Subscription when scopeData.SubscriptionIdProperty is not null:
return checkScopes(ResourceScope.Tenant, ResourceScope.ManagementGroup, ResourceScope.Subscription);
case ResourceScope.Subscription:
return checkScopes(ResourceScope.Subscription);
case ResourceScope.ResourceGroup when scopeData.SubscriptionIdProperty is not null && scopeData.ResourceGroupProperty is not null:
return checkScopes(ResourceScope.Tenant, ResourceScope.ManagementGroup, ResourceScope.ResourceGroup);
case ResourceScope.ResourceGroup when scopeData.ResourceGroupProperty is not null:
return checkScopes(ResourceScope.Subscription, ResourceScope.ResourceGroup);
case ResourceScope.ResourceGroup:
return checkScopes(ResourceScope.ResourceGroup);
}

return true;
}

public static ImmutableDictionary<ModuleSymbol, ScopeData> GetModuleScopeInfo(SemanticModel semanticModel, IDiagnosticWriter diagnosticWriter)
{
void logInvalidScopeDiagnostic(IPositionable positionable, ResourceScope suppliedScope, ResourceScope supportedScopes)
=> diagnosticWriter.Write(positionable, x => x.UnsupportedModuleScope(suppliedScope, supportedScopes));

var scopeInfo = new Dictionary<ModuleSymbol, ScopeData>();

foreach (var moduleSymbol in semanticModel.Root.ModuleDeclarations)
{
if (moduleSymbol.Type is not ModuleType moduleType)
{
// missing type should be caught during type validation
continue;
}

var scopeProperty = moduleSymbol.SafeGetBodyProperty(LanguageConstants.ResourceScopePropertyName);
var scopeData = ScopeHelper.ValidateScope(semanticModel, logInvalidScopeDiagnostic, moduleType.ValidParentScopes, moduleSymbol.DeclaringModule.Body, scopeProperty);

if (scopeData is null)
{
scopeData = new ScopeData { RequestedScope = semanticModel.TargetScope };
}

if (!ScopeHelper.ValidateNestedTemplateScopeRestrictions(semanticModel, scopeData))
{
if (scopeProperty is null)
{
// if there's a scope mismatch, the scope property will be required.
// this means a missing scope property will have already been flagged as an error by type validation.
continue;
}

switch (semanticModel.TargetScope)
{
case ResourceScope.Tenant:
diagnosticWriter.Write(scopeProperty.Value, x => x.InvalidModuleScopeForTenantScope());
break;
case ResourceScope.ManagementGroup:
diagnosticWriter.Write(scopeProperty.Value, x => x.InvalidModuleScopeForManagementScope());
break;
case ResourceScope.Subscription:
diagnosticWriter.Write(scopeProperty.Value, x => x.InvalidModuleScopeForSubscriptionScope());
break;
case ResourceScope.ResourceGroup:
diagnosticWriter.Write(scopeProperty.Value, x => x.InvalidModuleScopeForResourceGroup());
break;
default:
throw new InvalidOperationException($"Unrecognized target scope {semanticModel.TargetScope}");
}
continue;
}

scopeInfo[moduleSymbol] = scopeData;
}

return scopeInfo.ToImmutableDictionary();
}
}
}
Loading

0 comments on commit f27c21f

Please sign in to comment.