Skip to content

Commit c2dfa48

Browse files
authored
Merge pull request #2796 from vweijsters/fix-2163
Added system first support for static usings
2 parents 5231fbb + f609b96 commit c2dfa48

File tree

4 files changed

+253
-37
lines changed

4 files changed

+253
-37
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.UsingsSorter.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ private class UsingsSorter
3535
private readonly Dictionary<TreeTextSpan, List<UsingDirectiveSyntax>> systemUsings = new Dictionary<TreeTextSpan, List<UsingDirectiveSyntax>>();
3636
private readonly Dictionary<TreeTextSpan, List<UsingDirectiveSyntax>> namespaceUsings = new Dictionary<TreeTextSpan, List<UsingDirectiveSyntax>>();
3737
private readonly Dictionary<TreeTextSpan, List<UsingDirectiveSyntax>> aliases = new Dictionary<TreeTextSpan, List<UsingDirectiveSyntax>>();
38+
private readonly Dictionary<TreeTextSpan, List<UsingDirectiveSyntax>> systemStaticImports = new Dictionary<TreeTextSpan, List<UsingDirectiveSyntax>>();
3839
private readonly Dictionary<TreeTextSpan, List<UsingDirectiveSyntax>> staticImports = new Dictionary<TreeTextSpan, List<UsingDirectiveSyntax>>();
3940

4041
public UsingsSorter(StyleCopSettings settings, SemanticModel semanticModel, CompilationUnitSyntax compilationUnit, ImmutableArray<SyntaxTrivia> fileHeader)
@@ -76,6 +77,11 @@ public List<UsingDirectiveSyntax> GetContainedUsings(TreeTextSpan directiveSpan)
7677
result.AddRange(usingsList);
7778
}
7879

