Skip to content

Commit 2c7c70c

Browse files
committed
Merge pull request #1649 from vweijsters/SX1101
2 parents e7659d9 + d290121 commit 2c7c70c

10 files changed

Lines changed: 502 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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.Generic;
7+
using System.Collections.Immutable;
8+
using System.Composition;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Microsoft.CodeAnalysis;
12+
using Microsoft.CodeAnalysis.CodeActions;
13+
using Microsoft.CodeAnalysis.CodeFixes;
14+
using Microsoft.CodeAnalysis.CSharp.Syntax;
15+
using StyleCop.Analyzers.Helpers;
16+
17+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SX1101CodeFixProvider))]
18+
[Shared]
19+
internal class SX1101CodeFixProvider : CodeFixProvider
20+
{
21+
/// <inheritdoc/>
22+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
23+
ImmutableArray.Create(SX1101DoNotPrefixLocalMembersWithThis.DiagnosticId);
24+
25+
/// <inheritdoc/>
26+
public override FixAllProvider GetFixAllProvider()
27+
{
28+
return FixAll.Instance;
29+
}
30+
31+
/// <inheritdoc/>
32+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
33+
{
34+
foreach (var diagnostic in context.Diagnostics)
35+
{
36+
context.RegisterCodeFix(
37+
CodeAction.Create(
38+
ReadabilityResources.SX1101CodeFix,
39+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
40+
nameof(SX1101CodeFixProvider)),
41+
diagnostic);
42+
}
43+
44+
return SpecializedTasks.CompletedTask;
45+
}
46+
47+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
48+
{
49+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
50+
51+
var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true) as ThisExpressionSyntax;
52+
if (node == null)
53+
{
54+
return document;
55+
}
56+
57+
var replacementNode = GenerateReplacementNode(node);
58+
var newSyntaxRoot = syntaxRoot.ReplaceNode(node.Parent, replacementNode);
59+
return document.WithSyntaxRoot(newSyntaxRoot);
60+
}
61+
62+
private static SyntaxNode GenerateReplacementNode(ThisExpressionSyntax node)
63+
{
64+
var parent = (MemberAccessExpressionSyntax)node.Parent;
65+
return parent.Name.WithTriviaFrom(parent);
66+
}
67+
68+
private class FixAll : DocumentBasedFixAllProvider
69+
{
70+
public static FixAllProvider Instance { get; } =
71+
new FixAll();
72+
73+
protected override string CodeActionTitle =>
74+
ReadabilityResources.SX1101CodeFix;
75+
76+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
77+
{
78+
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
79+
if (diagnostics.IsEmpty)
80+
{
81+
return null;
82+
}
83+
84+
SyntaxNode syntaxRoot = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
85+
86+
var replaceMap = new Dictionary<SyntaxNode, SyntaxNode>();
87+
88+
foreach (Diagnostic diagnostic in diagnostics)
89+
{
90+
var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan, false, true) as ThisExpressionSyntax;
91+
if (node == null || node.IsMissing)
92+
{
93+
continue;
94+
}
95+
96+
replaceMap[node.Parent] = GenerateReplacementNode(node);
97+
}
98+
99+
return syntaxRoot.ReplaceNodes(replaceMap.Keys, (originalNode, rewrittenNode) => replaceMap[originalNode]);
100+
}
101+
}
102+
}
103+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
<Compile Include="ReadabilityRules\SA1129CodeFixProvider.cs" />
126126
<Compile Include="ReadabilityRules\SA1131CodeFixProvider.cs" />
127127
<Compile Include="ReadabilityRules\SA1132CodeFixProvider.cs" />
128+
<Compile Include="ReadabilityRules\SX1101CodeFixProvider.cs" />
128129
<Compile Include="Settings\SettingsFileCodeFixProvider.cs" />
129130
<Compile Include="SpacingRules\SA1003CodeFixProvider.cs" />
130131
<Compile Include="SpacingRules\SA1004CodeFixProvider.cs" />
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
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 Microsoft.CodeAnalysis.CodeFixes;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using StyleCop.Analyzers.ReadabilityRules;
12+
using TestHelper;
13+
using Xunit;
14+
15+
public class SX1101UnitTests : CodeFixVerifier
16+
{
17+
/// <summary>
18+
/// Verifies that this prefixes are detected and removed.
19+
/// </summary>
20+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
21+
[Fact]
22+
public async Task VerifyThisPrefixesAreDetectedAndRemovedAsync()
23+
{
24+
var testCode = @"using System;
25+
26+
public class BaseTestClass
27+
{
28+
protected int BaseTestField;
29+
30+
protected int BaseTestProperty { get; set; }
31+
32+
public event EventHandler BaseTestEvent;
33+
34+
protected void BaseTestMethod()
35+
{
36+
}
37+
}
38+
39+
public class TestClass : BaseTestClass
40+
{
41+
protected int TestField;
42+
43+
public int TestProperty { get; set; }
44+
45+
public event EventHandler TestEvent;
46+
47+
public void TestMethod1()
48+
{
49+
}
50+
51+
public void TestMethod2()
52+
{
53+
if ((this.BaseTestProperty == this.TestProperty) && (this.BaseTestField == this.TestField))
54+
{
55+
this.BaseTestMethod();
56+
}
57+
else
58+
{
59+
this.TestMethod1();
60+
}
61+
62+
this.BaseTestEvent += (s, e) => { };
63+
var listeners = this.TestEvent;
64+
if (listeners != null)
65+
{
66+
listeners(this, null);
67+
}
68+
}
69+
}
70+
";
71+
72+
var fixedTestCode = @"using System;
73+
74+
public class BaseTestClass
75+
{
76+
protected int BaseTestField;
77+
78+
protected int BaseTestProperty { get; set; }
79+
80+
public event EventHandler BaseTestEvent;
81+
82+
protected void BaseTestMethod()
83+
{
84+
}
85+
}
86+
87+
public class TestClass : BaseTestClass
88+
{
89+
protected int TestField;
90+
91+
public int TestProperty { get; set; }
92+
93+
public event EventHandler TestEvent;
94+
95+
public void TestMethod1()
96+
{
97+
}
98+
99+
public void TestMethod2()
100+
{
101+
if ((BaseTestProperty == TestProperty) && (BaseTestField == TestField))
102+
{
103+
BaseTestMethod();
104+
}
105+
else
106+
{
107+
TestMethod1();
108+
}
109+
110+
BaseTestEvent += (s, e) => { };
111+
var listeners = TestEvent;
112+
if (listeners != null)
113+
{
114+
listeners(this, null);
115+
}
116+
}
117+
}
118+
";
119+
120+
DiagnosticResult[] expected =
121+
{
122+
this.CSharpDiagnostic().WithLocation(30, 14),
123+
this.CSharpDiagnostic().WithLocation(30, 39),
124+
this.CSharpDiagnostic().WithLocation(30, 62),
125+
this.CSharpDiagnostic().WithLocation(30, 84),
126+
this.CSharpDiagnostic().WithLocation(32, 13),
127+
this.CSharpDiagnostic().WithLocation(36, 13),
128+
this.CSharpDiagnostic().WithLocation(39, 9),
129+
this.CSharpDiagnostic().WithLocation(40, 25)
130+
};
131+
132+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
133+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
134+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
135+
}
136+
137+
/// <summary>
138+
/// Verifies that a constructor call using the 'this(...)' will not produce any diagnostics.
139+
/// </summary>
140+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
141+
[Fact]
142+
public async Task VerifyThatConstructorCallsDoNotProduceDiagnosticsAsync()
143+
{
144+
var testCode = @"
145+
public class TestClass
146+
{
147+
private int _x;
148+
149+
public TestClass(int x)
150+
{
151+
_x = x;
152+
}
153+
154+
public TestClass()
155+
: this(1)
156+
{
157+
}
158+
}
159+
";
160+
161+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
162+
}
163+
164+
/// <summary>
165+
/// Verifies that extension methods will not produce any diagnostics.
166+
/// </summary>
167+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
168+
[Fact]
169+
public async Task VerifyThatExtensionMethodsDoNotProduceDiagnosticsAsync()
170+
{
171+
var testCode = @"
172+
public static class TestClass
173+
{
174+
public static void TestExtensionMethod(this System.AppDomain appDomain)
175+
{
176+
}
177+
}
178+
";
179+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
180+
}
181+
182+
/// <summary>
183+
/// Verifies indexers will not produce any diagnostics.
184+
/// </summary>
185+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
186+
[Fact]
187+
public async Task VerifyThatIndexersDoNotProduceDiagnosticsAsync()
188+
{
189+
var testCode = @"
190+
public class TestClass
191+
{
192+
public int this[int index]
193+
{
194+
get { return index; }
195+
}
196+
}
197+
";
198+
199+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
200+
}
201+
202+
/// <summary>
203+
/// Verifies that a necessary this prefix will not produce any diagnostics.
204+
/// </summary>
205+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
206+
[Fact]
207+
public async Task VerifyThatNecessaryPrefixWillNotProduceDiagnosticsAsync()
208+
{
209+
var testCode = @"
210+
public class TestClass
211+
{
212+
private int test;
213+
214+
public void TestMethod(int test)
215+
{
216+
this.test = test;
217+
}
218+
}
219+
";
220+
221+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
222+
}
223+
224+
/// <inheritdoc/>
225+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
226+
{
227+
yield return new SX1101DoNotPrefixLocalMembersWithThis();
228+
}
229+
230+
/// <inheritdoc/>
231+
protected override CodeFixProvider GetCSharpCodeFixProvider()
232+
{
233+
return new SX1101CodeFixProvider();
234+
}
235+
}
236+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@
315315
<Compile Include="ReadabilityRules\SA1130UnitTests.cs" />
316316
<Compile Include="ReadabilityRules\SA1131UnitTests.cs" />
317317
<Compile Include="ReadabilityRules\SA1132UnitTests.cs" />
318+
<Compile Include="ReadabilityRules\SX1101UnitTests.cs" />
318319
<Compile Include="Settings\SettingsFileCodeFixProviderUnitTests.cs" />
319320
<Compile Include="Settings\SettingsUnitTests.cs" />
320321
<Compile Include="SpacingRules\NumberSignSpacingTestBase.cs" />

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

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)