Skip to content

Commit

Permalink
Resource & property loop decompilation (#1640)
Browse files Browse the repository at this point in the history
* Hacky property & resource decompilation support

* Tidy-up

* Handle loop name dependencies

* Add some tests

* Address PR feedback
  • Loading branch information
anthony-c-martin authored Mar 4, 2021
1 parent 3773ab7 commit 6dba9a3
Show file tree
Hide file tree
Showing 16 changed files with 1,167 additions and 143 deletions.
92 changes: 76 additions & 16 deletions src/Bicep.Core/Syntax/SyntaxFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,39 @@ public static Token CreateToken(TokenType tokenType, string text = "")
public static IdentifierSyntax CreateIdentifier(string text)
=> new IdentifierSyntax(CreateToken(TokenType.Identifier, text));

public static Token NewlineToken
=> CreateToken(TokenType.NewLine, Environment.NewLine);
public static Token NewlineToken => CreateToken(TokenType.NewLine, Environment.NewLine);
public static Token AtToken => CreateToken(TokenType.At, "@");
public static Token LeftBraceToken => CreateToken(TokenType.LeftBrace, "{");
public static Token RightBraceToken => CreateToken(TokenType.RightBrace, "}");
public static Token LeftParenToken => CreateToken(TokenType.LeftParen, "(");
public static Token RightParenToken => CreateToken(TokenType.RightParen, ")");
public static Token LeftSquareToken => CreateToken(TokenType.LeftSquare, "[");
public static Token RightSquareToken => CreateToken(TokenType.RightSquare, "]");
public static Token CommaToken => CreateToken(TokenType.Comma, ",");
public static Token DotToken => CreateToken(TokenType.Dot, ".");
public static Token QuestionToken => CreateToken(TokenType.Question, "?");
public static Token ColonToken => CreateToken(TokenType.Colon, ":");
public static Token SemicolonToken => CreateToken(TokenType.Semicolon, ";");
public static Token AssignmentToken => CreateToken(TokenType.Assignment, "=");
public static Token PlusToken => CreateToken(TokenType.Plus, "+");
public static Token MinusToken => CreateToken(TokenType.Minus, "-");
public static Token AsteriskToken => CreateToken(TokenType.Asterisk, "*");
public static Token SlashToken => CreateToken(TokenType.Slash, "/");
public static Token ModuloToken => CreateToken(TokenType.Modulo, "%");
public static Token ExclamationToken => CreateToken(TokenType.Exclamation, "!");
public static Token LessThanToken => CreateToken(TokenType.LessThan, "<");
public static Token GreaterThanToken => CreateToken(TokenType.GreaterThan, ">");
public static Token LessThanOrEqualToken => CreateToken(TokenType.LessThanOrEqual, "<=");
public static Token GreaterThanOrEqualToken => CreateToken(TokenType.GreaterThanOrEqual, ">=");
public static Token EqualsToken => CreateToken(TokenType.Equals, "==");
public static Token NotEqualsToken => CreateToken(TokenType.NotEquals, "!=");
public static Token EqualsInsensitiveToken => CreateToken(TokenType.EqualsInsensitive, "=~");
public static Token NotEqualsInsensitiveToken => CreateToken(TokenType.NotEqualsInsensitive, "!~");
public static Token LogicalAndToken => CreateToken(TokenType.LogicalAnd, "&&");
public static Token LogicalOrToken => CreateToken(TokenType.LogicalOr, "||");
public static Token TrueKeywordToken => CreateToken(TokenType.TrueKeyword, "true");
public static Token FalseKeywordToken => CreateToken(TokenType.FalseKeyword, "false");
public static Token NullKeywordToken => CreateToken(TokenType.NullKeyword, "null");

public static ObjectPropertySyntax CreateObjectProperty(string key, SyntaxBase value)
=> new ObjectPropertySyntax(CreateObjectPropertyKey(key), CreateToken(TokenType.Colon, ":"), value);
Expand All @@ -38,14 +69,45 @@ public static ObjectSyntax CreateObject(IEnumerable<ObjectPropertySyntax> proper
}

return new ObjectSyntax(
CreateToken(TokenType.LeftBrace, "{"),
LeftBraceToken,
children,
CreateToken(TokenType.RightBrace, "}"));
RightBraceToken);
}

public static ArrayItemSyntax CreateArrayItem(SyntaxBase value)
=> new ArrayItemSyntax(value);

public static ForSyntax CreateRangedForSyntax(string indexIdentifier, SyntaxBase count, SyntaxBase body)
{
// generates "range(0, <count>)"
var rangeSyntax = new FunctionCallSyntax(
CreateIdentifier("range"),
LeftParenToken,
new FunctionArgumentSyntax[] {
new FunctionArgumentSyntax(
new IntegerLiteralSyntax(CreateToken(TokenType.Integer, "0"), 0),
CommaToken),
new FunctionArgumentSyntax(count, null),
},
RightParenToken);

return CreateForSyntax(indexIdentifier, rangeSyntax, body);
}

public static ForSyntax CreateForSyntax(string indexIdentifier, SyntaxBase inSyntax, SyntaxBase body)
{
// generates "[for <identifier> in <inSyntax>: <body>]"
return new(
LeftSquareToken,
CreateToken(TokenType.Identifier, "for"),
new LocalVariableSyntax(new IdentifierSyntax(CreateToken(TokenType.Identifier, indexIdentifier))),
CreateToken(TokenType.Identifier, "in"),
inSyntax,
ColonToken,
body,
RightSquareToken);
}

