Example #1
0
        private void EmitDependsOn(DeclaredSymbol declaredSymbol)
        {
            var dependencies = context.ResourceDependencies[declaredSymbol];

            if (!dependencies.Any())
            {
                return;
            }

            writer.WritePropertyName("dependsOn");
            writer.WriteStartArray();
            // need to put dependencies in a deterministic order to generate a deterministic template
            foreach (var dependency in dependencies.OrderBy(x => x.Name))
            {
                switch (dependency)
                {
                case ResourceSymbol resourceDependency:
                    var typeReference = EmitHelpers.GetTypeReference(resourceDependency);
                    emitter.EmitResourceIdReference(resourceDependency.DeclaringResource, typeReference);
                    break;

                case ModuleSymbol moduleDependency:
                    emitter.EmitModuleResourceIdExpression(moduleDependency);
                    break;

                default:
                    throw new InvalidOperationException($"Found dependency '{dependency.Name}' of unexpected type {dependency.GetType()}");
                }
            }
            writer.WriteEndArray();
        }
Example #2
0
        private void EmitResource(ResourceSymbol resourceSymbol)
        {
            writer.WriteStartObject();

            var typeReference = EmitHelpers.GetTypeReference(resourceSymbol);

            if (resourceSymbol.DeclaringResource.IfCondition is IfConditionSyntax ifCondition)
            {
                this.emitter.EmitProperty("condition", ifCondition.ConditionExpression);
            }

            this.emitter.EmitProperty("type", typeReference.FullyQualifiedType);
            this.emitter.EmitProperty("apiVersion", typeReference.ApiVersion);
            if (context.SemanticModel.EmitLimitationInfo.ResoureScopeData[resourceSymbol] is ResourceSymbol scopeResource)
            {
                this.emitter.EmitProperty("scope", () => this.emitter.EmitUnqualifiedResourceId(scopeResource));
            }
            this.emitter.EmitObjectProperties((ObjectSyntax)resourceSymbol.DeclaringResource.Body, ResourcePropertiesToOmit);

            // dependsOn is currently not allowed as a top-level resource property in bicep
            // we will need to revisit this and probably merge the two if we decide to allow it
            this.EmitDependsOn(resourceSymbol);

            writer.WriteEndObject();
        }
Example #3
0
        private LanguageExpression ConvertVariableAccess(VariableAccessSyntax variableAccessSyntax)
        {
            string name = variableAccessSyntax.Name.IdentifierName;

            var symbol = context.SemanticModel.GetSymbolInfo(variableAccessSyntax);

            // TODO: This will change to support inlined functions like reference() or list*()
            switch (symbol)
            {
            case ParameterSymbol _:
                return(CreateFunction("parameters", new JTokenExpression(name)));

            case VariableSymbol variableSymbol:
                if (context.VariablesToInline.Contains(variableSymbol))
                {
                    // we've got a runtime dependency, so we have to inline the variable usage
                    return(ConvertExpression(variableSymbol.DeclaringVariable.Value));
                }
                return(CreateFunction("variables", new JTokenExpression(name)));

            case ResourceSymbol resourceSymbol:
                var typeReference = EmitHelpers.GetTypeReference(resourceSymbol);
                return(GetReferenceExpression(resourceSymbol, typeReference, true));

            case ModuleSymbol moduleSymbol:
                return(GetModuleOutputsReferenceExpression(moduleSymbol));

            default:
                throw new NotImplementedException($"Encountered an unexpected symbol kind '{symbol?.Kind}' when generating a variable access expression.");
            }
        }
Example #4
0
        public LanguageExpression GetFullyQualifiedResourceId(ResourceSymbol resourceSymbol)
        {
            var typeReference = EmitHelpers.GetTypeReference(resourceSymbol);

            return(ScopeHelper.FormatFullyQualifiedResourceId(
                       context,
                       this,
                       context.ResourceScopeData[resourceSymbol],
                       typeReference.FullyQualifiedType,
                       GetResourceNameSegments(resourceSymbol, typeReference)));
        }
Example #5
0
        private LanguageExpression GenerateScopedResourceId(ResourceSymbol resourceSymbol, ResourceScope?targetScope)
        {
            var typeReference = EmitHelpers.GetTypeReference(resourceSymbol);
            var nameSegments  = GetResourceNameSegments(resourceSymbol, typeReference);

            if (!context.ResourceScopeData.TryGetValue(resourceSymbol, out var scopeData))
            {
                return(ScopeHelper.FormatLocallyScopedResourceId(targetScope, typeReference.FullyQualifiedType, nameSegments));
            }

            return(ScopeHelper.FormatCrossScopeResourceId(this, scopeData, typeReference.FullyQualifiedType, nameSegments));
        }
