in src/Bicep.Core/Emit/ScopeHelper.cs [26:195]
public record ScopeData(
ResourceScope RequestedScope,
SyntaxBase? ManagementGroupNameProperty = null,
SyntaxBase? SubscriptionIdProperty = null,
SyntaxBase? ResourceGroupProperty = null,
DeclaredResourceMetadata? ResourceScope = null,
ImmutableArray<SyntaxBase>? ResourceScopeNameSyntaxSegments = null,
SyntaxBase? IndexExpression = null);
public delegate void LogInvalidScopeDiagnostic(IPositionable positionable, ResourceScope suppliedScope, ResourceScope supportedScopes);
private static ScopeData? ValidateScope(SemanticModel semanticModel, LogInvalidScopeDiagnostic logInvalidScopeFunc, ResourceScope supportedScopes, SyntaxBase bodySyntax, SyntaxBase? scopeValue)
{
// If the DSC feature is enabled the scope is added to the supported scopes here so it doesn't have to be added to the Azure types.
if (semanticModel.Configuration.ExperimentalFeaturesEnabled.DesiredStateConfiguration)
{
supportedScopes |= ResourceScope.DesiredStateConfiguration;
}
if (semanticModel.Configuration.ExperimentalFeaturesEnabled.LocalDeploy)
{
supportedScopes |= ResourceScope.Local;
}
if (scopeValue 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;
}
var (scopeSymbol, indexExpression) = scopeValue switch
{
// scope indexing can only happen with references to module or resource collections
ArrayAccessSyntax { BaseExpression: VariableAccessSyntax baseVariableAccess } arrayAccess => (semanticModel.GetSymbolInfo(baseVariableAccess), arrayAccess.IndexExpression),
ArrayAccessSyntax { BaseExpression: ResourceAccessSyntax baseVariableAccess } arrayAccess => (semanticModel.GetSymbolInfo(baseVariableAccess), arrayAccess.IndexExpression),
// all other scope expressions
_ => (semanticModel.GetSymbolInfo(scopeValue), null)
};
var scopeType = semanticModel.GetTypeInfo(scopeValue);
switch (scopeType)
{
case TenantScopeType type:
if (!supportedScopes.HasFlag(ResourceScope.Tenant))
{
logInvalidScopeFunc(scopeValue, ResourceScope.Tenant, supportedScopes);
return null;
}
return new ScopeData(ResourceScope.Tenant, IndexExpression: indexExpression);
case ManagementGroupScopeType type:
if (!supportedScopes.HasFlag(ResourceScope.ManagementGroup))
{
logInvalidScopeFunc(scopeValue, ResourceScope.ManagementGroup, supportedScopes);
return null;
}
return type.Arguments.Length switch
{
0 => new ScopeData(ResourceScope.ManagementGroup, IndexExpression: indexExpression),
_ => new ScopeData(ResourceScope.ManagementGroup, ManagementGroupNameProperty: type.Arguments[0].Expression, IndexExpression: indexExpression),
};
case SubscriptionScopeType type:
if (!supportedScopes.HasFlag(ResourceScope.Subscription))
{
logInvalidScopeFunc(scopeValue, ResourceScope.Subscription, supportedScopes);
return null;
}
return type.Arguments.Length switch
{
0 => new ScopeData(ResourceScope.Subscription, IndexExpression: indexExpression),
_ => new ScopeData(ResourceScope.Subscription, SubscriptionIdProperty: type.Arguments[0].Expression, IndexExpression: indexExpression),
};
case ResourceGroupScopeType type:
if (!supportedScopes.HasFlag(ResourceScope.ResourceGroup))
{
logInvalidScopeFunc(scopeValue, ResourceScope.ResourceGroup, supportedScopes);
return null;
}
return type.Arguments.Length switch
{
0 => new ScopeData(ResourceScope.ResourceGroup, IndexExpression: indexExpression),
1 => new ScopeData(ResourceScope.ResourceGroup, ResourceGroupProperty: type.Arguments[0].Expression, IndexExpression: indexExpression),
_ => new ScopeData(ResourceScope.ResourceGroup, SubscriptionIdProperty: type.Arguments[0].Expression, ResourceGroupProperty: type.Arguments[1].Expression, IndexExpression: indexExpression),
};
case { } when scopeSymbol is ResourceSymbol targetResourceSymbol:
if (semanticModel.ResourceMetadata.TryLookup(targetResourceSymbol.DeclaringSyntax) is not DeclaredResourceMetadata targetResource)
{
return null;
}
if (targetResource.Symbol.IsCollection && indexExpression is null)
{
// the target is a resource collection, but the user didn't apply an array indexer to it
// the type check will produce a good error
return null;
}
if (StringComparer.OrdinalIgnoreCase.Equals(targetResource.TypeReference.FormatType(), AzResourceTypeProvider.ResourceTypeResourceGroup))
{
// special-case 'Microsoft.Resources/resourceGroups' in order to allow it to create a resourceGroup-scope resource
// ignore diagnostics - these will be collected separately in the pass over resources
var hasErrors = false;
var rgScopeData = ScopeHelper.ValidateScope(semanticModel, (_, _, _) => { hasErrors = true; }, targetResource.Type.ValidParentScopes, targetResource.Symbol.DeclaringResource.Value, targetResource.TryGetScopeSyntax());
if (!hasErrors)
{
if (!supportedScopes.HasFlag(ResourceScope.ResourceGroup))
{
logInvalidScopeFunc(scopeValue, ResourceScope.ResourceGroup, supportedScopes);
return null;
}
return new ScopeData(ResourceScope.ResourceGroup, SubscriptionIdProperty: rgScopeData?.SubscriptionIdProperty, ResourceGroupProperty: targetResource.TryGetNameSyntax(), IndexExpression: indexExpression);
}
}
if (StringComparer.OrdinalIgnoreCase.Equals(targetResource.TypeReference.FormatType(), AzResourceTypeProvider.ResourceTypeManagementGroup))
{
// special-case 'Microsoft.Management/managementGroups' in order to allow it to create a managementGroup-scope resource
// ignore diagnostics - these will be collected separately in the pass over resources
var hasErrors = false;
var mgScopeData = ScopeHelper.ValidateScope(semanticModel, (_, _, _) => { hasErrors = true; }, targetResource.Type.ValidParentScopes, targetResource.Symbol.DeclaringResource.Value, targetResource.TryGetScopeSyntax());
if (!hasErrors)
{
if (!supportedScopes.HasFlag(ResourceScope.ManagementGroup))
{
logInvalidScopeFunc(scopeValue, ResourceScope.ManagementGroup, supportedScopes);
return null;
}
return new ScopeData(ResourceScope.ManagementGroup, ManagementGroupNameProperty: targetResource.TryGetNameSyntax(), IndexExpression: indexExpression);
}
}
if (!supportedScopes.HasFlag(ResourceScope.Resource))
{
logInvalidScopeFunc(scopeValue, ResourceScope.Resource, supportedScopes);
return null;
}
return new ScopeData(ResourceScope.Resource, ResourceScope: targetResource, IndexExpression: indexExpression);
case { } when scopeSymbol is ModuleSymbol targetModuleSymbol:
if (targetModuleSymbol.IsCollection == (indexExpression is not null))
{
// using a single module as a scope of another module is not allowed
// we log this error only when we have single module without an index expression or
// a module collection with an index expression
// otherwise, the errors produced by the type check are sufficient
logInvalidScopeFunc(scopeValue, ResourceScope.Module, supportedScopes);
}
return null;
}
// type validation should have already caught this
return null;
}