80+
if (this.systemStaticImports.TryGetValue(directiveSpan, out usingsList))
81+
{
82+
result.AddRange(usingsList);
83+
}
84+
7985
if (this.staticImports.TryGetValue(directiveSpan, out usingsList))
8086
{
8187
result.AddRange(usingsList);
@@ -91,6 +97,7 @@ public SyntaxList<UsingDirectiveSyntax> GenerateGroupedUsings(TreeTextSpan direc
9197

9298
usingList.AddRange(this.GenerateUsings(this.systemUsings, directiveSpan, indentation, triviaToMove, qualifyNames));
9399
usingList.AddRange(this.GenerateUsings(this.namespaceUsings, directiveSpan, indentation, triviaToMove, qualifyNames));
100+
usingList.AddRange(this.GenerateUsings(this.systemStaticImports, directiveSpan, indentation, triviaToMove, qualifyNames));
94101
usingList.AddRange(this.GenerateUsings(this.staticImports, directiveSpan, indentation, triviaToMove, qualifyNames));
95102
usingList.AddRange(this.GenerateUsings(this.aliases, directiveSpan, indentation, triviaToMove, qualifyNames));
96103

@@ -116,6 +123,7 @@ public SyntaxList<UsingDirectiveSyntax> GenerateGroupedUsings(List<UsingDirectiv
116123

117124
usingList.AddRange(this.GenerateUsings(this.systemUsings, usingsList, indentation, triviaToMove, qualifyNames));
118125
usingList.AddRange(this.GenerateUsings(this.namespaceUsings, usingsList, indentation, triviaToMove, qualifyNames));
126+
usingList.AddRange(this.GenerateUsings(this.systemStaticImports, usingsList, indentation, triviaToMove, qualifyNames));
119127
usingList.AddRange(this.GenerateUsings(this.staticImports, usingsList, indentation, triviaToMove, qualifyNames));
120128
usingList.AddRange(this.GenerateUsings(this.aliases, usingsList, indentation, triviaToMove, qualifyNames));
121129

@@ -419,22 +427,35 @@ private int CompareUsings(UsingDirectiveSyntax left, UsingDirectiveSyntax right)
419427
return NameSyntaxHelpers.Compare(left.Name, right.Name);
420428
}
421429

430+
private bool IsSeparatedStaticSystemUsing(UsingDirectiveSyntax syntax)
431+
{
432+
if (!this.separateSystemDirectives)
433+
{
434+
return false;
435+
}
436+
437+
return this.StartsWithSystemUsingDirectiveIdentifier(syntax.Name);
438+
}
439+
422440
private bool IsSeparatedSystemUsing(UsingDirectiveSyntax syntax)
423441
{
424442
if (!this.separateSystemDirectives
425-
|| (syntax.Alias != null)
426-
|| syntax.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)
427443
|| syntax.HasNamespaceAliasQualifier())
428444
{
429445
return false;
430446
}
431447

432-
if (!(this.semanticModel.GetSymbolInfo(syntax.Name).Symbol is INamespaceSymbol namespaceSymbol))
448+
return this.StartsWithSystemUsingDirectiveIdentifier(syntax.Name);
449+
}
450+
451+
private bool StartsWithSystemUsingDirectiveIdentifier(NameSyntax name)
452+
{
453+
if (!(this.semanticModel.GetSymbolInfo(name).Symbol is INamespaceOrTypeSymbol namespaceOrTypeSymbol))
433454
{
434455
return false;
435456
}
436457

437-
var namespaceTypeName = namespaceSymbol.ToDisplayString(FullNamespaceDisplayFormat);
458+
var namespaceTypeName = namespaceOrTypeSymbol.ToDisplayString(FullNamespaceDisplayFormat);
438459
var firstPart = namespaceTypeName.Split('.')[0];
439460

440461
return string.Equals(SystemUsingDirectiveIdentifier, firstPart, StringComparison.Ordinal);
@@ -459,9 +480,16 @@ private void ProcessUsingDirectives(SyntaxList<UsingDirectiveSyntax> usingDirect
459480
{
460481
this.AddUsingDirective(this.aliases, usingDirective, containingSpan);
461482
}
462-
else if (!usingDirective.StaticKeyword.IsKind(SyntaxKind.None))
483+
else if (usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword))
463484
{
464-
this.AddUsingDirective(this.staticImports, usingDirective, containingSpan);
485+
if (this.IsSeparatedStaticSystemUsing(usingDirective))
486+
{
487+
this.AddUsingDirective(this.systemStaticImports, usingDirective, containingSpan);
488+
}
489+
else
490+
{
491+
this.AddUsingDirective(this.staticImports, usingDirective, containingSpan);
492+
}
465493
}
466494
else if (this.IsSeparatedSystemUsing(usingDirective))
467495
{

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ internal sealed partial class UsingCodeFixProvider : CodeFixProvider
2929

3030
private static readonly List<UsingDirectiveSyntax> EmptyUsingsList = new List<UsingDirectiveSyntax>();
3131
private static readonly SyntaxAnnotation UsingCodeFixAnnotation = new SyntaxAnnotation(nameof(UsingCodeFixAnnotation));
32-
private static readonly SymbolDisplayFormat FullNamespaceDisplayFormat = SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted);
32+
private static readonly SymbolDisplayFormat FullNamespaceDisplayFormat = SymbolDisplayFormat.FullyQualifiedFormat
33+
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
34+
.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers);
3335

3436
/// <inheritdoc/>
3537
public override ImmutableArray<string> FixableDiagnosticIds { get; } =

StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1217UnitTests.cs

Lines changed: 164 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,36 @@ namespace StyleCop.Analyzers.Test.OrderingRules
77
using System.Threading.Tasks;
88
using Microsoft.CodeAnalysis.Testing;
99
using StyleCop.Analyzers.OrderingRules;
10-
using TestHelper;
10+
using StyleCop.Analyzers.Test.Verifiers;
1111
using Xunit;
12-
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
13-
StyleCop.Analyzers.OrderingRules.SA1217UsingStaticDirectivesMustBeOrderedAlphabetically,
14-
StyleCop.Analyzers.OrderingRules.UsingCodeFixProvider>;
1512

1613
/// <summary>
1714
/// Unit tests for <see cref="SA1217UsingStaticDirectivesMustBeOrderedAlphabetically"/>.
1815
/// </summary>
1916
public class SA1217UnitTests
2017
{
18+
private const string TestSettings = @"
19+
{
20+
""settings"": {
21+
""orderingRules"": {
22+
""systemUsingDirectivesFirst"": true
23+
}
24+
}
25+
}
26+
";
27+
28+
private const string TestSettingsNoSystemDirectivesFirst = @"
29+
{
30+
""settings"": {
31+
""orderingRules"": {
32+
""systemUsingDirectivesFirst"": false
33+
}
34+
}
35+
}
36+
";
37+
38+
private bool useSystemUsingDirectivesFirst;
39+
2140
/// <summary>
2241
/// Verifies that the analyzer will not produce diagnostics for correctly ordered using directives inside a namespace.
2342
/// </summary>
@@ -34,7 +53,7 @@ public async Task TestValidUsingDirectivesInNamespaceAsync()
3453
}
3554
";
3655

37-
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
56+
await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
3857
}
3958