public static ArraySyntax CreateArray(IEnumerable<SyntaxBase> items)
{
var children = new List<SyntaxBase> { NewlineToken };
Expand All @@ -57,9 +119,9 @@ public static ArraySyntax CreateArray(IEnumerable<SyntaxBase> items)
}

return new ArraySyntax(
CreateToken(TokenType.LeftSquare, "["),
LeftSquareToken,
children,
CreateToken(TokenType.RightSquare, "]"));
RightSquareToken);
}

public static SyntaxBase CreateObjectPropertyKey(string text)
Expand Down Expand Up @@ -116,24 +178,22 @@ public static Token CreateStringInterpolationToken(bool isStart, bool isEnd, str
return CreateToken(TokenType.StringMiddlePiece, $"}}{EscapeBicepString(value)}${{");
}

public static FunctionCallSyntax CreateFunctionCall(string functionName, IEnumerable<SyntaxBase> argumentExpressions)
public static FunctionCallSyntax CreateFunctionCall(string functionName, params SyntaxBase[] argumentExpressions)
{
var arguments = argumentExpressions.Any()
? argumentExpressions.SkipLast(1)
.Select(expression => new FunctionArgumentSyntax(expression, CreateToken(TokenType.Comma, ",")))
.Append(new FunctionArgumentSyntax(argumentExpressions.Last(), null))
: Enumerable.Empty<FunctionArgumentSyntax>();
var arguments = argumentExpressions.Select((expression, i) => new FunctionArgumentSyntax(
expression,
i < argumentExpressions.Length - 1 ? CommaToken : null));

return new FunctionCallSyntax(
CreateIdentifier(functionName),
CreateToken(TokenType.LeftParen, "("),
LeftParenToken,
arguments,
CreateToken(TokenType.RightParen, ")"));
RightParenToken);
}

public static DecoratorSyntax CreateDecorator(string functionName, IEnumerable<SyntaxBase> argumentExpressions)
public static DecoratorSyntax CreateDecorator(string functionName, params SyntaxBase[] argumentExpressions)
{
return new DecoratorSyntax(CreateToken(TokenType.At, "@"), CreateFunctionCall(functionName, argumentExpressions));
return new DecoratorSyntax(AtToken, CreateFunctionCall(functionName, argumentExpressions));
}

private static string EscapeBicepString(string value)
Expand Down
4 changes: 3 additions & 1 deletion src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,11 @@ [new Uri($"file:///{resourcePath}")] = jsonContents,
}

[DataTestMethod]
[DataRow("NonWorking/copyloop.json", "[11:9]: The 'copy' property is not supported")]
[DataRow("NonWorking/unknownprops.json", "[15:29]: Unrecognized top-level resource property 'madeUpProperty'")]
[DataRow("NonWorking/nested-outer.json", "[11:23]: Nested template decompilation requires 'inner' expression evaluation scope. See 'https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates#expression-evaluation-scope-in-nested-templates' for more information Microsoft.Resources/deployments pid-00000000-0000-0000-0000-000000000000")]
[DataRow("NonWorking/condition-loop.json", "[14:9]: The 'copy' property is not supported in conjunction with the 'condition' property")]
[DataRow("NonWorking/invalid-schema.json", "[2:98]: $schema value \"https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#\" did not match any of the known ARM template deployment schemas.")]
[DataRow("NonWorking/batchsize.json", "[24:21]: The \"mode\" property is not currently supported")]
public void Decompiler_raises_errors_for_unsupported_features(string resourcePath, string expectedMessage)
{
Action onDecompile = () => {
Expand Down
33 changes: 33 additions & 0 deletions src/Bicep.Decompiler.IntegrationTests/NonWorking/batchsize.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageCount": {
"type": "int",
"defaultValue": 2
},
"storagePrefix": {
"type": "string"
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-04-01",
"name": "[tolower(concat(copyIndex(), parameters('storagePrefix'), uniqueString(resourceGroup().id)))]",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {},
"copy": {
"name": "storagecopy",
"count": "[parameters('storageCount')]",
"batchSize": 1,
"mode": "Serial"
}
}
],
"outputs": {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
"storageCount": {
"type": "int",
"defaultValue": 2
},
"storagePrefix": {
"type": "string"
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-04-01",
"name": "[concat(copyIndex(),'storage', uniqueString(resourceGroup().id))]",
"name": "[tolower(concat(copyIndex(), parameters('storagePrefix'), uniqueString(resourceGroup().id)))]",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard_LRS"
Expand All @@ -21,7 +24,8 @@
"copy": {
"name": "storagecopy",
"count": "[parameters('storageCount')]"
}
},
"condition": "[equals(copyIndex(), 1)]"
}
],
"outputs": {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vmSku": {
"value": "Standard_A1"
},
"vmssName": {
"value": "GEN-UNIQUE-32"
},
"instanceCount": {
"value": 3
},
"adminUsername": {
"value": "GEN-UNIQUE"
},
"adminPasswordOrKey": {
"value": "GEN-SSH-PUB-KEY"
}
}
}
Loading

0 comments on commit 6dba9a3

Please sign in to comment.