Example #6
0
        private LanguageExpression GenerateScopedResourceId(ResourceSymbol resourceSymbol, ResourceScopeType?targetScope)
        {
            var typeReference = EmitHelpers.GetTypeReference(resourceSymbol);
            var nameSegments  = GetResourceNameSegments(resourceSymbol, typeReference);

            if (context.ResoureScopeData[resourceSymbol] is {} parentResourceSymbol)
            {
                // this should be safe because we've already checked for cycles by now
                var parentResourceId = GetUnqualifiedResourceId(parentResourceSymbol);

                return(ExpressionConverter.GenerateScopedResourceId(parentResourceId, typeReference.FullyQualifiedType, nameSegments));
            }

            return(ScopeHelper.FormatLocallyScopedResourceId(targetScope, typeReference.FullyQualifiedType, nameSegments));
        }
Example #7
0
        private void EmitResource(ResourceSymbol resourceSymbol)
        {
            writer.WriteStartObject();

            var typeReference = EmitHelpers.GetTypeReference(resourceSymbol);

            this.emitter.EmitProperty("type", typeReference.FullyQualifiedType);
            this.emitter.EmitProperty("apiVersion", typeReference.ApiVersion);
            this.emitter.EmitObjectProperties((ObjectSyntax)resourceSymbol.Body, ResourcePropertiesToOmit);

            // dependsOn is currently not allowed as a top-level resource property in bicep
            // we will need to revisit this and probably merge the two if we decide to allow it
            this.EmitDependsOn(resourceSymbol);

            writer.WriteEndObject();
        }
Example #8
0
        private void EmitDependsOn(ResourceSymbol resourceSymbol)
        {
            var dependencies = context.ResourceDependencies[resourceSymbol];

            if (!dependencies.Any())
            {
                return;
            }

            writer.WritePropertyName("dependsOn");
            writer.WriteStartArray();
            // need to put dependencies in a deterministic order to generate a deterministic template
            foreach (var dependency in dependencies.OrderBy(x => x.Name))
            {
                var typeReference = EmitHelpers.GetTypeReference(dependency);
                emitter.EmitResourceIdReference(dependency.DeclaringResource, typeReference);
            }
            writer.WriteEndArray();
        }
Example #9
0
        /// <summary>
        /// Converts the specified bicep expression tree into an ARM template expression tree.
        /// The returned tree may be rooted at either a function expression or jtoken expression.
        /// </summary>
        /// <param name="expression">The expression</param>
        public LanguageExpression ConvertExpression(SyntaxBase expression)
        {
            switch (expression)
            {
            case BooleanLiteralSyntax boolSyntax:
                return(CreateFunction(boolSyntax.Value ? "true" : "false"));

            case NumericLiteralSyntax numericSyntax:
                return(new JTokenExpression(numericSyntax.Value));

            case StringSyntax stringSyntax:
                // using the throwing method to get semantic value of the string because
                // error checking should have caught any errors by now
                return(ConvertString(stringSyntax));

            case NullLiteralSyntax _:
                return(CreateFunction("null"));

            case ObjectSyntax @object:
                return(ConvertObject(@object));

            case ArraySyntax array:
                return(ConvertArray(array));

            case ParenthesizedExpressionSyntax parenthesized:
                // template expressions do not have operators so parentheses are irrelevant
                return(ConvertExpression(parenthesized.Expression));

            case UnaryOperationSyntax unary:
                return(ConvertUnary(unary));

            case BinaryOperationSyntax binary:
                return(ConvertBinary(binary));

            case TernaryOperationSyntax ternary:
                return(CreateFunction(
                           "if",
                           ConvertExpression(ternary.ConditionExpression),
                           ConvertExpression(ternary.TrueExpression),
                           ConvertExpression(ternary.FalseExpression)));

            case FunctionCallSyntax function:
                return(ConvertFunction(
                           function.Name.IdentifierName,
                           function.Arguments.Select(a => ConvertExpression(a.Expression))));

            case InstanceFunctionCallSyntax instanceFunctionCall:
                var namespaceSymbol = context.SemanticModel.GetSymbolInfo(instanceFunctionCall.BaseExpression);
                Assert(namespaceSymbol is NamespaceSymbol, $"BaseExpression must be a NamespaceSymbol, instead got: '{namespaceSymbol?.Kind}'");

                return(ConvertFunction(
                           instanceFunctionCall.Name.IdentifierName,
                           instanceFunctionCall.Arguments.Select(a => ConvertExpression(a.Expression))));

            case ArrayAccessSyntax arrayAccess:
                return(AppendProperties(
                           ToFunctionExpression(arrayAccess.BaseExpression),
                           ConvertExpression(arrayAccess.IndexExpression)));

            case PropertyAccessSyntax propertyAccess:
                if (propertyAccess.BaseExpression is VariableAccessSyntax propVariableAccess &&
                    context.SemanticModel.GetSymbolInfo(propVariableAccess) is ResourceSymbol resourceSymbol)
                {
                    // special cases for certain resource property access. if we recurse normally, we'll end up
                    // generating statements like reference(resourceId(...)).id which are not accepted by ARM

                    var typeReference = EmitHelpers.GetTypeReference(resourceSymbol);
                    switch (propertyAccess.PropertyName.IdentifierName)
                    {
                    case "id":
                        return(GetLocallyScopedResourceId(resourceSymbol));

                    case "name":
                        return(GetResourceNameExpression(resourceSymbol));

                    case "type":
                        return(new JTokenExpression(typeReference.FullyQualifiedType));

                    case "apiVersion":
                        return(new JTokenExpression(typeReference.ApiVersion));

                    case "properties":
                        // use the reference() overload without "full" to generate a shorter expression
                        return(GetReferenceExpression(resourceSymbol, typeReference, false));
                    }
                }

                var moduleAccess = TryGetModulePropertyAccess(propertyAccess);
                if (moduleAccess != null)
                {
                    var(moduleSymbol, outputName) = moduleAccess.Value;
                    return(AppendProperties(
                               GetModuleOutputsReferenceExpression(moduleSymbol),
                               new JTokenExpression(outputName),
                               new JTokenExpression("value")));
                }

                return(AppendProperties(
                           ToFunctionExpression(propertyAccess.BaseExpression),
                           new JTokenExpression(propertyAccess.PropertyName.IdentifierName)));

            case VariableAccessSyntax variableAccess:
                return(ConvertVariableAccess(variableAccess));

            default:
                throw new NotImplementedException($"Cannot emit unexpected expression of type {expression.GetType().Name}");
            }
        }
