private void EmitVariablesIfPresent(JsonTextWriter jsonWriter, ExpressionEmitter emitter) { if (!this.context.SemanticModel.Root.VariableDeclarations.Any(symbol => !this.context.VariablesToInline.Contains(symbol)) && this.context.FunctionVariables.Count == 0) { return; } jsonWriter.WritePropertyName("variables"); jsonWriter.WriteStartObject(); //emit internal variables foreach (var functionVariable in this.context.FunctionVariables.Values.OrderBy(x => x.Name, LanguageConstants.IdentifierComparer)) { jsonWriter.WritePropertyName(functionVariable.Name); emitter.EmitExpression(functionVariable.Value); } var variableLookup = this.context.SemanticModel.Root.VariableDeclarations.ToLookup(variableSymbol => variableSymbol.Value is ForSyntax); // local function IEnumerable <VariableSymbol> GetNonInlinedVariables(bool valueIsLoop) => variableLookup[valueIsLoop].Where(symbol => !this.context.VariablesToInline.Contains(symbol)); if (GetNonInlinedVariables(valueIsLoop: true).Any()) { // we have variables whose values are loops emitter.EmitProperty("copy", () => { jsonWriter.WriteStartArray(); foreach (var variableSymbol in GetNonInlinedVariables(valueIsLoop: true)) { // enforced by the lookup predicate above var @for = (ForSyntax)variableSymbol.Value; emitter.EmitCopyObject(variableSymbol.Name, @for, @for.Body); } jsonWriter.WriteEndArray(); }); } // emit non-loop variables foreach (var variableSymbol in GetNonInlinedVariables(valueIsLoop: false)) { jsonWriter.WritePropertyName(variableSymbol.Name); emitter.EmitExpression(variableSymbol.Value); } jsonWriter.WriteEndObject(); }
private static void EmitModuleParameters(JsonTextWriter jsonWriter, ModuleSymbol moduleSymbol, ExpressionEmitter emitter) { var paramsValue = moduleSymbol.TryGetBodyPropertyValue(LanguageConstants.ModuleParamsPropertyName); if (paramsValue is not ObjectSyntax paramsObjectSyntax) { // 'params' is optional if the module has no required params return; } jsonWriter.WritePropertyName("parameters"); jsonWriter.WriteStartObject(); foreach (var propertySyntax in paramsObjectSyntax.Properties) { if (!(propertySyntax.TryGetKeyText() is string keyName)) { // should have been caught by earlier validation throw new ArgumentException("Disallowed interpolation in module parameter"); } // we can't just call EmitObjectProperties here because the ObjectSyntax is flatter than the structure we're generating // because nested deployment parameters are objects with a single value property jsonWriter.WritePropertyName(keyName); jsonWriter.WriteStartObject(); if (propertySyntax.Value is ForSyntax @for) { // the value is a for-expression // write a single property copy loop emitter.EmitProperty("copy", () => { jsonWriter.WriteStartArray(); emitter.EmitCopyObject("value", @for, @for.Body, "value"); jsonWriter.WriteEndArray(); }); } else { // the value is not a for-expression - can emit normally emitter.EmitModuleParameterValue(propertySyntax.Value); } jsonWriter.WriteEndObject(); } jsonWriter.WriteEndObject(); }
private void EmitModuleParameters(JsonTextWriter jsonWriter, ModuleSymbol moduleSymbol, ExpressionEmitter emitter) { var paramsValue = moduleSymbol.TryGetBodyPropertyValue(LanguageConstants.ModuleParamsPropertyName); if (paramsValue is not ObjectSyntax paramsObjectSyntax) { // 'params' is optional if the module has no required params return; } jsonWriter.WritePropertyName("parameters"); jsonWriter.WriteStartObject(); foreach (var propertySyntax in paramsObjectSyntax.Properties) { if (!(propertySyntax.TryGetKeyText() is string keyName)) { // should have been caught by earlier validation throw new ArgumentException("Disallowed interpolation in module parameter"); } // we can't just call EmitObjectProperties here because the ObjectSyntax is flatter than the structure we're generating // because nested deployment parameters are objects with a single value property jsonWriter.WritePropertyName(keyName); jsonWriter.WriteStartObject(); if (propertySyntax.Value is ForSyntax @for) { // the value is a for-expression // write a single property copy loop emitter.EmitProperty("copy", () => { jsonWriter.WriteStartArray(); emitter.EmitCopyObject("value", @for, @for.Body, "value"); jsonWriter.WriteEndArray(); }); } else if ( this.context.SemanticModel.ResourceMetadata.TryLookup(propertySyntax.Value) is {} resourceMetadata&& moduleSymbol.TryGetModuleType() is ModuleType moduleType && moduleType.TryGetParameterType(keyName) is ResourceParameterType parameterType) { // This is a resource being passed into a module, we actually want to pass in its id // rather than the whole resource. emitter.EmitProperty("value", new PropertyAccessSyntax(propertySyntax.Value, SyntaxFactory.DotToken, SyntaxFactory.CreateIdentifier("id"))); }
private void EmitModule(JsonTextWriter jsonWriter, ModuleSymbol moduleSymbol, ExpressionEmitter emitter) { jsonWriter.WriteStartObject(); var body = moduleSymbol.DeclaringModule.Value; switch (body) { case IfConditionSyntax ifCondition: body = ifCondition.Body; emitter.EmitProperty("condition", ifCondition.ConditionExpression); break; case ForSyntax @for: if (@for.Body is IfConditionSyntax loopFilter) { body = loopFilter.Body; emitter.EmitProperty("condition", loopFilter.ConditionExpression); } else { body = @for.Body; } var batchSize = GetBatchSize(moduleSymbol.DeclaringModule); emitter.EmitProperty("copy", () => emitter.EmitCopyObject(moduleSymbol.Name, @for, input: null, batchSize: batchSize)); break; } emitter.EmitProperty("type", NestedDeploymentResourceType); emitter.EmitProperty("apiVersion", NestedDeploymentResourceApiVersion); // emit all properties apart from 'params'. In practice, this currrently only allows 'name', but we may choose to allow other top-level resource properties in future. // params requires special handling (see below). emitter.EmitObjectProperties((ObjectSyntax)body, ModulePropertiesToOmit); var scopeData = context.ModuleScopeData[moduleSymbol]; ScopeHelper.EmitModuleScopeProperties(context.SemanticModel.TargetScope, scopeData, emitter, body); if (scopeData.RequestedScope != ResourceScope.ResourceGroup) { // if we're deploying to a scope other than resource group, we need to supply a location if (this.context.SemanticModel.TargetScope == ResourceScope.ResourceGroup) { // the deployment() object at resource group scope does not contain a property named 'location', so we have to use resourceGroup().location emitter.EmitProperty("location", new FunctionExpression( "resourceGroup", Array.Empty <LanguageExpression>(), new LanguageExpression[] { new JTokenExpression("location") })); } else { // at all other scopes we can just use deployment().location emitter.EmitProperty("location", new FunctionExpression( "deployment", Array.Empty <LanguageExpression>(), new LanguageExpression[] { new JTokenExpression("location") })); } } jsonWriter.WritePropertyName("properties"); { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("expressionEvaluationOptions"); { jsonWriter.WriteStartObject(); emitter.EmitProperty("scope", "inner"); jsonWriter.WriteEndObject(); } emitter.EmitProperty("mode", "Incremental"); EmitModuleParameters(jsonWriter, moduleSymbol, emitter); var moduleSemanticModel = GetModuleSemanticModel(moduleSymbol); // If it is a template spec module, emit templateLink instead of template contents. jsonWriter.WritePropertyName(moduleSemanticModel is TemplateSpecSemanticModel ? "templateLink" : "template"); { TemplateWriterFactory.CreateTemplateWriter(moduleSemanticModel, this.settings).Write(jsonWriter); } jsonWriter.WriteEndObject(); } this.EmitDependsOn(jsonWriter, moduleSymbol, emitter, body); // Since we don't want to be mutating the body of the original ObjectSyntax, we create an placeholder body in place // and emit its properties to merge decorator properties. foreach (var(property, val) in AddDecoratorsToBody( moduleSymbol.DeclaringModule, SyntaxFactory.CreateObject(Enumerable.Empty <ObjectPropertySyntax>()), moduleSymbol.Type).ToNamedPropertyValueDictionary()) { emitter.EmitProperty(property, val); } jsonWriter.WriteEndObject(); }
private void EmitResource(JsonTextWriter jsonWriter, ResourceMetadata resource, ExpressionEmitter emitter) { jsonWriter.WriteStartObject(); // Note: conditions STACK with nesting. // // Children inherit the conditions of their parents, etc. This avoids a problem // where we emit a dependsOn to something that's not in the template, or not // being evaulated i the template. var conditions = new List <SyntaxBase>(); var loops = new List <(string name, ForSyntax @for, SyntaxBase?input)>(); var ancestors = this.context.SemanticModel.ResourceAncestors.GetAncestors(resource); foreach (var ancestor in ancestors) { if (ancestor.AncestorType == ResourceAncestorGraph.ResourceAncestorType.Nested && ancestor.Resource.Symbol.DeclaringResource.Value is IfConditionSyntax ifCondition) { conditions.Add(ifCondition.ConditionExpression); } if (ancestor.AncestorType == ResourceAncestorGraph.ResourceAncestorType.Nested && ancestor.Resource.Symbol.DeclaringResource.Value is ForSyntax @for) { loops.Add((ancestor.Resource.Symbol.Name, @for, null)); } } // Unwrap the 'real' resource body if there's a condition var body = resource.Symbol.DeclaringResource.Value; switch (body) { case IfConditionSyntax ifCondition: body = ifCondition.Body; conditions.Add(ifCondition.ConditionExpression); break; case ForSyntax @for: loops.Add((resource.Symbol.Name, @for, null)); if (@for.Body is IfConditionSyntax loopFilter) { body = loopFilter.Body; conditions.Add(loopFilter.ConditionExpression); } else { body = @for.Body; } break; } if (conditions.Count == 1) { emitter.EmitProperty("condition", conditions[0]); } else if (conditions.Count > 1) { var @operator = new BinaryOperationSyntax( conditions[0], SyntaxFactory.CreateToken(TokenType.LogicalAnd), conditions[1]); for (var i = 2; i < conditions.Count; i++) { @operator = new BinaryOperationSyntax( @operator, SyntaxFactory.CreateToken(TokenType.LogicalAnd), conditions[i]); } emitter.EmitProperty("condition", @operator); } if (loops.Count == 1) { var batchSize = GetBatchSize(resource.Symbol.DeclaringResource); emitter.EmitProperty("copy", () => emitter.EmitCopyObject(loops[0].name, loops[0].@for, loops[0].input, batchSize: batchSize)); } else if (loops.Count > 1) { throw new InvalidOperationException("nested loops are not supported"); } if (context.Settings.EnableSymbolicNames && resource.IsExistingResource) { jsonWriter.WritePropertyName("existing"); jsonWriter.WriteValue(true); } var importSymbol = context.SemanticModel.Root.ImportDeclarations.FirstOrDefault(i => resource.Type.DeclaringNamespace.AliasNameEquals(i.Name)); if (importSymbol is not null) { emitter.EmitProperty("import", importSymbol.Name); } if (resource.IsAzResource) { emitter.EmitProperty("type", resource.TypeReference.FormatType()); if (resource.TypeReference.ApiVersion is not null) { emitter.EmitProperty("apiVersion", resource.TypeReference.ApiVersion); } } else { emitter.EmitProperty("type", resource.TypeReference.FormatName()); } if (context.SemanticModel.EmitLimitationInfo.ResourceScopeData.TryGetValue(resource, out var scopeData)) { ScopeHelper.EmitResourceScopeProperties(context.SemanticModel, scopeData, emitter, body); } if (resource.IsAzResource) { emitter.EmitProperty(AzResourceTypeProvider.ResourceNamePropertyName, emitter.GetFullyQualifiedResourceName(resource)); emitter.EmitObjectProperties((ObjectSyntax)body, ResourcePropertiesToOmit.Add(AzResourceTypeProvider.ResourceNamePropertyName)); } else { jsonWriter.WritePropertyName("properties"); jsonWriter.WriteStartObject(); emitter.EmitObjectProperties((ObjectSyntax)body, ResourcePropertiesToOmit); jsonWriter.WriteEndObject(); } this.EmitDependsOn(jsonWriter, resource.Symbol, emitter, body); // Since we don't want to be mutating the body of the original ObjectSyntax, we create an placeholder body in place // and emit its properties to merge decorator properties. foreach (var(property, val) in AddDecoratorsToBody( resource.Symbol.DeclaringResource, SyntaxFactory.CreateObject(Enumerable.Empty <ObjectPropertySyntax>()), resource.Symbol.Type).ToNamedPropertyValueDictionary()) { emitter.EmitProperty(property, val); } jsonWriter.WriteEndObject(); }
private void EmitResource(JsonTextWriter jsonWriter, ResourceMetadata resource, ExpressionEmitter emitter) { jsonWriter.WriteStartObject(); // Note: conditions STACK with nesting. // // Children inherit the conditions of their parents, etc. This avoids a problem // where we emit a dependsOn to something that's not in the template, or not // being evaulated i the template. var conditions = new List <SyntaxBase>(); var loops = new List <(string name, ForSyntax @for, SyntaxBase?input)>(); var ancestors = this.context.SemanticModel.ResourceAncestors.GetAncestors(resource); foreach (var ancestor in ancestors) { if (ancestor.AncestorType == ResourceAncestorGraph.ResourceAncestorType.Nested && ancestor.Resource.Symbol.DeclaringResource.Value is IfConditionSyntax ifCondition) { conditions.Add(ifCondition.ConditionExpression); } if (ancestor.AncestorType == ResourceAncestorGraph.ResourceAncestorType.Nested && ancestor.Resource.Symbol.DeclaringResource.Value is ForSyntax @for) { loops.Add((ancestor.Resource.Symbol.Name, @for, null)); } } // Unwrap the 'real' resource body if there's a condition var body = resource.Symbol.DeclaringResource.Value; switch (body) { case IfConditionSyntax ifCondition: body = ifCondition.Body; conditions.Add(ifCondition.ConditionExpression); break; case ForSyntax @for: loops.Add((resource.Symbol.Name, @for, null)); if (@for.Body is IfConditionSyntax loopFilter) { body = loopFilter.Body; conditions.Add(loopFilter.ConditionExpression); } else { body = @for.Body; } break; } if (conditions.Count == 1) { emitter.EmitProperty("condition", conditions[0]); } else if (conditions.Count > 1) { var @operator = new BinaryOperationSyntax( conditions[0], SyntaxFactory.CreateToken(TokenType.LogicalAnd), conditions[1]); for (var i = 2; i < conditions.Count; i++) { @operator = new BinaryOperationSyntax( @operator, SyntaxFactory.CreateToken(TokenType.LogicalAnd), conditions[i]); } emitter.EmitProperty("condition", @operator); } if (loops.Count == 1) { var batchSize = GetBatchSize(resource.Symbol.DeclaringResource); emitter.EmitProperty("copy", () => emitter.EmitCopyObject(loops[0].name, loops[0].@for, loops[0].input, batchSize: batchSize)); } else if (loops.Count > 1) { throw new InvalidOperationException("nested loops are not supported"); } emitter.EmitProperty("type", resource.TypeReference.FullyQualifiedType); emitter.EmitProperty("apiVersion", resource.TypeReference.ApiVersion); if (context.SemanticModel.EmitLimitationInfo.ResourceScopeData.TryGetValue(resource, out var scopeData)) { ScopeHelper.EmitResourceScopeProperties(context.SemanticModel, scopeData, emitter, body); } emitter.EmitProperty("name", emitter.GetFullyQualifiedResourceName(resource)); if (context.Settings.EnableSymbolicNames && resource.IsExistingResource) { jsonWriter.WritePropertyName("existing"); jsonWriter.WriteValue(true); } body = AddDecoratorsToBody(resource.Symbol.DeclaringResource, (ObjectSyntax)body, resource.Type); emitter.EmitObjectProperties((ObjectSyntax)body, ResourcePropertiesToOmit); this.EmitDependsOn(jsonWriter, resource.Symbol, emitter, body); jsonWriter.WriteEndObject(); }