Skip to content
179 changes: 179 additions & 0 deletions src/Bicep.Core.IntegrationTests/ParametersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,185 @@ param values array
}"));
}

[TestMethod]
public void Base_object_spread_should_not_evaluate_unreferenced_external_input()
{
var result = CompilationHelper.CompileParams(
("parameters.bicepparam", @"
using 'main.bicep'
extends 'shared.bicepparam'
param B = {
...base.B
something: 'something'
}
"),
("shared.bicepparam", @"
using none
param external = externalInput('foo', 'bar')
param B = {
parent: 'parent'
}
"),
("main.bicep", @"
param external object
param B object
"));

result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
result.Parameters.Should().HaveJsonAtPath("parameters.B.value", @"{
""parent"": ""parent"",
""something"": ""something""
}");
result.Parameters.Should().HaveValueAtPath("parameters.external.expression", "[externalInputs('foo_0')]");
result.Parameters.Should().HaveJsonAtPath("externalInputDefinitions", @"{
""foo_0"": {
""kind"": ""foo"",
""config"": ""bar""
}
}");
}

[TestMethod]
public void Base_array_spread_should_not_evaluate_unreferenced_external_input()
{
var result = CompilationHelper.CompileParams(
("parameters.bicepparam", @"
using 'main.bicep'
extends 'shared.bicepparam'
param values = [
...base.values
'child'
]
"),
("shared.bicepparam", @"
using none
param enabled = bool(externalInput('feature', 'enabled'))
param values = [
'parent'
]
"),
("main.bicep", @"
param enabled bool
param values array
"));

result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
result.Parameters.Should().HaveJsonAtPath("parameters.values.value", @"[
""parent"",
""child""
]");
result.Parameters.Should().HaveValueAtPath("parameters.enabled.expression", "[bool(externalInputs('feature_0'))]");
result.Parameters.Should().HaveJsonAtPath("externalInputDefinitions", @"{
""feature_0"": {
""kind"": ""feature"",
""config"": ""enabled""
}
}");
}

[TestMethod]
public void Base_property_accesses_should_not_evaluate_unreferenced_external_input()
{
var result = CompilationHelper.CompileParams(
("parameters.bicepparam", @"
using 'main.bicep'
extends 'shared.bicepparam'
param dotAccess = base.selected.value
param bracketAccess = base['selected'].value
"),
("shared.bicepparam", @"
using none
param external = externalInput('foo')
param selected = {
value: 'parent'
}
"),
("main.bicep", @"
param external object
param selected object
param dotAccess string
param bracketAccess string
"));

result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
result.Parameters.Should().HaveValueAtPath("parameters.dotAccess.value", "parent");
result.Parameters.Should().HaveValueAtPath("parameters.bracketAccess.value", "parent");
result.Parameters.Should().HaveValueAtPath("parameters.external.expression", "[externalInputs('foo_0')]");
}

[TestMethod]
public void Nested_base_spreads_should_not_evaluate_unreferenced_external_input()
{
var result = CompilationHelper.CompileParams(
("parameters.bicepparam", @"
using 'main.bicep'
extends 'middle.bicepparam'
param tags = {
...base.tags
child: 'child'
}
"),
("middle.bicepparam", @"
using none
extends 'base.bicepparam'
param tags = {
...base.tags
middle: 'middle'
}
"),
("base.bicepparam", @"
using none
param external = externalInput('foo')
param tags = {
parent: 'parent'
}
"),
("main.bicep", @"
param external object
param tags object
"));

result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
result.Parameters.Should().HaveJsonAtPath("parameters.tags.value", @"{
""parent"": ""parent"",
""middle"": ""middle"",
""child"": ""child""
}");
result.Parameters.Should().HaveValueAtPath("parameters.external.expression", "[externalInputs('foo_0')]");
}

[TestMethod]
public void Base_spread_should_preserve_function_bindings_in_selected_parent_parameter()
{
var result = CompilationHelper.CompileParams(
("parameters.bicepparam", @"
using 'main.bicep'
extends 'shared.bicepparam'
param tags = {
...base.tags
child: 'child'
}
"),
("shared.bicepparam", @"
using none
param external = externalInput('foo')
param tags = {
parent: toLower('PARENT')
}
"),
("main.bicep", @"
param external object
param tags object
"));

result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
result.Parameters.Should().HaveJsonAtPath("parameters.tags.value", @"{
""parent"": ""parent"",
""child"": ""child""
}");
result.Parameters.Should().HaveValueAtPath("parameters.external.expression", "[externalInputs('foo_0')]");
}

[TestMethod]
public void Decorators_on_using_param_and_extends_statements_should_raise_errors()
{
Expand Down
17 changes: 17 additions & 0 deletions src/Bicep.Core/Intermediate/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,13 @@ private Expression ConvertArrayAccess(ArrayAccessSyntax arrayAccess)
}
}

if (arrayAccess.IndexExpression is StringSyntax indexString &&
indexString.TryGetLiteralValue() is { } propertyName &&
TryGetBaseParameterAssignment(arrayAccess.BaseExpression, propertyName) is { } parentAssignment)
{
return ConvertWithoutLowering(parentAssignment.DeclaringParameterAssignment.Value);
}

var convertedBase = ConvertWithoutLowering(arrayAccess.BaseExpression);
var convertedIndex = ConvertWithoutLowering(arrayAccess.IndexExpression);

Expand Down Expand Up @@ -1030,6 +1037,11 @@ private Expression ConvertPropertyAccess(PropertyAccessSyntax propertyAccess)
{
var flags = GetAccessExpressionFlags(propertyAccess);

if (TryGetBaseParameterAssignment(propertyAccess.BaseExpression, propertyAccess.PropertyName.IdentifierName) is { } parentAssignment)
{
return ConvertWithoutLowering(parentAssignment.DeclaringParameterAssignment.Value);
}

// Looking for: myResource.someProp (where myResource is a resource declared in-file)
if (Context.SemanticModel.ResourceMetadata.TryLookup(propertyAccess.BaseExpression) is DeclaredResourceMetadata resource)
{
Expand Down Expand Up @@ -1098,6 +1110,11 @@ moduleCollectionOutputs.BaseExpression is ArrayAccessSyntax moduleArrayAccess &&
flags);
}

private ParameterAssignmentSymbol? TryGetBaseParameterAssignment(SyntaxBase baseExpression, string propertyName)
=> Context.SemanticModel.GetSymbolInfo(baseExpression) is BaseParametersSymbol baseParameters
? baseParameters.ParentAssignments.FirstOrDefault(assignment => LanguageConstants.IdentifierComparer.Equals(assignment.Name, propertyName))
: null;

private Expression ConvertResourceAccess(ResourceAccessSyntax resourceAccessSyntax)
{
if (Context.SemanticModel.ResourceMetadata.TryLookup(resourceAccessSyntax) is { } resource)
Expand Down
Loading