Skip to content

Commit e17f6ee

Browse files
authored
Add a codefix for BCP035 (Azure#4570)
* pakrym/BCP035-code-fix * rename test * fix tests * Use az resource types in StartServerWithTextAsync * Feedback * Fix after merge
1 parent 93c893e commit e17f6ee

File tree

12 files changed

+290
-85
lines changed

12 files changed

+290
-85
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ $tf/
130130
_ReSharper*/
131131
*.[Rr]e[Ss]harper
132132
*.DotSettings.user
133+
.idea
133134

134135
# TeamCity is a build add-in
135136
_TeamCity*

src/Bicep.Core.IntegrationTests/ParserTests.cs

+2-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.IO;
88
using System.Text;
99
using Bicep.Core.Extensions;
10+
using Bicep.Core.Navigation;
1011
using Bicep.Core.UnitTests.Assertions;
1112
using Bicep.Core.Parsing;
1213
using Bicep.Core.Samples;
@@ -134,12 +135,7 @@ private static void RunRoundTripTest(string contents)
134135
var program = ParserHelper.Parse(contents);
135136
program.Should().BeOfType<ProgramSyntax>();
136137

137-
var buffer = new StringBuilder();
138-
var visitor = new PrintVisitor(buffer);
139-
140-
visitor.Visit(program);
141-
142-
buffer.ToString().Should().Be(contents);
138+
program.ToTextPreserveFormatting().Should().Be(contents);
143139
}
144140

145141
private static void RunSpanConsistencyTest(string text)

src/Bicep.Core.IntegrationTests/PrettyPrint/PrettyPrinterTests.cs

+6-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.IO;
66
using System.Linq;
77
using System.Text;
8+
using System.Text.RegularExpressions;
9+
using Bicep.Core.Navigation;
810
using Bicep.Core.Parsing;
911
using Bicep.Core.PrettyPrint;
1012
using Bicep.Core.PrettyPrint.Options;
@@ -62,18 +64,10 @@ public void PrintProgram_AnyProgram_ShouldRoundTrip(DataSet dataSet)
6264
newDiagnostics.Should().HaveSameCount(diagnostics);
6365
newDiagnosticMessages.Should().BeEquivalentTo(diagnosticMessages);
6466

65-
var buffer = new StringBuilder();
66-
var printVisitor = new PrintVisitor(buffer,x =>
67-
// Remove newlines and whitespaces.
68-
(x is Token token && token.Type == TokenType.NewLine) ||
69-
(x is SyntaxTrivia trivia && trivia.Type == SyntaxTriviaType.Whitespace));
70-
71-
printVisitor.Visit(program);
72-
string programText = buffer.ToString();
73-
74-
buffer.Clear();
75-
printVisitor.Visit(program);
76-
string formattedProgramText = buffer.ToString();
67+
// Normalize formatting
68+
var regex = new Regex("[\\r\\n\\s]+");
69+
string programText = regex.Replace(program.ToTextPreserveFormatting(), "");
70+
string formattedProgramText = regex.Replace(formattedProgram.ToTextPreserveFormatting(), "");
7771

7872
formattedProgramText.Should().Be(programText);
7973
}

src/Bicep.Core.UnitTests/Diagnostics/ErrorBuilderTests.cs

+117
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
using System.Collections.Immutable;
1313
using System.Linq;
1414
using System.Reflection;
15+
using Bicep.Core.Syntax;
16+
using Bicep.Core.UnitTests.Assertions;
17+
using Bicep.Core.UnitTests.Utils;
1518

1619
namespace Bicep.Core.UnitTests.Diagnostics
1720
{
@@ -155,7 +158,121 @@ private static object CreateMockParameter(ParameterInfo parameter, int index)
155158
return $"<param_{index}>";
156159
}
157160

