Skip to content

Commit afba2b3

Browse files
committed
Implemented SA1316
1 parent e5252da commit afba2b3

17 files changed

Lines changed: 739 additions & 24 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.NamingRules
5+
{
6+
using System.Collections.Immutable;
7+
using System.Composition;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.CodeActions;
12+
using Microsoft.CodeAnalysis.CodeFixes;
13+
using Microsoft.CodeAnalysis.CSharp;
14+
using StyleCop.Analyzers.Helpers;
15+
16+
/// <summary>
17+
/// Implements a code fix for <see cref="SA1316TupleFieldNamesMustUseCorrectCasing"/>.
18+
/// </summary>
19+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1316CodeFixProvider))]
20+
[Shared]
21+
internal class SA1316CodeFixProvider : CodeFixProvider
22+
{
23+
/// <inheritdoc/>
24+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
25+
ImmutableArray.Create(SA1316TupleFieldNamesMustUseCorrectCasing.DiagnosticId);
26+
27+
/// <inheritdoc/>
28+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
29+
{
30+
foreach (var diagnostic in context.Diagnostics)
31+
{
32+
if (diagnostic.Properties.TryGetValue(SA1316TupleFieldNamesMustUseCorrectCasing.ExpectedTupleFieldNameKey, out string fixedTupleFieldName))
33+
{
34+
context.RegisterCodeFix(
35+
CodeAction.Create(
36+
NamingResources.SA1316CodeFix,
37+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, fixedTupleFieldName, cancellationToken),
38+
nameof(SA1316CodeFixProvider)),
39+
diagnostic);
40+
}
41+
}
42+
43+
return SpecializedTasks.CompletedTask;
44+
}
45+
46+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, string fixedTupleFieldName, CancellationToken cancellationToken)
47+
{
48+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
49+
50+
var identifierToken = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start);
51+
52+
var newSyntaxRoot = syntaxRoot.ReplaceToken(identifierToken, SyntaxFactory.Identifier(fixedTupleFieldName).WithTriviaFrom(identifierToken));
53+
return document.WithSyntaxRoot(newSyntaxRoot);
54+
}
55+
}
56+
}
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
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.CSharp7.NamingRules
5+
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.Testing;
9+
using StyleCop.Analyzers.Lightup;
10+
using StyleCop.Analyzers.NamingRules;
11+
using Xunit;
12+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
13+
StyleCop.Analyzers.NamingRules.SA1316TupleFieldNamesMustUseCorrectCasing,
14+
StyleCop.Analyzers.NamingRules.SA1316CodeFixProvider>;
15+
16+
/// <summary>
17+
/// This class contains the CSharp 7.x unit tests for SA1316.
18+
/// </summary>
19+
/// <seealso cref="SA1316TupleFieldNamesMustUseCorrectCasing"/>
20+
/// <seealso cref="SA1316CodeFixProvider"/>
21+
public class SA1316CSharp7UnitTests
22+
{
23+
private const string DefaultTestSettings = @"
24+
{
25+
""settings"": {
26+
}
27+
}
28+
";
29+
30+
private const string CamelCaseTestSettings = @"
31+
{
32+
""settings"": {
33+
""namingRules"": {
34+
""tupleFieldNameCasing"": ""camelCase""
35+
}
36+
}
37+
}
38+
";
39+
40+
private const string PascalCaseTestSettings = @"
41+
{
42+
""settings"": {
43+
""namingRules"": {
44+
""tupleFieldNameCasing"": ""pascalCase""
45+
}
46+
}
47+
}
48+
";
49+
50+
private const string CamelCaseInferredTestSettings = @"
51+
{
52+
""settings"": {
53+
""namingRules"": {
54+
""includeInferredTupleFieldNames"" : true,
55+
""tupleFieldNameCasing"": ""camelCase""
56+
}
57+
}
58+
}
59+
";
60+
61+
private const string PascalCaseInferredTestSettings = @"
62+
{
63+
""settings"": {
64+
""namingRules"": {
65+
""includeInferredTupleFieldNames"" : true,
66+
""tupleFieldNameCasing"": ""pascalCase""
67+
}
68+
}
69+
}
70+
";
71+
72+
private const string CamelCaseExplicitOnlyTestSettings = @"
73+
{
74+
""settings"": {
75+
""namingRules"": {
76+
""includeInferredTupleFieldNames"" : false,
77+
""tupleFieldNameCasing"": ""camelCase""
78+
}
79+
}
80+
}
81+
";
82+
83+
private const string PascalCaseExplicitOnlyTestSettings = @"
84+
{
85+
""settings"": {
86+
""namingRules"": {
87+
""includeInferredTupleFieldNames"" : false,
88+
""tupleFieldNameCasing"": ""pascalCase""
89+
}
90+
}
91+
}
92+
";
93+
94+
/// <summary>
95+
/// Validates the properly named tuple field names will not produce diagnostics.
96+
/// </summary>
97+
/// <param name="settings">The test settings to use.</param>
98+
/// <param name="tupleFieldName1">The expected tuple field name for the first field.</param>
99+
/// <param name="tupleFieldName2">The expected tuple field name for the second field.</param>
100+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
101+
[Theory]
102+
[InlineData(DefaultTestSettings, "fieldName1", "fieldName2")]
103+
[InlineData(CamelCaseTestSettings, "fieldName1", "fieldName2")]
104+
[InlineData(PascalCaseTestSettings, "FieldName1", "FieldName2")]
105+
public async Task ValidateProperCasedTupleFieldNamesAsync(string settings, string tupleFieldName1, string tupleFieldName2)
106+
{
107+
var testCode = $@"
108+
public class TestClass
109+
{{
110+
public (int {tupleFieldName1}, int {tupleFieldName2}) TestMethod()
111+
{{
112+
return (1, 1);
113+
}}
114+
}}
115+
";
116+
117+
await VerifyCSharpDiagnosticAsync(LanguageVersionEx.CSharp7, testCode, settings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
118+
}
119+
120+
/// <summary>
121+
/// Validates that tuple fields with no name will not produce diagnostics.
122+
/// </summary>
123+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
124+
[Fact]
125+
public async Task ValidateNoTupleFieldNamesAsync()
126+
{
127+
var testCode = @"
128+
public class TestClass
129+
{
130+
public (int, int) TestMethod()
131+
{
132+
return (1, 1);
133+
}
134+
}
135+
";
136+
137+
await VerifyCSharpDiagnosticAsync(LanguageVersionEx.CSharp7, testCode, DefaultTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
138+
}
139+
140+
/// <summary>
141+
/// Validates the properly named inferred tuple field names will not produce diagnostics.
142+
/// </summary>
143+
/// <param name="settings">The test settings to use.</param>
144+
/// <param name="tupleFieldName1">The expected tuple field name for the first field.</param>
145+
/// <param name="tupleFieldName2">The expected tuple field name for the second field.</param>
146+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
147+
[Theory]
148+
[InlineData(DefaultTestSettings, "fieldName1", "fieldName2")]
149+
[InlineData(CamelCaseTestSettings, "fieldName1", "fieldName2")]
150+
[InlineData(PascalCaseTestSettings, "FieldName1", "FieldName2")]
151+
[InlineData(CamelCaseInferredTestSettings, "fieldName1", "fieldName2")]
152+
[InlineData(PascalCaseInferredTestSettings, "FieldName1", "FieldName2")]
153+
[InlineData(CamelCaseExplicitOnlyTestSettings, "fieldName1", "fieldName2")]
154+
[InlineData(PascalCaseExplicitOnlyTestSettings, "FieldName1", "FieldName2")]
155+
public async Task ValidateProperCasedInferredTupleFieldNamesAsync(string settings, string tupleFieldName1, string tupleFieldName2)
156+
{
157+
var testCode = $@"
158+
public class TestClass
159+
{{
160+
public void TestMethod()
161+
{{
162+
var {tupleFieldName1} = 1;
163+
var {tupleFieldName2} = ""test"";
164+
var tuple = ({tupleFieldName1}, {tupleFieldName2});
165+
}}
166+
}}
167+
";
168+
169+
await VerifyCSharpDiagnosticAsync(LanguageVersionEx.CSharp7_1, testCode, settings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
170+
}
171+
172+
/// <summary>
173+
/// Validates the improperly named tuple field names will produce the expected diagnostics.
174+
/// </summary>
175+
/// <param name="settings">The test settings to use.</param>
176+
/// <param name="tupleFieldName1">The expected tuple field name for the first field.</param>
177+
/// <param name="tupleFieldName2">The expected tuple field name for the second field.</param>
178+
/// <param name="fixedTupleFieldName1">The expected fixed tuple field name for the first field.</param>
179+
/// <param name="fixedTupleFieldName2">The expected fixed tuple field name for the second field.</param>
180+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
181+
[Theory]
182+
[InlineData(DefaultTestSettings, "FieldName1", "FieldName2", "fieldName1", "fieldName2")]
183+
[InlineData(CamelCaseTestSettings, "FieldName1", "FieldName2", "fieldName1", "fieldName2")]
184+
[InlineData(PascalCaseTestSettings, "fieldName1", "fieldName2", "FieldName1", "FieldName2")]
185+
public async Task ValidateImproperCasedTupleFieldNamesAsync(string settings, string tupleFieldName1, string tupleFieldName2, string fixedTupleFieldName1, string fixedTupleFieldName2)
186+
{
187+
var testCode = $@"
188+
public class TestClass
189+
{{
190+
public (int [|{tupleFieldName1}|], int [|{tupleFieldName2}|]) TestMethod1()
191+
{{
192+
return (1, 1);
193+
}}
194+
195+
public (int /* 1 */ [|{tupleFieldName1}|] /* 2 */ , int /* 3 */ [|{tupleFieldName2}|] /* 4 */) TestMethod2()
196+
{{
197+
return (1, 1);
198+
}}
199+
}}
200+
";
201+
202+
var fixedCode = $@"
203+
public class TestClass
204+
{{
205+
public (int {fixedTupleFieldName1}, int {fixedTupleFieldName2}) TestMethod1()
206+
{{
207+
return (1, 1);
208+
}}
209+
210+
public (int /* 1 */ {fixedTupleFieldName1} /* 2 */ , int /* 3 */ {fixedTupleFieldName2} /* 4 */) TestMethod2()
211+
{{
212+
return (1, 1);
213+
}}
214+
}}
215+
";
216+
217+
DiagnosticResult[] expectedDiagnostics =
218+
{
219+
// diagnostics are specified inline
220+
};
221+
222+
await VerifyCSharpFixAsync(LanguageVersionEx.CSharp7, testCode, settings, expectedDiagnostics, fixedCode, CancellationToken.None).ConfigureAwait(false);
223+
}
224+
225+
/// <summary>
226+
/// Verifies that improperly named inferred tuple field names are ignored when the 'includeInferredTupleFieldNames' option is not set to true.
227+
/// </summary>
228+
/// <param name="settings">The test settings to use.</param>
229+
/// <param name="tupleFieldName1">The expected tuple field name for the first field.</param>
230+
/// <param name="tupleFieldName2">The expected tuple field name for the second field.</param>
231+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
232+
[Theory]
233+
[InlineData(DefaultTestSettings, "FieldName1", "FieldName2")]
234+
[InlineData(CamelCaseTestSettings, "FieldName1", "FieldName2")]
235+
[InlineData(PascalCaseTestSettings, "fieldName1", "fieldName2")]
236+
[InlineData(CamelCaseExplicitOnlyTestSettings, "FieldName1", "FieldName2")]
237+
[InlineData(PascalCaseExplicitOnlyTestSettings, "fieldName1", "fieldName2")]
238+
public async Task ValidateImproperCasedInferredTupleFieldNamesAreIgnoredAsync(string settings, string tupleFieldName1, string tupleFieldName2)
239+
{
240+
var testCode = $@"
241+
public class TestClass
242+
{{
243+
public void TestMethod()
244+
{{
245+
var {tupleFieldName1} = 1;
246+
var {tupleFieldName2} = ""test"";
247+
var tuple = ({tupleFieldName1}, {tupleFieldName2});
248+
}}
249+
}}
250+
";
251+
252+
await VerifyCSharpDiagnosticAsync(LanguageVersionEx.CSharp7_1, testCode, settings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
253+
}
254+
255+
/// <summary>
256+
/// Validates the improperly named inferred tuple field names will produce the expected diagnostics.
257+
/// </summary>
258+
/// <param name="settings">The test settings to use.</param>
259+
/// <param name="tupleFieldName1">The expected tuple field name for the first field.</param>
260+
/// <param name="tupleFieldName2">The expected tuple field name for the second field.</param>
261+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
262+
[Theory]
263+
[InlineData(CamelCaseInferredTestSettings, "FieldName1", "FieldName2")]
264+
[InlineData(PascalCaseInferredTestSettings, "fieldName1", "fieldName2")]
265+
public async Task ValidateImproperCasedImplicitTupleFieldNamesAsync(string settings, string tupleFieldName1, string tupleFieldName2)
266+
{
267+
//// TODO: C# 7.1
268+
var testCode = $@"
269+
public class TestClass
270+
{{
271+
public void TestMethod1()
272+
{{
273+
var {tupleFieldName1} = 1;
274+
var {tupleFieldName2} = ""test"";
275+
var tuple = ([|{tupleFieldName1}|], [|{tupleFieldName2}|]);
276+
}}
277+
278+
public void TestMethod2()
279+
{{
280+
var {tupleFieldName1} = 1;
281+
var {tupleFieldName2} = ""test"";
282+
var tuple = (/* 1 */ [|{tupleFieldName1}|] /* 2 */, /* 3 */ [|{tupleFieldName2}|] /* 4 */);
283+
}}
284+
}}
285+
";
286+
287+
DiagnosticResult[] expectedDiagnostics =
288+
{
289+
// diagnostics are specified inline
290+
};
291+
292+
await VerifyCSharpDiagnosticAsync(LanguageVersionEx.CSharp7_1, testCode, settings, expectedDiagnostics, CancellationToken.None).ConfigureAwait(false);
293+
}
294+
}
295+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ internal static Task VerifyCSharpDiagnosticAsync(LanguageVersion? languageVersio
4444
=> StyleCopDiagnosticVerifier<TAnalyzer>.VerifyCSharpDiagnosticAsync(languageVersion, source, expected, cancellationToken);
4545

4646
internal static Task VerifyCSharpDiagnosticAsync(LanguageVersion? languageVersion, string source, DiagnosticResult[] expected, CancellationToken cancellationToken)
47-
=> StyleCopDiagnosticVerifier<TAnalyzer>.VerifyCSharpDiagnosticAsync(languageVersion, source, expected, cancellationToken);
47+
=> StyleCopDiagnosticVerifier<TAnalyzer>.VerifyCSharpDiagnosticAsync(languageVersion, source, null, expected, cancellationToken);
48+
49+
internal static Task VerifyCSharpDiagnosticAsync(LanguageVersion? languageVersion, string source, string settings, DiagnosticResult[] expected, CancellationToken cancellationToken)
50+
=> StyleCopDiagnosticVerifier<TAnalyzer>.VerifyCSharpDiagnosticAsync(languageVersion, source, settings, expected, cancellationToken);
4851

4952
internal static Task VerifyCSharpFixAsync(string source, DiagnosticResult expected, string fixedSource, CancellationToken cancellationToken)
5053
=> VerifyCSharpFixAsync(source, new[] { expected }, fixedSource, cancellationToken);
@@ -70,13 +73,17 @@ internal static Task VerifyCSharpFixAsync(string source, DiagnosticResult[] expe
7073
}
7174

7275
internal static Task VerifyCSharpFixAsync(LanguageVersion? languageVersion, string source, DiagnosticResult expected, string fixedSource, CancellationToken cancellationToken)
73-
=> VerifyCSharpFixAsync(languageVersion, source, new[] { expected }, fixedSource, cancellationToken);
76+
=> VerifyCSharpFixAsync(languageVersion, source, null, new[] { expected }, fixedSource, cancellationToken);
7477

7578
internal static Task VerifyCSharpFixAsync(LanguageVersion? languageVersion, string source, DiagnosticResult[] expected, string fixedSource, CancellationToken cancellationToken)
79+
=> VerifyCSharpFixAsync(languageVersion, source, null, expected, fixedSource, cancellationToken);
80+
81+
internal static Task VerifyCSharpFixAsync(LanguageVersion? languageVersion, string source, string settings, DiagnosticResult[] expected, string fixedSource, CancellationToken cancellationToken)
7682
{
7783
var test = new CSharpTest(languageVersion)
7884
{
7985
TestCode = source,
86+
Settings = settings,
8087
FixedCode = fixedSource,
8188
};
8289

0 commit comments

Comments
 (0)