Skip to content

Commit 08fe6d1

Browse files
committed
Implemented analyzer, code fix, and tests
1 parent 6b038b4 commit 08fe6d1

7 files changed

Lines changed: 336 additions & 3 deletions

File tree

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.ReadabilityRules
5+
{
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Analyzers.ReadabilityRules;
11+
using Microsoft.CodeAnalysis.CodeFixes;
12+
using Microsoft.CodeAnalysis.Diagnostics;
13+
using TestHelper;
14+
using Xunit;
15+
16+
/// <summary>
17+
/// Unit tests for the <see cref="SA1129DoNotUseDefaultValueTypeConstructor"/> class.
18+
/// </summary>
19+
public class SA1129UnitTests : CodeFixVerifier
20+
{
21+
/// <summary>
22+
/// Verifies that new expressions for reference types will not generate diagnostics.
23+
/// </summary>
24+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
25+
[Fact]
26+
public async Task VerifyReferenceTypeCreationAsync()
27+
{
28+
var testCode = @"public class TestClass
29+
{
30+
public void TestMethod()
31+
{
32+
var v1 = new TestClass2();
33+
var v2 = new TestClass2(1);
34+
}
35+
36+
private class TestClass2
37+
{
38+
public TestClass2()
39+
{
40+
}
41+
42+
public TestClass2(int x)
43+
{
44+
}
45+
}
46+
}
47+
";
48+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
49+
}
50+
51+
/// <summary>
52+
/// Verifies that new expressions for value types with parameters will not generate diagnostics.
53+
/// </summary>
54+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
55+
[Fact]
56+
public async Task VerifyValueTypeWithParametersCreationAsync()
57+
{
58+
var testCode = @"public class TestClass
59+
{
60+
public void TestMethod()
61+
{
62+
var v1 = new TestStruct(1);
63+
var v2 = new TestStruct(1) { TestProperty = 2 };
64+
var v3 = new TestStruct() { TestProperty = 2 };
65+
}
66+
67+
private struct TestStruct
68+
{
69+
public TestStruct(int x)
70+
{
71+
TestProperty = x;
72+
}
73+
74+
public int TestProperty { get; set; }
75+
}
76+
}
77+
";
78+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
79+
}
80+
81+
/// <summary>
82+
/// Verifies that new expressions for value types without parameters will generate diagnostics.
83+
/// </summary>
84+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
85+
[Fact]
86+
public async Task VerifyInvalidValueTypeCreationAsync()
87+
{
88+
var testCode = @"public class TestClass
89+
{
90+
public void TestMethod()
91+
{
92+
var v1 = new TestStruct();
93+
}
94+
95+
private struct TestStruct
96+
{
97+
public int TestProperty { get; set; }
98+
}
99+
}
100+
";
101+
102+
var fixedTestCode = @"public class TestClass
103+
{
104+
public void TestMethod()
105+
{
106+
var v1 = default(TestStruct);
107+
}
108+
109+
private struct TestStruct
110+
{
111+
public int TestProperty { get; set; }
112+
}
113+
}
114+
";
115+
116+
DiagnosticResult[] expected =
117+
{
118+
this.CSharpDiagnostic().WithLocation(5, 18)
119+
};
120+
121+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
122+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
123+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
124+
}
125+
126+
/// <summary>
127+
/// Verifies that the codefix will preserve surrounding trivia.
128+
/// </summary>
129+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
130+
[Fact]
131+
public async Task VerifyTriviaPreservationAsync()
132+
{
133+
var testCode = @"public class TestClass
134+
{
135+
public void TestMethod()
136+
{
137+
var v1 = /* c1 */ new TestStruct(); // c2
138+
139+
var v2 =
140+
#if true
141+
new TestStruct();
142+
#else
143+
new TestStruct() { TestProperty = 3 };
144+
#endif
145+
}
146+
147+
private struct TestStruct
148+
{
149+
public int TestProperty { get; set; }
150+
}
151+
}
152+
";
153+
154+
var fixedTestCode = @"public class TestClass
155+
{
156+
public void TestMethod()
157+
{
158+
var v1 = /* c1 */ default(TestStruct); // c2
159+
160+
var v2 =
161+
#if true
162+
default(TestStruct);
163+
#else
164+
new TestStruct() { TestProperty = 3 };
165+
#endif
166+
}
167+
168+
private struct TestStruct
169+
{
170+
public int TestProperty { get; set; }
171+
}
172+
}
173+
";
174+
175+
DiagnosticResult[] expected =
176+
{
177+
this.CSharpDiagnostic().WithLocation(5, 27),
178+
this.CSharpDiagnostic().WithLocation(9, 13)
179+
};
180+
181+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
182+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
183+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
184+
}
185+
186+
/// <inheritdoc/>
187+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
188+
{
189+
yield return new SA1129DoNotUseDefaultValueTypeConstructor();
190+
}
191+
192+
/// <inheritdoc/>
193+
protected override CodeFixProvider GetCSharpCodeFixProvider()
194+
{
195+
return new SA1129CodeFixProvider();
196+
}
197+
}
198+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@
303303
<Compile Include="ReadabilityRules\SA1126UnitTests.cs" />
304304
<Compile Include="ReadabilityRules\SA1127UnitTests.cs" />
305305
<Compile Include="ReadabilityRules\SA1128UnitTests.cs" />
306+
<Compile Include="ReadabilityRules\SA1129UnitTests.cs" />
306307
<Compile Include="ReadabilityRules\SA1130UnitTests.cs" />
307308
<Compile Include="ReadabilityRules\SA1132UnitTests.cs" />
308309
<Compile Include="Settings\SettingsFileCodeFixProviderUnitTests.cs" />

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/ReadabilityResources.Designer.cs

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/ReadabilityResources.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,11 +411,14 @@
411411
<data name="SA1128Title" xml:space="preserve">
412412
<value>Put constructor initializers on their own line</value>
413413
</data>
414+
<data name="SA1129CodeFix" xml:space="preserve">
415+
<value>Replace with default(T)</value>
416+
</data>
414417
<data name="SA1129Description" xml:space="preserve">
415418
<value>When creating a new instance of a value type T, the syntax 'default(T)' is functionally equivalent to the syntax 'new T()'. To avoid confusion regarding the behavior of the resulting instance, the first form is preferred.</value>
416419
</data>
417420
<data name="SA1129MessageFormat" xml:space="preserve">
418-
<value />
421+
<value>Do not use default value type constructor</value>
419422
</data>
420423
<data name="SA1129Title" xml:space="preserve">
421424
<value>Do not use default value type constructor</value>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.ReadabilityRules
5+
{
6+
using System.Collections.Immutable;
7+
using System.Composition;
8+
using System.Linq;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Helpers;
12+
using Microsoft.CodeAnalysis;
13+
using Microsoft.CodeAnalysis.CodeActions;
14+
using Microsoft.CodeAnalysis.CodeFixes;
15+
using Microsoft.CodeAnalysis.CSharp;
16+
using Microsoft.CodeAnalysis.CSharp.Syntax;
17+
18+
/// <summary>
19+
/// Implements a code fix for <see cref="SA1129DoNotUseDefaultValueTypeConstructor"/>.
20+
/// </summary>
21+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1129CodeFixProvider))]
22+
[Shared]
23+
internal class SA1129CodeFixProvider : CodeFixProvider
24+
{
25+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
26+
ImmutableArray.Create(SA1129DoNotUseDefaultValueTypeConstructor.DiagnosticId);
27+
28+
/// <inheritdoc/>
29+
public override FixAllProvider GetFixAllProvider()
30+
{
31+
return FixAll.Instance;
32+
}
33+
34+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
35+
{
36+
foreach (var diagnostic in context.Diagnostics.Where(d => this.FixableDiagnosticIds.Contains(d.Id)))
37+
{
38+
context.RegisterCodeFix(
39+
CodeAction.Create(
40+
ReadabilityResources.SA1129CodeFix,
41+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
42+
equivalenceKey: nameof(SA1129CodeFixProvider)),
43+
diagnostic);
44+
}
45+
46+
return SpecializedTasks.CompletedTask;
47+
}
48+
49+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
50+
{
51+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
52+
53+
var newExpression = syntaxRoot.FindNode(diagnostic.Location.SourceSpan);
54+
var newSyntaxRoot = syntaxRoot.ReplaceNode(newExpression, GetReplacementNode(newExpression));
55+
return document.WithSyntaxRoot(newSyntaxRoot);
56+
}
57+
58+
private static SyntaxNode GetReplacementNode(SyntaxNode node)
59+
{
60+
var newExpression = (ObjectCreationExpressionSyntax)node;
61+
62+
return SyntaxFactory.DefaultExpression(newExpression.Type)
63+
.WithLeadingTrivia(newExpression.GetLeadingTrivia())
64+
.WithTrailingTrivia(newExpression.GetTrailingTrivia());
65+
}
66+
67+
private class FixAll : DocumentBasedFixAllProvider
68+
{
69+
public static FixAllProvider Instance { get; } =
70+
new FixAll();
71+
72+
protected override string CodeActionTitle { get; } =
73+
ReadabilityResources.SA1129CodeFix;
74+
75+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
76+
{
77+
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
78+
if (diagnostics.IsEmpty)
79+
{
80+
return null;
81+
}
82+
83+
var syntaxRoot = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
84+
85+
var nodes = diagnostics.Select(diagnostic => syntaxRoot.FindNode(diagnostic.Location.SourceSpan));
86+
87+
return syntaxRoot.ReplaceNodes(nodes, (originalNode, rewrittenNode) => GetReplacementNode(rewrittenNode));
88+
}
89+
}
90+
}
91+
}

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1129DoNotUseDefaultValueTypeConstructor.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace StyleCop.Analyzers.ReadabilityRules
55
{
66
using System.Collections.Immutable;
77
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
810
using Microsoft.CodeAnalysis.Diagnostics;
911

1012
/// <summary>
@@ -32,7 +34,35 @@ internal class SA1129DoNotUseDefaultValueTypeConstructor : DiagnosticAnalyzer
3234
/// <inheritdoc/>
3335
public override void Initialize(AnalysisContext context)
3436
{
35-
// TODO: Implement analysis
37+
context.RegisterCompilationStartAction(HandleCompilationStart);
38+
}
39+
40+
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
41+
{
42+
context.RegisterSyntaxNodeActionHonorExclusions(HandleNewExpression, SyntaxKind.ObjectCreationExpression);
43+
}
44+
45+
private static void HandleNewExpression(SyntaxNodeAnalysisContext context)
46+
{
47+
ObjectCreationExpressionSyntax newExpression = (ObjectCreationExpressionSyntax)context.Node;
48+
49+
var typeToCreate = context.SemanticModel.GetTypeInfo(newExpression, context.CancellationToken);
50+
if (typeToCreate.Type.IsReferenceType)
51+
{
52+
return;
53+
}
54+
55+
if (newExpression.ArgumentList.Arguments.Count > 0)
56+
{
57+
return;
58+
}
59+
60+
if (newExpression.Initializer != null)
61+
{
62+
return;
63+
}
64+
65+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, newExpression.GetLocation()));
3666
}
3767
}
3868
}

StyleCop.Analyzers/StyleCop.Analyzers/StyleCop.Analyzers.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@
322322
<Compile Include="ReadabilityRules\SA1127GenericTypeConstraintsMustBeOnOwnLine.cs" />
323323
<Compile Include="ReadabilityRules\SA1128CodeFixProvider.cs" />
324324
<Compile Include="ReadabilityRules\SA1128ConstructorInitializerMustBeOnOwnLine.cs" />
325+
<Compile Include="ReadabilityRules\SA1129CodeFixProvider.cs" />
325326
<Compile Include="ReadabilityRules\SA1129DoNotUseDefaultValueTypeConstructor.cs" />
326327
<Compile Include="ReadabilityRules\SA1130UseLambdaSyntax.cs" />
327328
<Compile Include="ReadabilityRules\SA1131UseReadableConditions.cs" />

0 commit comments

Comments
 (0)