public record ScopeData()

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;
        }