Skip to content

Commit

Permalink
Fixes quotes get incorrectly duplicated #2593 (#2612)
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored Dec 16, 2023
1 parent da9632b commit 4533f73
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 7 deletions.
6 changes: 6 additions & 0 deletions docs/CHANGELOG-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers

## Unreleased

What's changed since v1.32.0:

- Bug fixes:
- Fixed quotes get incorrectly duplicated by @BernieWhite.
[#2593](https://github.com/Azure/PSRule.Rules.Azure/issues/2593)

## v1.32.0

What's changed since v1.31.3:
Expand Down
2 changes: 1 addition & 1 deletion src/PSRule.Rules.Azure/Data/Template/ExpressionParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Threading;
Expand Down
34 changes: 29 additions & 5 deletions src/PSRule.Rules.Azure/Data/Template/ExpressionStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -140,7 +141,7 @@ public bool CaptureString(out string s)
Next();
SkipQuotePairs(ref length);
}
s = Substring(start, length);
s = Substring(start, length, ignoreDoubleQuotes: false);
IsString();
return true;
}
Expand Down Expand Up @@ -215,7 +216,7 @@ private void SkipQuotePairs(ref int length)
{
while (!IsEscaped && Apostrophe == Current && Offset(1, out var offset) && offset == Apostrophe)
{
length += 2;
length += 1;
Next();
Next();
}
Expand All @@ -240,20 +241,40 @@ private void UpdateCurrent(bool ignoreEscaping = false)
_Current = _Source[_Position + _EscapeLength];
}

/// <summary>
/// Count excape is used to offset a position if an escape sequence exists.
/// </summary>
private int GetEscapeCount(int position)
{
// Check for escape sequences
// Check for escape sequences.
if (position < _Length && _Source[position] == Backslash)
{
var next = _Source[position + 1];

// Check against list of escapable characters
// Check against list of escapable characters.
if (next is Backslash or BracketOpen or ParenthesesOpen or BracketClose or ParenthesesClose)
return 1;
}
return 0;
}

/// <summary>
/// Count dobule quote is used to offset a postion if double quoting exists with an apostrophe.
/// </summary>
private int GetDoubleQuoteCount(int position)
{
// Check for apostrophe quote character.
if (position < _Length && _Source[position] == Apostrophe)
{
var next = _Source[position + 1];

// Check for secondary apostrophe quote character.
if (next is Apostrophe)
return 1;
}
return 0;
}

public string CaptureUntil(char[] c, bool ignoreEscaping = false)
{
var start = Position;
Expand All @@ -269,7 +290,7 @@ public string CaptureUntil(char[] c, bool ignoreEscaping = false)
return Substring(start, length, ignoreEscaping);
}