161+
if (parameter.ParameterType == typeof(ObjectSyntax))
162+
{
163+
return TestSyntaxFactory.CreateObject(Array.Empty<ObjectPropertySyntax>());
164+
}
165+
158166
throw new AssertFailedException($"Unable to generate mock parameter value of type '{parameter.ParameterType}' for the diagnostic builder method.");
159167
}
168+
169+
private void ExpectDiagnosticWithFixedText(string text, string expectedText)
170+
{
171+
var result = CompilationHelper.Compile(text);
172+
result.Diagnostics.Should().HaveCount(1);
173+
174+
FixableDiagnostic diagnostic = (FixableDiagnostic)result.Diagnostics.Single();
175+
diagnostic.Code.Should().Be("BCP035");
176+
diagnostic.Fixes.Should().HaveCount(1);
177+
178+
var fix = diagnostic.Fixes.Single();
179+
fix.Replacements.Should().HaveCount(1);
180+
181+
var replacement = fix.Replacements.Single();
182+
183+
var actualText = text.Remove(replacement.Span.Position, replacement.Span.Length);
184+
actualText = actualText.Insert(replacement.Span.Position, replacement.Text);
185+
186+
// Normalize line endings
187+
expectedText = expectedText.Replace("\r\n", "\n").Replace("\n", Environment.NewLine);
188+
actualText = actualText.Replace("\r\n", "\n").Replace("\n", Environment.NewLine);
189+
190+
actualText.Should().Be(expectedText);
191+
}
192+
193+
[DataRow(@"
194+
resource vnet 'Microsoft.Network/virtualNetworks@2018-10-01' = {
195+
}",
196+
@"
197+
resource vnet 'Microsoft.Network/virtualNetworks@2018-10-01' = {
198+
name:
199+
}"
200+
)]
201+
[DataRow(@"
202+
resource vnet 'Microsoft.Network/virtualNetworks@2018-10-01' = {
203+
204+
}",
205+
@"
206+
resource vnet 'Microsoft.Network/virtualNetworks@2018-10-01' = {
207+
name:
208+
}"
209+
)]
210+
// There is leading whitespace in this one
211+
[DataRow(@"
212+
resource vnet 'Microsoft.Network/virtualNetworks@2018-10-01' = {
213+
214+
}",
215+
@"
216+
resource vnet 'Microsoft.Network/virtualNetworks@2018-10-01' = {
217+
name:
218+
}"
219+
)]
220+
[DataRow(@"
221+
resource vnet 'Microsoft.Network/virtualNetworks@2018-10-01' = {
222+
location: 'westus2'
223+
}",
224+
@"
225+
resource vnet 'Microsoft.Network/virtualNetworks@2018-10-01' = {
226+
location: 'westus2'
227+
name:
228+
}"
229+
)]
230+
[DataRow(@"
231+
resource vnet 'Microsoft.Network/virtualNetworks@2018-10-01' = {
232+
location: 'westus2'
233+
}",
234+
@"
235+
resource vnet 'Microsoft.Network/virtualNetworks@2018-10-01' = {
236+
location: 'westus2'
237+
name:
238+
}"
239+
)]
240+
[DataRow(@"
241+
resource appService 'Microsoft.Web/serverFarms@2020-06-01' = {
242+
sku: {
243+
244+
name: 'D1'
245+
246+
}
247+
// comment
248+
}",
249+
@"
250+
resource appService 'Microsoft.Web/serverFarms@2020-06-01' = {
251+
sku: {
252+
253+
name: 'D1'
254+
255+
}
256+
// comment
257+
location:
258+
name:
259+
}"
260+
)]
261+
[DataRow(@"
262+
resource appService 'Microsoft.Web/serverFarms@2020-06-01' = {
263+
sku: {}
264+
}",
265+
@"
266+
resource appService 'Microsoft.Web/serverFarms@2020-06-01' = {
267+
sku: {}
268+
location:
269+
name:
270+
}"
271+
)]
272+
[DataTestMethod]
273+
public void MissingTypePropertiesHasFix(string text, string expectedFix)
274+
{
275+
ExpectDiagnosticWithFixedText(text, expectedFix);
276+
}
160277
}
161278
}

src/Bicep.Core.UnitTests/Utils/PrintHelper.cs

+2-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Text;
8+
using Bicep.Core.Navigation;
89
using Bicep.Core.Parsing;
910
using Bicep.Core.PrettyPrint;
1011
using Bicep.Core.PrettyPrint.Options;
@@ -46,19 +47,9 @@ public Annotation(TextSpan span, string message)
4647
public string Message { get; }
4748
}
4849

49-
private static string GetProgramText(BicepFile bicepFile)
50-
{
51-
var buffer = new StringBuilder();
52-
var visitor = new PrintVisitor(buffer);
53-
54-
visitor.Visit(bicepFile.ProgramSyntax);
55-
56-
return buffer.ToString();
57-
}
58-
5950
private static string[] GetProgramTextLines(BicepFile bicepFile)
6051
{
61-
var programText = GetProgramText(bicepFile);
52+
var programText = bicepFile.ProgramSyntax.ToTextPreserveFormatting();
6253

6354
return StringUtils.ReplaceNewlines(programText, "\n").Split("\n");
6455
}

