Skip to content

Commit

Permalink
Include constraints (decorators) for parameters (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimdalen authored Jan 8, 2023
1 parent 769b269 commit 030e2f8
Show file tree
Hide file tree
Showing 17 changed files with 265 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: Build BicepDocs
run: dotnet build --configuration release src/BicepDocs.sln
- name: Run tests
run: dotnet test --configuration release ./src/**/*Tests.csproj
run: dotnet test --configuration release src/BicepDocs.sln
- id: get-version
run: |
parsedVersion=$(echo ${{ github.ref }} | cut -dv -f2)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Build BicepDocs
run: dotnet build --configuration release src/BicepDocs.sln
- name: Run tests
run: dotnet test --configuration release ./src/**/*Tests.csproj
run: dotnet test --configuration release src/BicepDocs.sln
- name: Build binary
run: dotnet publish --configuration release --self-contained true -p:PublishTrimmed=true -p:PublishSingleFile=true -p:TrimmerDefaultAction=copyused -p:SuppressTrimAnalysisWarnings=true -r ${{ matrix.rid }} ./src/BicepDocs.Cli/BicepDocs.Cli.csproj

Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module exampleInstance 'ts/IacModules:resources/resource-groups:2022-12-17' = {
| `complexObject` | Object input | object | [complexObjectValue](#complexobjectvalue) |
| `inputArray` | Complex array input | array | [inputArrayValue](#inputarrayvalue) |
| `inputArraySimple` | Simple array input | array | [ 'one' 'two'] |
| `intInput` | int input | int | 124 |
| `intInput` | int input | int <br/> <br/>Acceptable values range from 1 to 2333. | 124 |
| `resourceGroupLocation` | Location of the resource group | [resourceGroupLocationAllow](#resourcegrouplocationallow) | |
| `resourceGroupName` | Name of the resource group | string | |
| `tags` | Tags to append to resource group | object | {} |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module exampleInstance 'ts/IacModules:resources/resource-groups:2022-12-17' = {
| `complexObject` | Object input | object | [complexObjectValue](#complexobjectvalue) |
| `inputArray` | Complex array input | array | [inputArrayValue](#inputarrayvalue) |
| `inputArraySimple` | Simple array input | array | [ 'one' 'two'] |
| `intInput` | int input | int | 124 |
| `intInput` | int input | int <br/> <br/>Acceptable values range from 1 to 2333. | 124 |
| `resourceGroupLocation` | Location of the resource group | [resourceGroupLocationAllow](#resourcegrouplocationallow) | |
| `resourceGroupName` | Name of the resource group | string | |
| `tags` | Tags to append to resource group | object | {} |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ module exampleInstance 'ts/IacModules:resources/resource-groups:2022-12-17' = {
| Parameter | Description | Type | Default |
| --- | --- | --- | --- |
| `boolInput` | Bool input | bool | false |
| `complexObject` | Object input | object | [complexObjectValue](#complexobjectvalue) |
| `complexObject` | Object input | object (secure) | [complexObjectValue](#complexobjectvalue) |
| `inputArray` | Complex array input | array | [inputArrayValue](#inputarrayvalue) |
| `inputArraySimple` | Simple array input | array | [ 'one' 'two'] |
| `intInput` | int input | int | 124 |
| `resourceGroupLocation` | Location of the resource group | [resourceGroupLocationAllow](#resourcegrouplocationallow) | |
| `intInput` | int input | int <br/> <br/>Accepted values: from 1 to 2333. | 124 |
| `resourceGroupLocation` | Location of the resource group | [resourceGroupLocationAllow](#resourcegrouplocationallow) (secure) <br/> <br/>Character limit: 3-24 | |
| `resourceGroupName` | Name of the resource group | string | |
| `tags` | Tags to append to resource group | object | {} |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ module exampleInstance 'ts/IacModules:resources/resource-groups:2022-12-17' = {
| Parameter | Description | Type | Default |
| --- | --- | --- | --- |
| `boolInput` | Bool input | bool | false |
| `complexObject` | Object input | object | [complexObjectValue](#complexobjectvalue) |
| `complexObject` | Object input | object (secure) | [complexObjectValue](#complexobjectvalue) |
| `inputArray` | Complex array input | array | [inputArrayValue](#inputarrayvalue) |
| `inputArraySimple` | Simple array input | array | [ 'one' 'two'] |
| `intInput` | int input | int | 124 |
| `resourceGroupLocation` | Location of the resource group | [resourceGroupLocationAllow](#resourcegrouplocationallow) | |
| `intInput` | int input | int <br/> <br/>Accepted values: from 1 to 2333. | 124 |
| `resourceGroupLocation` | Location of the resource group | [resourceGroupLocationAllow](#resourcegrouplocationallow) (secure) <br/> <br/>Character limit: 3-24 | |
| `resourceGroupName` | Name of the resource group | string | |
| `tags` | Tags to append to resource group | object | {} |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ param resourceGroupName string
'westeurope'
])
@description('Location of the resource group')
@minLength(3)
@maxLength(24)
@secure()
param resourceGroupLocation string

@description('Tags to append to resource group')
Expand All @@ -31,6 +34,8 @@ param tags object = {}
param boolInput bool = false

@description('int input')
@minValue(1)
@maxValue(2333)
param intInput int = 124

@description('Complex array input')
Expand All @@ -45,6 +50,7 @@ param inputArraySimple array = [
]

@description('Object input')
@secure()
param complexObject object = {
prop: 'one'
propTwo: 'two'
Expand Down
28 changes: 0 additions & 28 deletions src/BicepDocs.Cli.UnitTests/BicepDocs.Cli.UnitTests.csproj

This file was deleted.

1 change: 0 additions & 1 deletion src/BicepDocs.Cli.UnitTests/Usings.cs

This file was deleted.

55 changes: 55 additions & 0 deletions src/BicepDocs.Core.UnitTests/Parsers/ParameterParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,61 @@ param resourceGroupLocation string
Assert.AreEqual("Tags to append to resource group", tags.Description);
}


[TestMethod]
public async Task Parameter_Constraints_Parses()
{
const string template = @"targetScope = 'subscription'
@minLength(3)
@maxLength(24)
@description('Name of the resource group')
param resourceGroupName string
@minLength(3)
@maxLength(24)
param someArr array = []
@maxValue(100)
@minValue(1)
param intVal int = 1
@secure()
param objectVal object = {}
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-01-01' = {
name: resourceGroupName
}";
var semanticModel = await GetModel(template);
var parameters = ParameterParser.ParseParameters(semanticModel);

var resourceGroupName = parameters.First(x => x.Name == "resourceGroupName");
Assert.AreEqual("resourceGroupName", resourceGroupName.Name);
Assert.AreEqual("string", resourceGroupName.Type);
Assert.AreEqual("Name of the resource group", resourceGroupName.Description);
Assert.AreEqual(3, resourceGroupName.MinLength);
Assert.AreEqual(24, resourceGroupName.MaxLength);


var someArr = parameters.First(x => x.Name == "someArr");
Assert.AreEqual("someArr", someArr.Name);
Assert.AreEqual("array", someArr.Type);
Assert.AreEqual(3, someArr.MinLength);
Assert.AreEqual(24, someArr.MaxLength);


var intVal = parameters.First(x => x.Name == "intVal");
Assert.AreEqual("intVal", intVal.Name);
Assert.AreEqual("int", intVal.Type);
Assert.AreEqual(100, intVal.MaxValue);
Assert.AreEqual(1, intVal.MinValue);

var objectVal = parameters.First(x => x.Name == "objectVal");
Assert.AreEqual("objectVal", objectVal.Name);
Assert.AreEqual("object", objectVal.Type);
Assert.IsTrue(objectVal.Secure);
}

[TestMethod]
public async Task Parameter_Interpolated_Parses()
{
Expand Down
10 changes: 10 additions & 0 deletions src/BicepDocs.Core/Models/Parsing/ParsedParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,14 @@ public record ParsedParameter(string Name, string Type)
public List<string>? AllowedValues { get; set; }
public bool IsComplexAllow { get; set; }
public bool IsInterpolated { get; set; }

public int? MinLength { get; set; }

public int? MinValue { get; set; }

public int? MaxLength { get; set; }

public int? MaxValue { get; set; }

public bool Secure { get; set; }
}
64 changes: 51 additions & 13 deletions src/BicepDocs.Core/Parsers/ParameterParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using Bicep.Core;
using Bicep.Core.Navigation;
using Bicep.Core.Semantics;
using Bicep.Core.Syntax;
Expand All @@ -10,10 +11,6 @@ public static class ParameterParser
{
public static ImmutableList<ParsedParameter> ParseParameters(SemanticModel model)
{
var defaultValueSyntaxes = model.Root.ParameterDeclarations
.ToImmutableDictionary(x => x.Name, y => y.DeclaringParameter.Modifier as ParameterDefaultValueSyntax)
.ToDictionary(x => x.Key, y => y.Value);

var parameters = new List<ParsedParameter>();
foreach (var templateParameter in model.Parameters.OrderBy(x => x.Key))
{
Expand All @@ -25,37 +22,78 @@ public static ImmutableList<ParsedParameter> ParseParameters(SemanticModel model
{
Description = templateParameter.Value.Description
};
if (defaultValueSyntaxes.TryGetValue(templateParameter.Key, out var syntaxBase) && syntaxBase != null)

var paramType = templateParameter.Value.TypeReference.Type.Name;
var allowList = paramType.Split('\'').Select(x => x.Trim()).Where(x => x.Length > 1).ToArray();
var allowValues = allowList.Select(x => x.Replace("'", "")).Where(x => !string.IsNullOrEmpty(x)).ToList();
parameter.IsComplexAllow = allowList.Length > 2;
parameter.AllowedValues = allowValues;

var symbol = GetParameterSymbol(model, templateParameter.Key);
if (symbol == null)
{
parameter.DefaultValue = syntaxBase.DefaultValue.ToTextPreserveFormatting();
parameter.IsComplexDefault = syntaxBase.DefaultValue switch
parameters.Add(parameter);
continue;
}

parameter.MaxLength = GetDecorator(symbol, LanguageConstants.ParameterMaxLengthPropertyName);
parameter.MinLength = GetDecorator(symbol, LanguageConstants.ParameterMinLengthPropertyName);
parameter.Secure = HasDecorator(symbol, LanguageConstants.ParameterSecurePropertyName);
parameter.MinValue = GetDecorator(symbol, LanguageConstants.ParameterMinValuePropertyName);
parameter.MaxValue = GetDecorator(symbol, LanguageConstants.ParameterMaxValuePropertyName);


var defaultValueSyntaxBase = GetDefaultValue(symbol);
if (defaultValueSyntaxBase != null)
{
parameter.DefaultValue = defaultValueSyntaxBase.ToTextPreserveFormatting();
parameter.IsComplexDefault = defaultValueSyntaxBase switch
{
ObjectSyntax objectSyntax when objectSyntax.ToNamedPropertyDictionary().IsEmpty => false,
ObjectSyntax => true,
ArraySyntax arraySyntax => IsComplexArray(arraySyntax),
_ => false
};

parameter.IsInterpolated = syntaxBase.DefaultValue switch
parameter.IsInterpolated = defaultValueSyntaxBase switch
{
StringSyntax stringSyntax when stringSyntax.IsInterpolated() => true,
PropertyAccessSyntax => true,
_ => false
};
}

var paramType = templateParameter.Value.TypeReference.Type.Name;
var allowList = paramType.Split('\'').Select(x => x.Trim()).Where(x => x.Length > 1).ToArray();
var allowValues = allowList.Select(x => x.Replace("'", "")).Where(x => !string.IsNullOrEmpty(x)).ToList();
parameter.IsComplexAllow = allowList.Length > 2;
parameter.AllowedValues = allowValues;

parameters.Add(parameter);
}

return parameters.ToImmutableList();
}

private static bool HasDecorator(ParameterSymbol parameterSymbol, string decoratorName)
{
var f = parameterSymbol.DeclaringParameter.Decorators;
var fcs = f.FirstOrDefault(x => (x.Expression as FunctionCallSyntax)?.Name?.IdentifierName == decoratorName);
return fcs != null;
}

private static ParameterSymbol? GetParameterSymbol(SemanticModel model, string paramName) =>
model.Root.ParameterDeclarations.FirstOrDefault(x => x.Name == paramName);

private static SyntaxBase? GetDefaultValue(ParameterSymbol parameterSymbol)
{
var declaration = parameterSymbol.DeclaringParameter.Modifier as ParameterDefaultValueSyntax;
return declaration?.DefaultValue;
}

private static int? GetDecorator(ParameterSymbol parameterSymbol, string decoratorName)
{
var f = parameterSymbol.DeclaringParameter.Decorators;
var fcs = f.FirstOrDefault(x => (x.Expression as FunctionCallSyntax)?.Name?.IdentifierName == decoratorName);
var literalText = (fcs?.Arguments.FirstOrDefault()?.Expression as IntegerLiteralSyntax)?.Literal?.Text;
return int.TryParse(literalText, out var value) ? value : default(int?);
}

private static bool IsComplexArray(ArraySyntax syntax)
{
if (!syntax.Items.Any())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,46 @@ public void BuildParameters_NoParameters_DoesNotGenerate()
Assert.AreEqual(0, document.Count);
}

[DataTestMethod]
[DataRow(1, null, null, null, false, "<br/> <br/>Accepted values: from 1.")]
[DataRow(null, 1, null, null, false, "<br/> <br/>Accepted values: to 1.")]
[DataRow(1, 10, null, null, false, "<br/> <br/>Accepted values: from 1 to 10.")]
[DataRow(null, null, 1, null, false, "<br/> <br/>Character limit: 1-X")]
[DataRow(null, null, null, 10, false, "<br/> <br/>Character limit: X-10")]
[DataRow(null, null, 1, 10, false, "<br/> <br/>Character limit: 1-10")]
[DataRow(null, null, null, null, true, "(secure)")]
public void BuildParameters_DecoratorSet_BuildsCorrectly(int? minValue, int? maxValue, int? minLength,
int? maxLength, bool secure, string expectedDesc)
{
string expected = $@"## Parameters
| Parameter | Description | Type | Default |
| --- | --- | --- | --- |
| `location` | The location of the resource | string {expectedDesc} | |";

var parameters = new List<ParsedParameter>
{
new("location", "string")
{
Description = "The location of the resource",
MinValue = minValue,
MaxValue = maxValue,
MinLength = minLength,
MaxLength = maxLength,
Secure = secure
}
}.ToImmutableList();
var document = new MarkdownDocument();

ParameterGenerator.BuildParameters(document, new FormatterOptions(), parameters);

Assert.AreEqual(2, document.Count);

var md = document.ToMarkdown();

Assert.AreEqual(expected, md);
}

[TestMethod]
public void BuildParameters_SimpleParameterType_BuildsCorrectly()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public void BuildReferencedResources_Input_BuildsCorrectly()
| Provider | Name | Scope |
| --- | --- | --- |
| Microsoft.Web/sites/2022-12-18 | siteOne | `subscription()` |";
| Microsoft.Web/sites/2022-12-18 | `siteOne` | `subscription()` |";
var resources = new List<ParsedResource>
{
new("Microsoft.Web/sites/2022-12-18", "Microsoft.Web", "sites")
Expand Down Expand Up @@ -185,8 +185,8 @@ public void BuildReferencedResources_MultipleOfSame_BuildsAll()
| Provider | Name | Scope |
| --- | --- | --- |
| Microsoft.Web/sites/2022-12-18 | siteOne | `subscription()` |
| Microsoft.Web/sites/2022-12-18 | siteTwo | `subscription()` |";
| Microsoft.Web/sites/2022-12-18 | `siteOne` | `subscription()` |
| Microsoft.Web/sites/2022-12-18 | `siteTwo` | `subscription()` |";
var resources = new List<ParsedResource>
{
new("Microsoft.Web/sites/2022-12-18", "Microsoft.Web", "sites")
Expand Down Expand Up @@ -230,7 +230,7 @@ public async Task BuildReferencedResources_InputTemplate_BuildsSingle()
| Provider | Name | Scope |
| --- | --- | --- |
| Microsoft.Resources/resourceGroups@2021-01-01 | resourceGroupName | - |";
| Microsoft.Resources/resourceGroups@2021-01-01 | `resourceGroupName` | - |";

const string template = @"resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-01-01' existing = {
name: resourceGroupName
Expand Down
2 changes: 1 addition & 1 deletion src/BicepDocs.Formatter.Markdown/Elements/MkTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ public override string ToMarkdown()
sb.AppendLine($"| {string.Join(" | ", row)} |");
}

return sb.ToString().Replace("<", "\\<").Replace(">", "\\>");
return sb.ToString().Replace("<", "\\<").Replace(">", "\\>").Replace("\\<br/\\>", "<br/>");
}
}
Loading

0 comments on commit 030e2f8

Please sign in to comment.