private string Substring(int start, int length, bool ignoreEscaping = false)
private string Substring(int start, int length, bool ignoreEscaping = false, bool ignoreDoubleQuotes = true)
{
if (ignoreEscaping)
return _Source.Substring(start, length);
Expand All @@ -280,6 +301,9 @@ private string Substring(int start, int length, bool ignoreEscaping = false)
while (i < length)
{
var offset = GetEscapeCount(position);
if (!ignoreDoubleQuotes && offset == 0)
offset = GetDoubleQuoteCount(position);

buffer[i] = _Source[position + offset];
position += offset + 1;
i++;
Expand Down
2 changes: 1 addition & 1 deletion tests/PSRule.Rules.Azure.Tests/ExpressionBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public void JsonQuoteNesting()
var context = GetContext();
var actual = Build(context, expression) as JObject;
Assert.NotNull(actual);
Assert.Equal("[parameters(''effect'')]", actual["effect"].Value<string>());
Assert.Equal("[parameters('effect')]", actual["effect"].Value<string>());

expression = "[json('{ \"value\": \"[int(last(split(replace(field(''test''), ''t'', ''''), ''/'')))]\" }')]";
actual = Build(context, expression) as JObject;
Expand Down
26 changes: 26 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/ExpressionParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,31 @@ public void ParseExpressionWithStringIndexProperty()
Assert.Equal("item1", actual[14].Content);
Assert.Equal(ExpressionTokenType.IndexEnd, actual[15].Type);
}

[Fact]
public void ParseQuoting()
{
var expression = "[format('A''{0}''', string(variables('task').parameters))]";
var actual = ExpressionParser.Parse(expression).ToArray();

Assert.Equal(ExpressionTokenType.Element, actual[0].Type); // format
Assert.Equal("format", actual[0].Content);
Assert.Equal(ExpressionTokenType.GroupStart, actual[1].Type);
Assert.Equal(ExpressionTokenType.String, actual[2].Type); // 'A'{0}''
Assert.Equal("A'{0}'", actual[2].Content);
Assert.Equal(ExpressionTokenType.Element, actual[3].Type); // string
Assert.Equal("string", actual[3].Content);
Assert.Equal(ExpressionTokenType.GroupStart, actual[4].Type);
Assert.Equal(ExpressionTokenType.Element, actual[5].Type); // variables
Assert.Equal("variables", actual[5].Content);
Assert.Equal(ExpressionTokenType.GroupStart, actual[6].Type);
Assert.Equal(ExpressionTokenType.String, actual[7].Type); // 'task'
Assert.Equal("task", actual[7].Content);
Assert.Equal(ExpressionTokenType.GroupEnd, actual[8].Type);
Assert.Equal(ExpressionTokenType.Property, actual[9].Type); // parameters
Assert.Equal("parameters", actual[9].Content);
Assert.Equal(ExpressionTokenType.GroupEnd, actual[10].Type);
Assert.Equal(ExpressionTokenType.GroupEnd, actual[11].Type);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@
<None Update="Tests.Bicep.32.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Tests.Bicep.33.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Tests.Bicep.4.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
13 changes: 13 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,19 @@ public void DependencyOrdering()
Assert.Equal("dep_subnet4_2", actual["properties"]["tags"]["deployment"].Value<string>());
}

[Fact]
public void Quoting()
{
var resources = ProcessTemplate(GetSourcePath("Tests.Bicep.33.json"), null, out var templateContext);
Assert.Equal(3, resources.Length);

Assert.True(templateContext.RootDeployment.TryOutput("outTask", out JObject result));
Assert.Equal(5, result["value"]["parameters"]["B"].Value<int>());
Assert.True(templateContext.RootDeployment.TryOutput("outTasks", out result));
Assert.Equal("A'{\"B\":5}'", result["value"][0]["parameters"]["debug"].Value<string>());
Assert.Equal(10, result["value"][0]["parameters"]["debugLength"].Value<int>());
}

#region Helper methods

private static string GetSourcePath(string fileName)
Expand Down
46 changes: 46 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/Tests.Bicep.33.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// Community provided sample from: https://github.com/Azure/PSRule.Rules.Azure/issues/2593

var task = {
name: 'task1'
parameters: {
B: 5
}
}

var tasks = [
{
name: task.name
parameters: {
debug: 'A\'${string(task.parameters)}\''
debugLength: length('A\'${string(task.parameters)}\'')
}
}
]

#disable-next-line BCP081 no-deployments-resources
resource taskDeployment 'Microsoft.Resources/deployments@2020-10-01' = {
name: 'name'
properties: {
mode: 'Incremental'
template: {
'$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#'
contentVersion: '1.0.0.0'
resources: [
{
apiVersion: '2019-12-01'
type: 'Microsoft.ManagedIdentity/userAssignedIdentities'
name: 'test'
properties: {
tasks: tasks
}
}
]
}
}
}

output outTask object = task
output outTasks array = tasks
62 changes: 62 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/Tests.Bicep.33.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.24.24.22086",
"templateHash": "9157129407835921175"
}
},
"variables": {
"task": {
"name": "task1",
"parameters": {
"B": 5
}
},
"tasks": [
{
"name": "[variables('task').name]",
"parameters": {
"debug": "[format('A''{0}''', string(variables('task').parameters))]",
"debugLength": "[length(format('A''{0}''', string(variables('task').parameters)))]"
}
}
]
},
"resources": [
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2020-10-01",
"name": "name",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"apiVersion": "2019-12-01",
"type": "Microsoft.ManagedIdentity/userAssignedIdentities",
"name": "test",
"properties": {
"tasks": "[variables('tasks')]"
}
}
]
}
}
}
],
"outputs": {
"outTask": {
"type": "object",
"value": "[variables('task')]"
},
"outTasks": {
"type": "array",
"value": "[variables('tasks')]"
}
}
}

0 comments on commit 4533f73

Please sign in to comment.