src/Bicep.Core.UnitTests/Utils/PrintVisitor.cs

-51
This file was deleted.

src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs

+13-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Bicep.Core.CodeAction;
1111
using Bicep.Core.Extensions;
1212
using Bicep.Core.Modules;
13+
using Bicep.Core.Navigation;
1314
using Bicep.Core.Parsing;
1415
using Bicep.Core.Resources;
1516
using Bicep.Core.Semantics;
@@ -219,17 +220,26 @@ private static string BuildAccessiblePropertiesClause(string? accessedSymbolName
219220
"BCP034",
220221
$"The enclosing array expected an item of type \"{expectedType}\", but the provided item was of type \"{actualType}\".");
221222

222-
public Diagnostic MissingRequiredProperties(bool warnInsteadOfError, Symbol? sourceDeclaration, IEnumerable<string> properties, string blockName)
223+
public Diagnostic MissingRequiredProperties(bool warnInsteadOfError, Symbol? sourceDeclaration, ObjectSyntax objectSyntax, IEnumerable<string> properties, string blockName)
223224
{
224225
var sourceDeclarationClause = sourceDeclaration is not null
225226
? $" from source declaration \"{sourceDeclaration.Name}\""
226227
: string.Empty;
227228

228-
return new(
229+
var newSyntax = objectSyntax.AddChildrenWithFormatting(
230+
properties.Select(p => SyntaxFactory.CreateObjectProperty(p, SyntaxFactory.EmptySkippedTrivia))
231+
);
232+
233+
var codeFix = new CodeFix("Add required properties", true, new CodeReplacement(objectSyntax.Span, newSyntax.ToTextPreserveFormatting()));
234+
235+
return new FixableDiagnostic(
229236
TextSpan,
230237
warnInsteadOfError ? DiagnosticLevel.Warning : DiagnosticLevel.Error,
231238
"BCP035",
232-
$"The specified \"{blockName}\" declaration is missing the following required properties{sourceDeclarationClause}: {ToQuotedString(properties)}.");
239+
$"The specified \"{blockName}\" declaration is missing the following required properties{sourceDeclarationClause}: {ToQuotedString(properties)}.",
240+
null,
241+
null,
242+
codeFix);
233243
}
234244

235245
public Diagnostic PropertyTypeMismatch(bool warnInsteadOfError, Symbol? sourceDeclaration, string property, TypeSymbol expectedType, TypeSymbol actualType)

src/Bicep.Core/Navigation/SyntaxBaseExtensions.cs

+38
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33
using System;
4+
using System.Collections.Generic;
45
using System.Text;
6+
using Bicep.Core.Parsing;
57
using Bicep.Core.PrettyPrint;
68
using Bicep.Core.Syntax;
79

@@ -78,6 +80,42 @@ public static string ToText(this SyntaxBase syntax, string indent="")
7880
document.Layout(sb, indent, System.Environment.NewLine);
7981
return sb.ToString();
8082
}
83+
84+
/// <summary>
85+
/// Generate a string that represents this Syntax element.
86+
/// </summary>
87+
public static string ToTextPreserveFormatting(this SyntaxBase syntax)
88+
{
89+
var sb = new StringBuilder();
90+
var printVisitor = new PrintVisitor(sb);
91+
printVisitor.Visit(syntax);
92+
return sb.ToString();
93+
}
94+
95+
private class PrintVisitor : SyntaxVisitor
96+
{
97+
private readonly StringBuilder buffer;
98+
99+
public PrintVisitor(StringBuilder buffer)
100+
{
101+
this.buffer = buffer;
102+
}
103+
104+
public override void VisitToken(Token token)
105+
{
106+
WriteTrivia(token.LeadingTrivia);
107+
buffer.Append(token.Text);
108+
WriteTrivia(token.TrailingTrivia);
109+
}
110+
111+
private void WriteTrivia(IEnumerable<SyntaxTrivia> triviaList)
112+
{
113+
foreach (var trivia in triviaList)
114+
{
115+
buffer.Append(trivia.Text);
116+
}
117+
}
118+
}
81119
}
82120
}
83121

0 commit comments

Comments
 (0)