4059
/// <summary>
@@ -61,7 +80,7 @@ namespace Bar
6180
}
6281
";
6382

64-
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
83+
await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
6584
}
6685

6786
/// <summary>
@@ -81,7 +100,7 @@ public class Foo
81100
}
82101
";
83102

84-
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
103+
await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
85104
}
86105

87106
/// <summary>
@@ -131,7 +150,7 @@ namespace Bar
131150
Diagnostic().WithLocation(11, 5).WithArguments("System.Math", "System.Array"),
132151
};
133152

134-
await VerifyCSharpFixAsync(testCode, expectedDiagnostics, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
153+
await this.VerifyCSharpFixAsync(testCode, expectedDiagnostics, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
135154
}
136155

137156
/// <summary>
@@ -150,7 +169,7 @@ public async Task TestValidUsingDirectivesWithInlineCommentsAsync()
150169
}
151170
";
152171

153-
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
172+
await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
154173
}
155174

156175
/// <summary>
@@ -178,9 +197,12 @@ public async Task TestInvalidUsingDirectivesWithGlobalPrefixAsync()
178197
}
179198
";
180199

181-
var expectedDiagnostic = Diagnostic().WithLocation(5, 5).WithArguments("System.Math", "global::System.Array");
200+
DiagnosticResult[] expectedDiagnostic =
201+
{
202+
Diagnostic().WithLocation(5, 5).WithArguments("System.Math", "global::System.Array"),
203+
};
182204

183-
await VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
205+
await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
184206
}
185207

