Skip to content

Commit 1ed7bbf

Browse files
committed
Merge pull request #1615 from vweijsters/SA1649
2 parents 057193d + 02ff3ae commit 1ed7bbf

14 files changed

Lines changed: 592 additions & 129 deletions
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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.DocumentationRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Linq;
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.Diagnostics;
15+
using StyleCop.Analyzers.DocumentationRules;
16+
using TestHelper;
17+
using Xunit;
18+
19+
/// <summary>
20+
/// Unit tests for the SA1649 diagnostic.
21+
/// </summary>
22+
public class SA1649UnitTests : CodeFixVerifier
23+
{
24+
private const string MetadataSettings = @"
25+
{
26+
""settings"": {
27+
""documentationRules"": {
28+
""fileNamingConvention"": ""metadata""
29+
}
30+
}
31+
}
32+
";
33+
34+
private const string StyleCopSettings = @"
35+
{
36+
""settings"": {
37+
""documentationRules"": {
38+
""fileNamingConvention"": ""stylecop""
39+
}
40+
}
41+
}
42+
";
43+
44+
private bool useMetadataSettings;
45+
46+
public static IEnumerable<object[]> TypeKeywords
47+
{
48+
get
49+
{
50+
yield return new object[] { "class" };
51+
yield return new object[] { "struct" };
52+
yield return new object[] { "interface" };
53+
}
54+
}
55+
56+
/// <summary>
57+
/// Verifies that a wrong file name is correctly reported
58+
/// </summary>
59+
/// <param name="typeKeyword">The type keyword to use during the test.</param>
60+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
61+
[Theory]
62+
[MemberData(nameof(TypeKeywords))]
63+
public async Task VerifyWrongFileNameAsync(string typeKeyword)
64+
{
65+
var testCode = $@"namespace TestNameSpace
66+
{{
67+
public {typeKeyword} TestType
68+
{{
69+
}}
70+
}}
71+
";
72+
73+
var fixedCode = $@"namespace TestNamespace
74+
{{
75+
public {typeKeyword} TestType
76+
{{
77+
}}
78+
}}
79+
";
80+
81+
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation("WrongFileName.cs", 3, 13 + typeKeyword.Length);
82+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "WrongFileName.cs").ConfigureAwait(false);
83+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None, "TestType.cs").ConfigureAwait(false);
84+
await this.VerifyRenameAsync(testCode, "TestType.cs", CancellationToken.None).ConfigureAwait(false);
85+
}
86+
87+
/// <summary>
88+
/// Verifies that the file name is not case sensitive.
89+
/// </summary>
90+
/// <param name="typeKeyword">The type keyword to use during the test.</param>
91+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
92+
[Theory]
93+
[MemberData(nameof(TypeKeywords))]
94+
public async Task VerifyCaseInsensitivityAsync(string typeKeyword)
95+
{
96+
var testCode = $@"namespace TestNameSpace
97+
{{
98+
public {typeKeyword} TestType
99+
{{
100+
}}
101+
}}
102+
";
103+
104+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "testtype.cs").ConfigureAwait(false);
105+
}
106+
107+
/// <summary>
108+
/// Verifies that the file name is based on the first type.
109+
/// </summary>
110+
/// <param name="typeKeyword">The type keyword to use during the test.</param>
111+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
112+
[Theory]
113+
[MemberData(nameof(TypeKeywords))]
114+
public async Task VerifyFirstTypeIsUsedAsync(string typeKeyword)
115+
{
116+
var testCode = $@"namespace TestNameSpace
117+
{{
118+
public {typeKeyword} TestType
119+
{{
120+
}}
121+
122+
public {typeKeyword} TestType2
123+
{{
124+
}}
125+
}}
126+
";
127+
128+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "TestType.cs").ConfigureAwait(false);
129+
}
130+
131+
/// <summary>
132+
/// Verifies that partial types are ignored.
133+
/// </summary>
134+
/// <param name="typeKeyword">The type keyword to use during the test.</param>
135+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
136+
[Theory]
137+
[MemberData(nameof(TypeKeywords))]
138+
public async Task VerifyThatPartialTypesAreIgnoredAsync(string typeKeyword)
139+
{
140+
var testCode = $@"namespace TestNameSpace
141+
{{
142+
public partial {typeKeyword} TestType
143+
{{
144+
}}
145+
}}
146+
";
147+
148+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "WrongFileName.cs").ConfigureAwait(false);
149+
}
150+
151+
/// <summary>
152+
/// Verifies that the StyleCop file name convention for a generic type is handled correctly.
153+
/// </summary>
154+
/// <param name="typeKeyword">The type keyword to use during the test.</param>
155+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
156+
[Theory]
157+
[MemberData(nameof(TypeKeywords))]
158+
public async Task VerifyStyleCopNamingConventionForGenericTypeAsync(string typeKeyword)
159+
{
160+
this.useMetadataSettings = false;
161+
162+
var testCode = $@"namespace TestNameSpace
163+
{{
164+
public {typeKeyword} TestType<T1, T2, T3>
165+
{{
166+
}}
167+
}}
168+
";
169+
170+
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation("TestType`3.cs", 3, 13 + typeKeyword.Length);
171+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "TestType`3.cs").ConfigureAwait(false);
172+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "TestType{T1,T2,T3}.cs").ConfigureAwait(false);
173+
await this.VerifyRenameAsync(testCode, "TestType{T1,T2,T3}.cs", CancellationToken.None).ConfigureAwait(false);
174+
}
175+
176+
/// <summary>
177+
/// Verifies that the metadata file name convention for a generic type is handled correctly.
178+
/// </summary>
179+
/// <param name="typeKeyword">The type keyword to use during the test.</param>
180+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
181+
[Theory]
182+
[MemberData(nameof(TypeKeywords))]
183+
public async Task VerifyMetadataNamingConventionForGenericTypeAsync(string typeKeyword)
184+
{
185+
this.useMetadataSettings = true;
186+
187+
var testCode = $@"namespace TestNameSpace
188+
{{
189+
public {typeKeyword} TestType<T1, T2, T3>
190+
{{
191+
}}
192+
}}
193+
";
194+
195+
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation("TestType{T1,T2,T3}.cs", 3, 13 + typeKeyword.Length);
196+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "TestType{T1,T2,T3}.cs").ConfigureAwait(false);
197+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "TestType`3.cs").ConfigureAwait(false);
198+
await this.VerifyRenameAsync(testCode, "TestType`3.cs", CancellationToken.None).ConfigureAwait(false);
199+
}
200+
201+
/// <summary>
202+
/// Verifies that no diagnostic is generated if there is no first type.
203+
/// </summary>
204+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
205+
[Fact]
206+
public async Task VerifyWithoutFirstTypeAsync()
207+
{
208+
var testCode = @"namespace TestNameSpace
209+
{
210+
}
211+
";
212+
213+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
214+
}
215+
216+
/// <inheritdoc/>
217+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
218+
{
219+
yield return new SA1649FileNameMustMatchTypeName();
220+
}
221+
222+
/// <inheritdoc/>
223+
protected override CodeFixProvider GetCSharpCodeFixProvider()
224+
{
225+
return new SA1649CodeFixProvider();
226+
}
227+
228+
/// <inheritdoc/>
229+
protected override string GetSettings()
230+
{
231+
return this.useMetadataSettings ? MetadataSettings : StyleCopSettings;
232+
}
233+
234+
private async Task VerifyRenameAsync(string source, string expectedFileName, CancellationToken cancellationToken)
235+
{
236+
var analyzers = this.GetCSharpDiagnosticAnalyzers().ToImmutableArray();
237+
var document = this.CreateDocument(source, LanguageNames.CSharp);
238+
var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, new[] { document }, cancellationToken).ConfigureAwait(false);
239+
240+
Assert.Equal(1, analyzerDiagnostics.Length);
241+
242+
var actions = new List<CodeAction>();
243+
var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), cancellationToken);
244+
await this.GetCSharpCodeFixProvider().RegisterCodeFixesAsync(context).ConfigureAwait(false);
245+
246+
Assert.Equal(1, actions.Count);
247+
248+
var operations = await actions[0].GetOperationsAsync(cancellationToken).ConfigureAwait(false);
249+
250+
var changedSolution = operations.OfType<ApplyChangesOperation>().Single().ChangedSolution;
251+
252+
var solutionChanges = changedSolution.GetChanges(document.Project.Solution);
253+
var projectChanges = solutionChanges.GetProjectChanges().ToArray();
254+
255+
Assert.Equal(1, projectChanges.Length);
256+
257+
var removedDocuments = projectChanges[0].GetRemovedDocuments().ToArray();
258+
Assert.Equal(1, removedDocuments.Length);
259+
Assert.Equal(document.Id, removedDocuments[0]);
260+
261+
var addedDocuments = projectChanges[0].GetAddedDocuments().ToArray();
262+
Assert.Equal(1, addedDocuments.Length);
263+
264+
var newDocument = changedSolution.GetDocument(addedDocuments[0]);
265+
Assert.Equal(expectedFileName, newDocument.Name);
266+
}
267+
}
268+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@
160160
<Compile Include="DocumentationRules\SA1646UnitTests.cs" />
161161
<Compile Include="DocumentationRules\SA1647UnitTests.cs" />
162162
<Compile Include="DocumentationRules\SA1648UnitTests.cs" />
163+
<Compile Include="DocumentationRules\SA1649UnitTests.cs" />
163164
<Compile Include="DocumentationRules\SA1650UnitTests.cs" />
164165
<Compile Include="DocumentationRules\SA1651UnitTests.cs" />
165166
<Compile Include="DocumentationRules\SA1652UnitTests.cs" />

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/DocumentationResources.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.

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/DocumentationResources.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,18 @@
222222
<data name="SA1642SA1643CodeFix" xml:space="preserve">
223223
<value>Add standard text</value>
224224
</data>
225+
<data name="SA1649CodeFix" xml:space="preserve">
226+
<value>Rename file to match first type name</value>
227+
</data>
228+
<data name="SA1649Description" xml:space="preserve">
229+
<value>The file name of a C# code file does not match the first type declared in the file.</value>
230+
</data>
231+
<data name="SA1649MessageFormat" xml:space="preserve">
232+
<value>File name must match first type name.</value>
233+
</data>
234+
<data name="SA1649Title" xml:space="preserve">
235+
<value>File name must match first type name</value>
236+
</data>
225237
<data name="SA1651CodeFix" xml:space="preserve">
226238
<value>Finalize placeholder text</value>
227239
</data>

0 commit comments

Comments
 (0)