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(); }
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(); }
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."); } }
public LanguageExpression GetFullyQualifiedResourceId(ResourceSymbol resourceSymbol) { var typeReference = EmitHelpers.GetTypeReference(resourceSymbol); return(ScopeHelper.FormatFullyQualifiedResourceId( context, this, context.ResourceScopeData[resourceSymbol], typeReference.FullyQualifiedType, GetResourceNameSegments(resourceSymbol, typeReference))); }
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)); }
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)); }
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(); }
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(); }
/// <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}"); } }
/// <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}"); } }