Example #10
0
        /// <summary>
        /// Converts the specified bicep expression tree into an ARM template expression tree.
        /// The returned tree may be rooted at either a function expression or jtoken expression.
        /// </summary>
        /// <param name="expression">The expression</param>
        public LanguageExpression ConvertExpression(SyntaxBase expression)
        {
            switch (expression)
            {
            case BooleanLiteralSyntax boolSyntax:
                return(CreateJsonFunctionCall(boolSyntax.Value));

            case NumericLiteralSyntax numericSyntax:
                return(new JTokenExpression(numericSyntax.Value));

            case StringSyntax stringSyntax:
                // using the throwing method to get semantic value of the string because
                // error checking should have caught any errors by now
                return(ConvertString(stringSyntax));

            case NullLiteralSyntax _:
                return(CreateJsonFunctionCall(JValue.CreateNull()));

            case ObjectSyntax _:
                return(ConvertComplexLiteral(expression));

            case ArraySyntax array:
                return(ConvertArray(array));

            case ParenthesizedExpressionSyntax parenthesized:
                // template expressions do not have operators so parentheses are irrelevant
                return(ConvertExpression(parenthesized.Expression));

            case UnaryOperationSyntax unary:
                return(ConvertUnary(unary));

            case BinaryOperationSyntax binary:
                return(ConvertBinary(binary));

            case TernaryOperationSyntax ternary:
                return(new FunctionExpression(
                           "if",
                           new[]
                {
                    ConvertExpression(ternary.ConditionExpression),
                    ConvertExpression(ternary.TrueExpression),
                    ConvertExpression(ternary.FalseExpression)
                },
                           Array.Empty <LanguageExpression>()));

            case FunctionCallSyntax function:
                return(ConvertFunction(
                           function.Name.IdentifierName,
                           function.Arguments.Select(a => ConvertExpression(a.Expression)).ToArray()));

            case ArrayAccessSyntax arrayAccess:
                return(AppendProperty(
                           ToFunctionExpression(arrayAccess.BaseExpression),
                           ConvertExpression(arrayAccess.IndexExpression)));

            case PropertyAccessSyntax propertyAccess:
                if (propertyAccess.BaseExpression is VariableAccessSyntax propVariableAccess &&
                    context.SemanticModel.GetSymbolInfo(propVariableAccess) is ResourceSymbol resourceSymbol)
                {
                    // special cases for certain resource property access. if we recurse normally, we'll end up
                    // generating statements like reference(resourceId(...)).id which are not accepted by ARM

                    var typeReference = EmitHelpers.GetTypeReference(resourceSymbol);
                    switch (propertyAccess.PropertyName.IdentifierName)
                    {
                    case "id":
                        return(GetResourceIdExpression(resourceSymbol.DeclaringResource, typeReference));

                    case "name":
                        return(GetResourceNameExpression(resourceSymbol.DeclaringResource));

                    case "type":
                        return(new JTokenExpression(typeReference.FullyQualifiedType));

                    case "apiVersion":
                        return(new JTokenExpression(typeReference.ApiVersion));

                    case "properties":
                        // use the reference() overload without "full" to generate a shorter expression
                        return(GetReferenceExpression(resourceSymbol.DeclaringResource, typeReference, false));
                    }
                }

                return(AppendProperty(
                           ToFunctionExpression(propertyAccess.BaseExpression),
                           new JTokenExpression(propertyAccess.PropertyName.IdentifierName)));

            case VariableAccessSyntax variableAccess:
                return(ConvertVariableAccess(variableAccess));

            default:
                throw new NotImplementedException($"Cannot emit unexpected expression of type {expression.GetType().Name}");
            }
        }