in src/Bicep.Decompiler/TemplateConverter.cs [1341:1512]
private SyntaxBase ParseModule(IReadOnlyDictionary<string, string> copyResourceLookup, JObject resource, string typeString, string nameString)
{
var expectedProps = new HashSet<string>(new[] {
"name",
"type",
"apiVersion",
"location",
"properties",
"dependsOn",
"comments",
"metadata",
}, StringComparer.OrdinalIgnoreCase);
var propsToOmit = new HashSet<string>(new[] {
"condition",
LanguageConstants.CopyLoopIdentifier,
"resourceGroup",
"subscriptionId",
"metadata",
}, StringComparer.OrdinalIgnoreCase);
TemplateHelpers.AssertUnsupportedProperty(resource, "scope", "The 'scope' property is not supported");
foreach (var prop in resource.Properties())
{
if (propsToOmit.Contains(prop.Name))
{
continue;
}
if (!expectedProps.Contains(prop.Name))
{
throw new ConversionFailedException($"Unrecognized top-level resource property '{prop.Name}'", prop);
}
}
var identifier = nameResolver.TryLookupResourceName(typeString, ExpressionHelpers.ParseExpression(nameString)) ?? throw new ArgumentException($"Unable to find resource {typeString} {nameString}");
var nestedProperties = TemplateHelpers.GetNestedProperty(resource, "properties");
var nestedTemplate = TemplateHelpers.GetNestedProperty(resource, "properties", "template");
if (nestedProperties is not null && nestedTemplate is not null)
{
if (nestedTemplate is not JObject nestedTemplateObject)
{
throw new ConversionFailedException($"Expected template object for {typeString} {nameString}", nestedTemplate);
}
var expressionEvaluationScope = TemplateHelpers.GetNestedProperty(resource, "properties", "expressionEvaluationOptions", "scope")?.ToString();
if (!StringComparer.OrdinalIgnoreCase.Equals(expressionEvaluationScope, "inner"))
{
if (TemplateHelpers.GetNestedProperty(nestedTemplateObject, "parameters") is { } existingParameters &&
existingParameters.Children().Any())
{
throw new ConversionFailedException($"Outer-scoped nested templates cannot contain parameters", existingParameters);
}
var (rewrittenTemplate, parameters) = TemplateHelpers.ConvertNestedTemplateInnerToOuter(nestedTemplateObject);
if (TemplateHelpers.GetNestedProperty(rewrittenTemplate, "parameters") is not JObject rewrittenParameters)
{
rewrittenParameters = new JObject();
rewrittenTemplate["parameters"] = rewrittenParameters;
}
foreach (var parameter in parameters.Keys)
{
if (TemplateHelpers.GetNestedProperty(template, "parameters", parameter) is { } parentTemplateParam &&
parentTemplateParam.DeepClone() is JObject nestedParam)
{
rewrittenParameters[parameter] = nestedParam;
TemplateHelpers.RemoveNestedProperty(nestedParam, "defaultValue");
}
else
{
rewrittenParameters[parameter] = new JObject
{
["type"] = parameters[parameter].type,
};
}
}
nestedTemplateObject = rewrittenTemplate;
nestedProperties["template"] = rewrittenTemplate;
nestedProperties["parameters"] = new JObject(parameters.Select(x => new JProperty(
x.Key,
new JObject
{
["value"] = ExpressionsEngine.SerializeExpression(x.Value.expression),
})));
}
// Metadata/description should be first
var decoratorsAndNewLines = ProcessMetadataDescription(name => resource[name]).ToList();
var (nestedBody, resourceCopyDecorators) = ProcessResourceCopy(resource, x => ProcessModuleBody(copyResourceLookup, x));
decoratorsAndNewLines.AddRange(resourceCopyDecorators);
var nestedValue = ProcessCondition(resource, nestedBody);
var filePath = $"./nested_{identifier}.bicep";
if (!Uri.TryCreate(bicepFileUri, filePath, out var nestedModuleUri))
{
throw new ConversionFailedException($"Failed to create module uri for {typeString} {nameString}", nestedTemplate);
}
if (workspace.TryGetSourceFile(nestedModuleUri, out _))
{
throw new ConversionFailedException($"Unable to generate duplicate module to path ${nestedModuleUri} for {typeString} {nameString}", nestedTemplate);
}
var nestedOptions = this.options with { AllowMissingParamsAndVars = this.options.AllowMissingParamsAndVarsInNestedTemplates };
var nestedConverter = new TemplateConverter(this.sourceFileFactory, workspace, nestedModuleUri, nestedTemplateObject, this.jsonTemplateUrisByModule, nestedOptions);
var nestedBicepFile = this.sourceFileFactory.CreateBicepFile(nestedModuleUri, nestedConverter.Parse().ToString());
workspace.UpsertSourceFile(nestedBicepFile);
return new ModuleDeclarationSyntax(
decoratorsAndNewLines,
SyntaxFactory.ModuleKeywordToken,
SyntaxFactory.CreateIdentifierWithTrailingSpace(identifier),
SyntaxFactory.CreateStringLiteral(filePath),
SyntaxFactory.AssignmentToken,
[],
nestedValue);
}
else
{
var pathProperty = TemplateHelpers.GetNestedProperty(resource, "properties", "templateLink", "uri")
?? TemplateHelpers.GetNestedProperty(resource, "properties", "templateLink", "relativePath");
var idProperty = TemplateHelpers.GetNestedProperty(resource, "properties", "templateLink", "id");
// Metadata/description should be first
var decoratorsAndNewLines = ProcessMetadataDescription(name => resource[name]).ToList();
var (body, resourceCopyDecorators) = ProcessResourceCopy(resource, x => ProcessModuleBody(copyResourceLookup, x));
decoratorsAndNewLines.AddRange(resourceCopyDecorators);
var value = ProcessCondition(resource, body);
// fetch the module templatespec if the id property set else fetch the module path
SyntaxBase modulePath;
Uri? jsonTemplateUri = null;
if (pathProperty?.Value<string>() is string templatePathString)
{
(modulePath, jsonTemplateUri) = GetModuleFilePath(templatePathString);
}
else if (idProperty?.Value<string>() is string templateSpecIdString)
{
modulePath = GetModuleFromId(templateSpecIdString, resource);
}
else
{
throw new ConversionFailedException($"Unable to find \"uri\" or \"relativePath\" or \"id\" properties under {resource["name"]}.properties.templateLink for linked template.", resource);
}
var module = new ModuleDeclarationSyntax(
decoratorsAndNewLines,
SyntaxFactory.ModuleKeywordToken,
SyntaxFactory.CreateIdentifierWithTrailingSpace(identifier),
modulePath,
SyntaxFactory.AssignmentToken,
[],
value);
/*
* We need to save jsonTemplateUri because it may not necessarily end with .json extension.
* When decompiling the module, jsonTemplateUri will be used to load the JSON template file.
*/
if (jsonTemplateUri is not null)
{
this.jsonTemplateUrisByModule[module] = jsonTemplateUri;
}
return module;
}
}