public void If_ResLocationIs_AnyOtherStringLiteral_ShouldFail_AndOfferToCreateNewParameter() { var result = CompilationHelper.Compile(@" resource appInsightsComponents 'Microsoft.Insights/components@2020-02-02-preview' = { name: 'name' location: 'non-global' kind: 'web' properties: { Application_Type: 'web' } }" ); CodeFix x = result.Diagnostics.First().Should().BeAssignableTo <IFixable>() .Which.Fixes.Single(); x.Should().BeEquivalentTo( new { Description = "Create new parameter 'location' with default value 'non-global'", Replacements = new[] { // Replacement 1: change 'non-global' to 'location' new { Text = "location" }, // Replacement 1: add new location param new { Text = "@description('Specifies the location for resources.')\nparam location string = 'non-global'\n\n" } } }); }
public void If_NameLocationAlreadyInUse_ShouldChooseAnotherNameForFix() { var result = CompilationHelper.Compile(@" var location = 'fee fie' param location2 string resource location3 'Microsoft.Insights/components@2020-02-02-preview' = { name: 'name' location: 'non-global' kind: 'web' properties: { Application_Type: 'web' } } output location4 string = '${location}${location2}' "); CodeFix x = result.Diagnostics.First().Should().BeAssignableTo <IFixable>() .Which.Fixes.Single(); x.Should().BeEquivalentTo( new { Description = "Create new parameter 'location5' with default value 'non-global'", Replacements = new[] { // Replacement 1: change 'non-global' to 'location5' new { Text = "location5" }, // Replacement 1: add new location param new { Text = "@description('Specifies the location for resources.')\nparam location5 string = 'non-global'\n\n" } } }); }
public void Emitter_should_generate_correct_extension_scope_property_and_correct_dependsOn() { var(json, diags) = CompilationHelper.Compile(@" resource resourceA 'My.Rp/myResource@2020-01-01' = { name: 'resourceA' } resource resourceB 'My.Rp/myResource@2020-01-01' = { scope: resourceA name: 'resourceB' } resource resourceC 'My.Rp/myResource@2020-01-01' = { scope: resourceB name: 'resourceC' }"); json.Should().NotBeNull(); var template = JObject.Parse(json !); using (new AssertionScope()) { template.SelectToken("$.resources[?(@.name == 'resourceB')].scope") !.ToString().Should().Be("[format('My.Rp/myResource/{0}', 'resourceA')]"); template.SelectToken("$.resources[?(@.name == 'resourceB')].dependsOn[0]") !.ToString().Should().Be("[resourceId('My.Rp/myResource', 'resourceA')]"); template.SelectToken("$.resources[?(@.name == 'resourceC')].scope") !.ToString().Should().Be("[extensionResourceId(format('My.Rp/myResource/{0}', 'resourceA'), 'My.Rp/myResource', 'resourceB')]"); template.SelectToken("$.resources[?(@.name == 'resourceC')].dependsOn[0]") !.ToString().Should().Be("[extensionResourceId(format('My.Rp/myResource/{0}', 'resourceA'), 'My.Rp/myResource', 'resourceB')]"); } }
public void List_wildcard_function_on_resource_references() { var result = CompilationHelper.Compile(@" resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: 'testacc' location: 'West US' kind: 'StorageV2' sku: { name: 'Standard_LRS' } } #disable-next-line outputs-should-not-contain-secrets output pkStandard string = listKeys(stg.id, stg.apiVersion).keys[0].value #disable-next-line outputs-should-not-contain-secrets output pkMethod string = stg.listKeys().keys[0].value #disable-next-line outputs-should-not-contain-secrets output pkMethodVersionOverride string = stg.listKeys('2021-01-01').keys[0].value #disable-next-line outputs-should-not-contain-secrets output pkMethodPayload string = stg.listKeys(stg.apiVersion, { key1: 'val1' }) "); result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); result.Template.Should().HaveValueAtPath("$.outputs['pkStandard'].value", "[listKeys(resourceId('Microsoft.Storage/storageAccounts', 'testacc'), '2019-06-01').keys[0].value]"); result.Template.Should().HaveValueAtPath("$.outputs['pkMethod'].value", "[listKeys(resourceId('Microsoft.Storage/storageAccounts', 'testacc'), '2019-06-01').keys[0].value]"); result.Template.Should().HaveValueAtPath("$.outputs['pkMethodVersionOverride'].value", "[listKeys(resourceId('Microsoft.Storage/storageAccounts', 'testacc'), '2021-01-01').keys[0].value]"); result.Template.Should().HaveValueAtPath("$.outputs['pkMethodPayload'].value", "[listKeys(resourceId('Microsoft.Storage/storageAccounts', 'testacc'), '2019-06-01', createObject('key1', 'val1'))]"); }
private void ExpectDiagnosticWithFixedText(string text, string expectedText) { var result = CompilationHelper.Compile(text); result.Diagnostics.Should().HaveCount(1); FixableDiagnostic diagnostic = (FixableDiagnostic)result.Diagnostics.Single(); diagnostic.Code.Should().Be("BCP035"); diagnostic.Fixes.Should().HaveCount(1); var fix = diagnostic.Fixes.Single(); fix.Replacements.Should().HaveCount(1); var replacement = fix.Replacements.Single(); var actualText = text.Remove(replacement.Span.Position, replacement.Span.Length); actualText = actualText.Insert(replacement.Span.Position, replacement.Text); // Normalize line endings expectedText = expectedText.Replace("\r\n", "\n").Replace("\n", Environment.NewLine); actualText = actualText.Replace("\r\n", "\n").Replace("\n", Environment.NewLine); actualText.Should().Be(expectedText); }
public void List_wildcard_function_on_cross_scope_resource_references() { var result = CompilationHelper.Compile(@" resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' existing = { scope: resourceGroup('other') name: 'testacc' } #disable-next-line outputs-should-not-contain-secrets output pkStandard string = listKeys(stg.id, stg.apiVersion).keys[0].value #disable-next-line outputs-should-not-contain-secrets output pkMethod string = stg.listKeys().keys[0].value #disable-next-line outputs-should-not-contain-secrets output pkMethodVersionOverride string = stg.listKeys('2021-01-01').keys[0].value #disable-next-line outputs-should-not-contain-secrets output pkMethodPayload string = stg.listKeys(stg.apiVersion, { key1: 'val1' }) "); result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); result.Template.Should().HaveValueAtPath("$.outputs['pkStandard'].value", "[listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, 'other'), 'Microsoft.Storage/storageAccounts', 'testacc'), '2019-06-01').keys[0].value]"); result.Template.Should().HaveValueAtPath("$.outputs['pkMethod'].value", "[listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, 'other'), 'Microsoft.Storage/storageAccounts', 'testacc'), '2019-06-01').keys[0].value]"); result.Template.Should().HaveValueAtPath("$.outputs['pkMethodVersionOverride'].value", "[listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, 'other'), 'Microsoft.Storage/storageAccounts', 'testacc'), '2021-01-01').keys[0].value]"); result.Template.Should().HaveValueAtPath("$.outputs['pkMethodPayload'].value", "[listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, 'other'), 'Microsoft.Storage/storageAccounts', 'testacc'), '2019-06-01', createObject('key1', 'val1'))]"); }
private void RunWithDiagnosticAnnotations(string bicepText, Func <IDiagnostic, bool> filterFunc, OnCompileErrors onCompileErrors, Action <IEnumerable <IDiagnostic> > assertAction) { using (AssertionScope scope = new AssertionScope()) { scope.AddReportable("bicep code", bicepText); var result = CompilationHelper.Compile(bicepText); result.Should().NotHaveDiagnosticsWithCodes(new[] { LinterAnalyzer.LinterRuleInternalError }, "There should never be linter LinterRuleInternalError errors"); if (onCompileErrors == OnCompileErrors.Fail) { var compileErrors = result.Diagnostics.Where(d => d.Level == DiagnosticLevel.Error); DiagnosticAssertions.DoWithDiagnosticAnnotations( result.Compilation.SourceFileGrouping.EntryPoint, compileErrors, diags => diags.Should().HaveCount(0)); } IDiagnostic[] diagnosticsMatchingCode = result.Diagnostics.Where(filterFunc).ToArray(); DiagnosticAssertions.DoWithDiagnosticAnnotations( result.Compilation.SourceFileGrouping.EntryPoint, result.Diagnostics.Where(filterFunc), assertAction); } }
public void Casing_issues_are_corrected() { var bicepFile = @" resource resA 'My.Rp/resA@2020-01-01' = { name: 'resA' properties: { lowerCaseProp: 'abc' camelcaseprop: 'def' 'lowerCaseQuoted=+.Prop': 'ghi' 'camelcasequoted=+.prop': 'jkl' lowerCaseEnumProp: 'MyEnum' pascalCaseEnumProp: 'myenum' lowerCaseEnumUnionProp: 'MyEnum' pascalCaseEnumUnionProp: 'myenum' } } output myObj object = { lowerCaseProp: resA.properties.lowerCaseProp camelcaseprop: resA.properties.camelcaseprop } "; var typeDefinition = ResourceTypeProviderHelper.CreateCustomResourceType("My.Rp/resA", "2020-01-01", TypeSymbolValidationFlags.WarnOnTypeMismatch, new TypeProperty("lowercaseprop", LanguageConstants.String), new TypeProperty("camelCaseProp", LanguageConstants.String), new TypeProperty("lowercasequoted=+.prop", LanguageConstants.String), new TypeProperty("camelCaseQuoted=+.Prop", LanguageConstants.String), new TypeProperty("lowerCaseEnumProp", new StringLiteralType("myenum")), new TypeProperty("pascalCaseEnumProp", new StringLiteralType("MyEnum")), new TypeProperty("lowerCaseEnumUnionProp", UnionType.Create(new StringLiteralType("myenum"), new StringLiteralType("blahblah"))), new TypeProperty("pascalCaseEnumUnionProp", UnionType.Create(new StringLiteralType("MyEnum"), new StringLiteralType("BlahBlah")))); var typeProvider = ResourceTypeProviderHelper.CreateMockTypeProvider(typeDefinition.AsEnumerable()); var(_, _, compilation) = CompilationHelper.Compile(typeProvider, ("main.bicep", bicepFile)); var rewriter = new TypeCasingFixerRewriter(compilation.GetEntrypointSemanticModel()); var newProgramSyntax = rewriter.Rewrite(compilation.SyntaxTreeGrouping.EntryPoint.ProgramSyntax); PrintHelper.PrettyPrint(newProgramSyntax).Should().Be( @"resource resA 'My.Rp/resA@2020-01-01' = { name: 'resA' properties: { lowercaseprop: 'abc' camelCaseProp: 'def' 'lowercasequoted=+.prop': 'ghi' 'camelCaseQuoted=+.Prop': 'jkl' lowerCaseEnumProp: 'myenum' pascalCaseEnumProp: 'MyEnum' lowerCaseEnumUnionProp: 'myenum' pascalCaseEnumUnionProp: 'MyEnum' } } output myObj object = { lowerCaseProp: resA.properties.lowercaseprop camelcaseprop: resA.properties.camelCaseProp }"); }
protected IDiagnostic[] GetDiagnostics(string ruleCode, string text) { var compilationResult = CompilationHelper.Compile(text); var internalRuleErrors = compilationResult.Diagnostics.Where(d => d.Code == LinterAnalyzer.FailedRuleCode).ToArray(); internalRuleErrors.Count().Should().Be(0, "There should never be linter FailedRuleCode errors"); return(compilationResult.Diagnostics.OfType <IDiagnostic>().Where(d => d.Code == ruleCode).ToArray()); }
public void AssigningResourceToVariable_ShouldNotGenerateVariables_ChainedCondition() { var result = CompilationHelper.Compile(@" param mode int = 0 resource storage1_1 'Microsoft.Storage/storageAccounts@2019-06-01' = if (mode == 1) { name: 'test11' location: 'eastus' kind: 'StorageV2' sku: { name: 'Standard_LRS' } } resource storage1_2 'Microsoft.Storage/storageAccounts@2019-06-01' = if (mode != 1) { name: 'test12' location: 'eastus' kind: 'StorageV2' sku: { name: 'Standard_LRS' } } var refResource = ref3 var ref1 = storage1_1 var ref2 = storage1_2 var ref3 = mode == 1 ? ref1 : ref2 resource storage2 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: 'test2' location: 'eastus' kind: 'StorageV2' sku: { name: 'Standard_LRS' } properties: { allowBlobPublicAccess: refResource.properties.allowBlobPublicAccess } } "); result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); using (new AssertionScope()) { result.Template.Should().NotHaveValueAtPath("$.variables", "variable should not be generated"); result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'test2')].dependsOn", new JArray { "[resourceId('Microsoft.Storage/storageAccounts', 'test11')]", "[resourceId('Microsoft.Storage/storageAccounts', 'test12')]" }, "Referenced resource should be added to depends on section"); result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'test2')].properties.allowBlobPublicAccess", "[if(equals(parameters('mode'), 1), reference(resourceId('Microsoft.Storage/storageAccounts', 'test11'), '2019-06-01', 'full'), reference(resourceId('Microsoft.Storage/storageAccounts', 'test12'), '2019-06-01', 'full')).properties.allowBlobPublicAccess]", "Resource access should be in-lined"); } }
private void DoWithDiagnosticAnnotations(string text, Func <IDiagnostic, bool> filterFunc, Action <IEnumerable <IDiagnostic> > assertAction) { var result = CompilationHelper.Compile(text); result.Should().NotHaveDiagnosticsWithCodes(new [] { LinterAnalyzer.FailedRuleCode }, "There should never be linter FailedRuleCode errors"); DiagnosticAssertions.DoWithDiagnosticAnnotations( result.Compilation.SourceFileGrouping.EntryPoint, result.Diagnostics.Where(filterFunc), assertAction); }
public void TemplateEmitter_should_not_dispose_text_writer() { var(_, _, compilation) = CompilationHelper.Compile(string.Empty); var stringBuilder = new StringBuilder(); var stringWriter = new StringWriter(stringBuilder); var emitter = new TemplateEmitter(compilation.GetEntrypointSemanticModel(), BicepTestConstants.EmitterSettings); emitter.Emit(stringWriter); // second write should succeed if stringWriter wasn't closed emitter.Emit(stringWriter); }
public void If_ResLocationIs_StringLiteral_ShouldFail_WithFixes() { var result = CompilationHelper.Compile(@" resource storageaccount 'Microsoft.Storage/storageAccounts@2021-02-01' = { name: 'name' location: 'westus' kind: 'StorageV2' sku: { name: 'Premium_LRS' } } "); result.Diagnostics.Should().HaveDiagnostics(new[]
public void If_ResLocationIs_Global_CaseInsensitive_ShouldPass() { var result = CompilationHelper.Compile(@" resource appInsightsComponents 'Microsoft.Insights/components@2020-02-02-preview' = { name: 'name' location: 'GLOBAL' kind: 'web' properties: { Application_Type: 'web' } }" ); result.Diagnostics.Should().BeEmpty(); }
public void If_ResLocationIs_VariableWithGlobal_ShouldPass() { var result = CompilationHelper.Compile(@" var location = 'Global' resource appInsightsComponents 'Microsoft.Insights/components@2020-02-02-preview' = { name: 'name' location: location kind: 'web' properties: { Application_Type: 'web' } }" ); result.Diagnostics.Should().BeEmpty(); }
public void AssigningResourceToVariable_ShouldNotGenerateVariables_ChainedVariables() { var result = CompilationHelper.Compile(@" resource storage 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: 'test' location: 'eastus' kind: 'StorageV2' sku: { name: 'Standard_LRS' } } var ref4 = ref3 var ref3 = ref2 var ref2 = ref1 var ref1 = storage resource storage2 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: 'test2' location: 'eastus' kind: 'StorageV2' sku: { name: 'Standard_LRS' } properties: { allowBlobPublicAccess: ref4.properties.allowBlobPublicAccess } } "); result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); using (new AssertionScope()) { result.Template.Should().NotHaveValueAtPath("$.variables", "variable should not be generated"); result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'test2')].dependsOn", new JArray { "[resourceId('Microsoft.Storage/storageAccounts', 'test')]" }, "Referenced resource should be added to depends on section"); result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'test2')].properties.allowBlobPublicAccess", "[reference(resourceId('Microsoft.Storage/storageAccounts', 'test')).allowBlobPublicAccess]", "Resource access should be in-lined"); } }
public void Multiline_strings_should_parse_correctly(string newlineSequence) { var inputFile = @" var multiline = ''' this is a multiline string ''' "; var(template, _, _) = CompilationHelper.Compile(StringUtils.ReplaceNewlines(inputFile, newlineSequence)); var expected = string.Join(newlineSequence, new [] { "this", " is", " a", " multiline", " string", "" }); template !.SelectToken("$.variables.multiline") !.Should().DeepEqual(expected); }
private void CompileAndTest(string bicepText, string[] expectedFoundMessages) { using (var scope = new AssertionScope()) { scope.AddReportable("bicepText", bicepText); var result = CompilationHelper.Compile(bicepText); var semanticModel = result.Compilation.GetEntrypointSemanticModel(); // Look for an output - that's what we'll use in the test var output = result.BicepFile.ProgramSyntax.Children.OfType <OutputDeclarationSyntax>() .Should().HaveCount(1, "Each testcase should contain a single output with an expression to test") .And.Subject.First(); var secrets = FindPossibleSecretsVisitor.FindPossibleSecrets(semanticModel, output.Value); secrets.Select(s => s.FoundMessage).Should().BeEquivalentTo(expectedFoundMessages); } }
public void Readonly_properties_are_removed() { var bicepFile = @" resource resA 'My.Rp/resA@2020-01-01' = { name: 'resA' properties: { readOnlyProp: 'abc' readWriteProp: 'def' writeOnlyProp: 'ghi' } } output myObj object = { readOnlyProp: resA.properties.readOnlyProp readWriteProp: resA.properties.readWriteProp } "; var typeDefinition = TestTypeHelper.CreateCustomResourceType("My.Rp/resA", "2020-01-01", TypeSymbolValidationFlags.WarnOnTypeMismatch, new TypeProperty("readOnlyProp", LanguageConstants.String, TypePropertyFlags.ReadOnly), new TypeProperty("readWriteProp", LanguageConstants.String, TypePropertyFlags.None), new TypeProperty("writeOnlyProp", LanguageConstants.String, TypePropertyFlags.WriteOnly)); var typeLoader = TestTypeHelper.CreateAzResourceTypeLoaderWithTypes(typeDefinition.AsEnumerable()); var(_, _, compilation) = CompilationHelper.Compile(typeLoader, ("main.bicep", bicepFile)); var rewriter = new ReadOnlyPropertyRemovalRewriter(compilation.GetEntrypointSemanticModel()); var newProgramSyntax = rewriter.Rewrite(compilation.SourceFileGrouping.EntryPoint.ProgramSyntax); PrintHelper.PrintAndCheckForParseErrors(newProgramSyntax).Should().Be( @"resource resA 'My.Rp/resA@2020-01-01' = { name: 'resA' properties: { readWriteProp: 'def' writeOnlyProp: 'ghi' } } output myObj object = { readOnlyProp: resA.properties.readOnlyProp readWriteProp: resA.properties.readWriteProp }"); }
public void Only_list_methods_are_permitted() { var result = CompilationHelper.Compile(@" resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' existing = { name: 'testacc' } var allowed = { a: stg.list() b: stg.listA() c: stg.listTotallyMadeUpMethod() } var disallowed = { a: stg.lis() b: stg.lsit() c: stg.totallyMadeUpMethod() } "); result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new[] {
public void TestRuleThrowingExceptionReturnsOneDiagnostic() { var text = @" @secure() param param1 string = 'val'"; var compilationResult = CompilationHelper.Compile(text); var semanticModel = compilationResult.Compilation.GetSemanticModel(compilationResult.BicepFile); var throwRule = new LinterThrowsTestRule(); var diagnostics = throwRule.Analyze(semanticModel); diagnostics.Should().NotBeNullOrEmpty(); diagnostics.Should().HaveCount(1); var diag = diagnostics.First(); diag.Should().NotBeNull(); diag.Code.Should().NotBeNull(); diag.Code.Should().Match("linter-internal-error"); diag.Span.Should().NotBeNull(); diag.Span.Position.Should().Be(0); }
public void Nested_casing_issues_take_multiple_passes_to_correct() { var bicepFile = @" resource resA 'My.Rp/resA@2020-01-01' = { name: 'resA' properties: { lowerCaseObj: { lowerCaseStr: 'test' } } } output myObj object = { lowerCaseProp: resA.properties.lowerCaseObj.lowerCaseStr } "; var typeDefinition = ResourceTypeProviderHelper.CreateCustomResourceType("My.Rp/resA", "2020-01-01", TypeSymbolValidationFlags.WarnOnTypeMismatch, new TypeProperty("lowercaseobj", new NamedObjectType("lowercaseobj", TypeSymbolValidationFlags.Default, new [] { new TypeProperty("lowercasestr", LanguageConstants.String) }, null))); var typeProvider = ResourceTypeProviderHelper.CreateMockTypeProvider(typeDefinition.AsEnumerable()); var(_, _, compilation) = CompilationHelper.Compile(typeProvider, ("main.bicep", bicepFile)); var rewriter = new TypeCasingFixerRewriter(compilation.GetEntrypointSemanticModel()); var newProgramSyntax = rewriter.Rewrite(compilation.SyntaxTreeGrouping.EntryPoint.ProgramSyntax); var firstPassBicepFile = PrintHelper.PrettyPrint(newProgramSyntax); firstPassBicepFile.Should().Be( @"resource resA 'My.Rp/resA@2020-01-01' = { name: 'resA' properties: { lowercaseobj: { lowerCaseStr: 'test' } } } output myObj object = { lowerCaseProp: resA.properties.lowercaseobj.lowerCaseStr }"); (_, _, compilation) = CompilationHelper.Compile(typeProvider, ("main.bicep", firstPassBicepFile)); rewriter = new TypeCasingFixerRewriter(compilation.GetEntrypointSemanticModel()); newProgramSyntax = rewriter.Rewrite(compilation.SyntaxTreeGrouping.EntryPoint.ProgramSyntax); PrintHelper.PrettyPrint(newProgramSyntax).Should().Be( @"resource resA 'My.Rp/resA@2020-01-01' = { name: 'resA' properties: { lowercaseobj: { lowercasestr: 'test' } } } output myObj object = { lowerCaseProp: resA.properties.lowercaseobj.lowercasestr }"); }