private SyntaxBase ParseModule()

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