Skip to content

Commit

Permalink
Support decompiling templates with extensions that are not .json (#2201)
Browse files Browse the repository at this point in the history
* Support decompile templates with extensions that are not .json

* Address comment

* Fix test errors

* Fix an error and handle a special case

* Simplify include paths

* Avoid decompiling nested example templates twice
  • Loading branch information
shenglol authored Apr 14, 2021
1 parent 95ebbb0 commit eded2cf
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 135 deletions.
31 changes: 30 additions & 1 deletion src/Bicep.Core/FileSystem/PathHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public static class PathHelper

private const string TemplateOutputExtension = ".json";

private const string BicepExtension = ".bicep";

public static StringComparer PathComparer => IsFileSystemCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;

public static StringComparison PathComparison => IsFileSystemCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
Expand Down Expand Up @@ -85,5 +87,32 @@ public static Uri FilePathToFileUrl(string filePath)

return uriBuilder.Uri;
}

public static Uri ChangeExtension(Uri uri, string? newExtension)
{
var uriString = uri.ToString();
var finalDotIndex = uriString.LastIndexOf('.');

newExtension = newExtension is null ? "" : NormalizeExtension(newExtension);
uriString = (finalDotIndex >= 0 ? uriString.Substring(0, finalDotIndex) : uriString) + newExtension;

return new Uri(uriString);
}

public static bool HasExtension(Uri uri, string extension)
{
extension = NormalizeExtension(extension);

return uri.AbsolutePath.EndsWith(extension, StringComparison.OrdinalIgnoreCase);
}

public static Uri RemoveExtension(Uri uri) => ChangeExtension(uri, null);

public static Uri ChangeToBicepExtension(Uri uri) => ChangeExtension(uri, BicepExtension);

public static bool HasBicepExtension(Uri uri) => HasExtension(uri, BicepExtension);

private static string NormalizeExtension(string extension) =>
extension.StartsWith(".") ? extension : $".{extension}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,13 @@
</PropertyGroup>

<ItemGroup>
<None Remove="./Working/**/*.json" />
<None Remove="./Working/**/*.bicep" />
<None Remove="./NonWorking/**/*.json" />
<None Remove="./NonWorking/**/*.bicep" />
<None Remove="./Working/**/*.*" />
<None Remove="./NonWorking/**/*.*" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="./Working/**/*.json" LogicalName="$([System.String]::new('Working/%(RecursiveDir)%(Filename)%(Extension)').Replace('\', '/'))" />
<EmbeddedResource Include="./Working/**/*.bicep" LogicalName="$([System.String]::new('Working/%(RecursiveDir)%(Filename)%(Extension)').Replace('\', '/'))" />
<EmbeddedResource Include="./NonWorking/**/*.json" LogicalName="$([System.String]::new('NonWorking/%(RecursiveDir)%(Filename)%(Extension)').Replace('\', '/'))" />
<EmbeddedResource Include="./NonWorking/**/*.bicep" LogicalName="$([System.String]::new('NonWorking/%(RecursiveDir)%(Filename)%(Extension)').Replace('\', '/'))" />
<EmbeddedResource Include="./Working/**/*.*" LogicalName="$([System.String]::new('Working/%(RecursiveDir)%(Filename)%(Extension)').Replace('\', '/'))" />
<EmbeddedResource Include="./NonWorking/**/*.*" LogicalName="$([System.String]::new('NonWorking/%(RecursiveDir)%(Filename)%(Extension)').Replace('\', '/'))" />
</ItemGroup>