186208
/// <summary>
@@ -191,8 +213,8 @@ public async Task TestInvalidUsingDirectivesWithGlobalPrefixAsync()
191213
public async Task TestPreprocessorDirectivesAsync()
192214
{
193215
var testCode = @"
194-
using System;
195216
using Microsoft.Win32;
217+
using System;
196218
using MyList = System.Collections.Generic.List<int>;
197219
using static System.Tuple;
198220
@@ -205,8 +227,8 @@ public async Task TestPreprocessorDirectivesAsync()
205227
#endif";
206228

207229
var fixedTestCode = @"
208-
using System;
209230
using Microsoft.Win32;
231+
using System;
210232
using static System.Tuple;
211233
using MyList = System.Collections.Generic.List<int>;
212234
@@ -219,9 +241,136 @@ public async Task TestPreprocessorDirectivesAsync()
219241
#endif";
220242

221243
// else block is skipped
222-
var expected = Diagnostic().WithLocation(8, 1).WithArguments("System.String", "System.Math");
244+
DiagnosticResult[] expectedDiagnostic =
245+
{
246+
Diagnostic().WithLocation(8, 1).WithArguments("System.String", "System.Math"),
247+
};
248+
249+
await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
250+
}
251+
252+
/// <summary>
253+
/// Verify that the systemUsingDirectivesFirst setting is honored correctly.
254+
/// </summary>
255+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
256+
[Fact]
257+
[WorkItem(2163, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2163")]
258+
public async Task VerifySystemUsingDirectivesFirstAsync()
259+
{
260+
this.useSystemUsingDirectivesFirst = true;
261+
262+
var testCode = @"
263+
using static MyNamespace.TestClass;
264+
using static System.Math;
265+
266+
namespace MyNamespace
267+
{
268+
public static class TestClass
269+
{
270+
public static void TestMethod()
271+
{
272+
}
273+
}
274+
}
275+
";
276+
277+
var fixedTestCode = @"
278+
using static System.Math;
279+
using static MyNamespace.TestClass;
280+
281+
namespace MyNamespace
282+
{
283+
public static class TestClass
284+
{
285+
public static void TestMethod()
286+
{
287+
}
288+
}
289+
}
290+
";
291+
292+
DiagnosticResult[] expectedDiagnostic =
293+
{
294+
Diagnostic().WithLocation(2, 1).WithArguments("MyNamespace.TestClass", "System.Math"),
295+
};
296+
297+
await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
298+
}
299+
300+
/// <summary>
301+
/// Verify that the systemUsingDirectivesFirst setting is honored correctly when using multiple static system usings.
302+
/// </summary>
303+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
304+
[Fact]
305+
[WorkItem(2163, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2163")]
306+
public async Task VerifyMultipleStaticSystemUsingDirectivesAsync()
307+
{
308+
this.useSystemUsingDirectivesFirst = true;
309+
310+
var testCode = @"
311+
using static System.Math;
312+
using static System.Activator;
313+
314+
namespace MyNamespace
315+
{
316+
public static class TestClass
317+
{
318+
public static void TestMethod()
319+
{
320+
}
321+
}
322+
}
323+
";
324+
325+
var fixedTestCode = @"
326+
using static System.Activator;
327+
using static System.Math;
328+
329+
namespace MyNamespace
330+
{
331+
public static class TestClass
332+
{
333+
public static void TestMethod()
334+
{
335+
}
336+
}
337+
}
338+
";
339+
340+
DiagnosticResult[] expectedDiagnostic =
341+
{
342+
Diagnostic().WithLocation(2, 1).WithArguments("System.Math", "System.Activator"),
343+
};
344+
345+
await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
346+
}
347+
348+
private static DiagnosticResult Diagnostic()
349+
=> StyleCopCodeFixVerifier<SA1217UsingStaticDirectivesMustBeOrderedAlphabetically, UsingCodeFixProvider>.Diagnostic();
350+
351+
private Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken)
352+
{
353+
var test = new StyleCopCodeFixVerifier<SA1217UsingStaticDirectivesMustBeOrderedAlphabetically, UsingCodeFixProvider>.CSharpTest
354+
{
355+
TestCode = source,
356+
Settings = this.useSystemUsingDirectivesFirst ? TestSettings : TestSettingsNoSystemDirectivesFirst,
357+
};
358+
359+
test.ExpectedDiagnostics.AddRange(expected);
360+
return test.RunAsync(cancellationToken);
361+
}
362+
363+
private Task VerifyCSharpFixAsync(string source, DiagnosticResult[] expected, string fixedSource, CancellationToken cancellationToken)
364+
{
365+
var test = new StyleCopCodeFixVerifier<SA1217UsingStaticDirectivesMustBeOrderedAlphabetically, UsingCodeFixProvider>.CSharpTest
366+
{
367+
TestCode = source,
368+
FixedCode = fixedSource,
369+
Settings = this.useSystemUsingDirectivesFirst ? TestSettings : TestSettingsNoSystemDirectivesFirst,
370+
};
223371

224-
await VerifyCSharpFixAsync(testCode, expected, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
372+
test.ExpectedDiagnostics.AddRange(expected);
373+
return test.RunAsync(cancellationToken);
225374
}
226375
}
227376
}

0 commit comments

Comments
 (0)