Skip to content

Commit 1566e36

Browse files
committed
Merge pull request #1518 from vweijsters/implement-1482
2 parents 6b038b4 + abddc87 commit 1566e36

7 files changed

Lines changed: 390 additions & 3 deletions

File tree

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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.Collections.Generic;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Analyzers.ReadabilityRules;
10+
using Microsoft.CodeAnalysis.CodeFixes;
11+
using Microsoft.CodeAnalysis.Diagnostics;
12+
using TestHelper;
13+
using Xunit;
14+
15+
/// <summary>
16+
/// Unit tests for the <see cref="SA1129DoNotUseDefaultValueTypeConstructor"/> class.
17+
/// </summary>
18+
public class SA1129UnitTests : CodeFixVerifier
19+
{
20+
/// <summary>
21+
/// Verifies that new expressions for reference types will not generate diagnostics.
22+
/// </summary>
23+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
24+
[Fact]
25+
public async Task VerifyReferenceTypeCreationAsync()
26+
{
27+
var testCode = @"public class TestClass
28+
{
29+
public void TestMethod()
30+
{
31+
var v1 = new TestClass2();
32+
var v2 = new TestClass2(1);
33+
}
34+
35+
private class TestClass2
36+
{
37+
public TestClass2()
38+
{
39+
}
40+
41+
public TestClass2(int x)
42+
{
43+
}
44+
}
45+
}
46+
";
47+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
48+
}
49+
50+
/// <summary>
51+
/// Verifies that new expressions for value types with parameters will not generate diagnostics.
52+
/// </summary>
53+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
54+
[Fact]
55+
public async Task VerifyValueTypeWithParametersCreationAsync()
56+
{
57+
var testCode = @"public class TestClass
58+
{
59+
public void TestMethod()
60+
{
61+
var v1 = new TestStruct(1);
62+
var v2 = new TestStruct(1) { TestProperty = 2 };
63+
var v3 = new TestStruct() { TestProperty = 2 };
64+
var v4 = 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+
/// <summary>
187+
/// Verifies that new expressions for value types through generics will generate diagnostics.
188+
/// </summary>
189+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
190+
[Fact]
191+
public async Task VerifyGenericsTypeCreationAsync()
192+
{
193+
var testCode = @"public class TestClass
194+
{
195+
public T TestMethod1<T>()
196+
where T : struct
197+
{
198+
return new T();
199+
}
200+
201+
public T TestMethod2<T>()
202+
where T : new()
203+
{
204+
return new T();
205+
}
206+
}
207+
";
208+
209+
var fixedTestCode = @"public class TestClass
210+
{
211+
public T TestMethod1<T>()
212+
where T : struct
213+
{
214+
return default(T);
215+
}
216+
217+
public T TestMethod2<T>()
218+
where T : new()
219+
{
220+
return new T();
221+
}
222+
}
223+
";
224+
225+
DiagnosticResult[] expected =
226+
{
227+
this.CSharpDiagnostic().WithLocation(6, 16)
228+
};
229+
230+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
231+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
232+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
233+
}
234+
235+
/// <inheritdoc/>
236+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
237+
{
238+
yield return new SA1129DoNotUseDefaultValueTypeConstructor();
239+
}
240+
241+
/// <inheritdoc/>
242+
protected override CodeFixProvider GetCSharpCodeFixProvider()
243+
{
244+
return new SA1129CodeFixProvider();
245+
}
246+
}
247+
}

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)
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 =>
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+
}

0 commit comments

Comments
 (0)