<ItemGroup>
Expand Down
47 changes: 35 additions & 12 deletions src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,22 @@ public ExampleData(string bicepStreamName, string jsonStreamName, string outputF
private static IEnumerable<object[]> GetWorkingExampleData()
{
const string pathPrefix = "Working/";
const string jsonExtension = ".json";
const string bicepExtension = ".bicep";

foreach (var streamName in typeof(DecompilationTests).Assembly.GetManifestResourceNames().Where(p => p.StartsWith(pathPrefix, StringComparison.Ordinal)))
// Only return files whose path segment length is 3 as entry files to avoid decompiling nested templates twice.
var entryStreamNames = typeof(DecompilationTests).Assembly.GetManifestResourceNames()
.Where(p => p.StartsWith(pathPrefix, StringComparison.Ordinal) && p.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Length == 3);

foreach (var streamName in entryStreamNames)
{
var extension = Path.GetExtension(streamName);
if (!StringComparer.OrdinalIgnoreCase.Equals(extension, jsonExtension))
if (StringComparer.OrdinalIgnoreCase.Equals(extension, bicepExtension))
{
continue;
}

var outputFolderName = streamName
.Substring(0, streamName.Length - jsonExtension.Length)
.Substring(pathPrefix.Length)
.Replace('/', '_');

var exampleData = new ExampleData(streamName, Path.ChangeExtension(streamName, "json"), outputFolderName);
var outputFolderName = streamName[pathPrefix.Length..^extension.Length].Replace('/', '_');
var exampleData = new ExampleData(Path.ChangeExtension(streamName, bicepExtension), streamName, outputFolderName);

yield return new object[] { exampleData };
}
Expand All @@ -84,7 +84,6 @@ public void Decompiler_generates_expected_bicep_files_with_diagnostics(ExampleDa
// save all the files in the containing directory to disk so that we can test module resolution
var parentStream = Path.GetDirectoryName(example.BicepStreamName)!.Replace('\\', '/');
var outputDirectory = FileHelper.SaveEmbeddedResourcesWithPathPrefix(TestContext, typeof(DecompilationTests).Assembly, parentStream);
var bicepFileName = Path.Combine(outputDirectory, Path.GetFileName(example.BicepStreamName));
var jsonFileName = Path.Combine(outputDirectory, Path.GetFileName(example.JsonStreamName));
var typeProvider = AzResourceTypeProvider.CreateWithAzTypes();

Expand Down Expand Up @@ -112,7 +111,7 @@ public void Decompiler_generates_expected_bicep_files_with_diagnostics(ExampleDa
File.WriteAllText(syntaxTree.FileUri.LocalPath + ".actual", sourceTextWithDiags);

sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput(
TestContext,
TestContext,
exampleExists ? File.ReadAllText(syntaxTree.FileUri.LocalPath) : "",
expectedLocation: Path.Combine("src", "Bicep.Decompiler.IntegrationTests", parentStream, Path.GetRelativePath(outputDirectory, syntaxTree.FileUri.LocalPath)),
actualLocation: syntaxTree.FileUri.LocalPath + ".actual");
Expand Down Expand Up @@ -226,5 +225,29 @@ public void Decompiler_handles_banned_function_replacement(string expression, st

filesToSave[entryPointUri].Should().Contain($"output calculated {type} = ({expectedValue})");
}

[TestMethod]
public void Decompiler_should_not_decompile_bicep_extension()
{
const string template = @"{
""$schema"": ""https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"",
""contentVersion"": ""1.0.0.0"",
""parameters"": {},
""variables"": {},
""resources"": [],
""outputs"": {}
}";

var fileUri = new Uri("file:///path/to/main.bicep");
var fileResolver = new InMemoryFileResolver(new Dictionary<Uri, string>
{
[fileUri] = template,
});

Action sut = () => TemplateDecompiler.DecompileFileWithModules(TestTypeHelper.CreateEmptyProvider(), fileResolver, fileUri);

sut.Should().Throw<InvalidOperationException>()
.WithMessage("Cannot decompile the file with .bicep extension: file:///path/to/main.bicep.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"abc",
"[parameters('location')]"
],
"boolVar": true
},
"resources": [
{
Expand All @@ -48,6 +49,29 @@
}
}
},
{
"name": "module1ArmDeploy",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2020-10-01",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[concat(variables('armBaseUrl'),'/nested/module1.arm')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"stringParam": {
"value": "[parameters('location')]"
},
"objectParam": {
"value": "[variables('objectVar')]"
},
"boolParam": {
"value": "[variables('boolVar')]"
}
}
}
},
{
"name": "module2Deploy",
"type": "Microsoft.Resources/deployments",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
param location string = resourceGroup().location

@description('Base URL for the reference templates and scripts')
param baseUrl string = 'https://my.base/url'

var armBaseUrl = baseUrl
var objectVar = {
val1: 'a${location}b'
}
var arrayVar = [
'abc'
location
]

module module1Deploy 'nested/module1.bicep' = {
name: 'module1Deploy'
params: {
stringParam: location
objectParam: objectVar
arrayParam: arrayVar
}
}

module module2Deploy 'nested/module2.bicep' = {
name: 'module2Deploy'
params: {
stringParam: location
objectParam: objectVar
arrayParam: arrayVar
}
}
param location string = resourceGroup().location

@description('Base URL for the reference templates and scripts')
param baseUrl string = 'https://my.base/url'

var armBaseUrl = baseUrl
var objectVar = {
val1: 'a${location}b'
}
var arrayVar = [
'abc'
location
]
var boolVar = true

module module1Deploy 'nested/module1.bicep' = {
name: 'module1Deploy'
params: {
stringParam: location
objectParam: objectVar
arrayParam: arrayVar
}
}

module module1ArmDeploy 'nested/module1.arm.bicep' = {
name: 'module1ArmDeploy'
params: {
stringParam: location
objectParam: objectVar
boolParam: boolVar
}
}

module module2Deploy 'nested/module2.bicep' = {
name: 'module2Deploy'
params: {
stringParam: location
objectParam: objectVar
arrayParam: arrayVar
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"stringParam": {
"type": "string"
},
"objectParam": {
"type": "object"
},
"boolParam": {
"type": "bool"
}
},
"variables": {},
"resources": [],
"outputs": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
param stringParam string
param objectParam object
param boolParam bool
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ param baseUrl string = 'https://my.base/url'

var armBaseUrl = baseUrl
var module1Url = '${armBaseUrl}/nested/module1.json'
var module2Url = '${armBaseUrl}/nested/module2.json'
var module2Url = '${armBaseUrl}/nested/module2.jsonc'
var objectVar = {
val1: 'a${location}b'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"variables": {
"armBaseUrl": "[parameters('baseUrl')]",
"module1Url": "[concat(variables('armBaseUrl'),'/nested/module1.json')]",
"module2Url": "[concat(variables('armBaseUrl'),'/nested/module2.json')]",
"module2Url": "[concat(variables('armBaseUrl'),'/nested/module2.jsonc')]",
"objectVar": {
"val1": "[concat('a', parameters('location'), 'b')]"
},
Expand Down
Loading

0 comments on commit eded2cf

Please sign in to comment.