Skip to content

Commit af431f3

Browse files
committed
Added system first support for static usings
1 parent 9950efe commit af431f3

4 files changed

Lines changed: 193 additions & 35 deletions

File tree

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: 104 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,26 @@ 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 bool useSystemUsingDirectivesFirst;
29+
2130
/// <summary>
2231
/// Verifies that the analyzer will not produce diagnostics for correctly ordered using directives inside a namespace.
2332
/// </summary>
@@ -34,7 +43,7 @@ public async Task TestValidUsingDirectivesInNamespaceAsync()
3443
}
3544
";
3645

37-
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
46+
await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
3847
}
3948

4049
/// <summary>
@@ -61,7 +70,7 @@ namespace Bar
6170
}
6271
";
6372

64-
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
73+
await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
6574
}
6675

6776
/// <summary>
@@ -81,7 +90,7 @@ public class Foo
8190
}
8291
";
8392

84-
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
93+
await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
8594
}
8695

8796
/// <summary>
@@ -131,7 +140,7 @@ namespace Bar
131140
Diagnostic().WithLocation(11, 5).WithArguments("System.Math", "System.Array"),
132141
};
133142

134-
await VerifyCSharpFixAsync(testCode, expectedDiagnostics, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
143+
await this.VerifyCSharpFixAsync(testCode, expectedDiagnostics, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
135144
}
136145

137146
/// <summary>
@@ -150,7 +159,7 @@ public async Task TestValidUsingDirectivesWithInlineCommentsAsync()
150159
}
151160
";
152161

153-
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
162+
await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
154163
}
155164

156165
/// <summary>
@@ -178,9 +187,12 @@ public async Task TestInvalidUsingDirectivesWithGlobalPrefixAsync()
178187
}
179188
";
180189

181-
var expectedDiagnostic = Diagnostic().WithLocation(5, 5).WithArguments("System.Math", "global::System.Array");
190+
DiagnosticResult[] expectedDiagnostic =
191+
{
192+
Diagnostic().WithLocation(5, 5).WithArguments("System.Math", "global::System.Array"),
193+
};
182194

183-
await VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
195+
await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
184196
}
185197

186198
/// <summary>
@@ -219,9 +231,88 @@ public async Task TestPreprocessorDirectivesAsync()
219231
#endif";
220232

221233
// else block is skipped
222-
var expected = Diagnostic().WithLocation(8, 1).WithArguments("System.String", "System.Math");
234+
DiagnosticResult[] expectedDiagnostic =
235+
{
236+
Diagnostic().WithLocation(8, 1).WithArguments("System.String", "System.Math"),
237+
};
238+
239+
await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
240+
}
241+
242+
/// <summary>
243+
/// Verify that the systemUsingDirectivesFirst setting is honored correctly.
244+
/// </summary>
245+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
246+
[Fact]
247+
[WorkItem(2163, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2163")]
248+
public async Task VerifySystemUsingDirectivesFirstAsync()
249+
{
250+
this.useSystemUsingDirectivesFirst = true;
251+
252+
var testCode = @"
253+
using static MyNamespace.TestClass;
254+
using static System.Math;
255+
256+
namespace MyNamespace
257+
{
258+
public static class TestClass
259+
{
260+
public static void TestMethod()
261+
{
262+
}
263+
}
264+
}
265+
";
266+
267+
var fixedTestCode = @"
268+
using static System.Math;
269+
using static MyNamespace.TestClass;
270+
271+
namespace MyNamespace
272+
{
273+
public static class TestClass
274+
{
275+
public static void TestMethod()
276+
{
277+
}
278+
}
279+
}
280+
";
281+
282+
DiagnosticResult[] expectedDiagnostic =
283+
{
284+
Diagnostic().WithLocation(2, 1).WithArguments("MyNamespace.TestClass", "System.Math"),
285+
};
286+
287+
await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
288+
}
289+
290+
private static DiagnosticResult Diagnostic()
291+
=> StyleCopCodeFixVerifier<SA1217UsingStaticDirectivesMustBeOrderedAlphabetically, UsingCodeFixProvider>.Diagnostic();
292+
293+
private Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken)
294+
{
295+
var test = new StyleCopCodeFixVerifier<SA1217UsingStaticDirectivesMustBeOrderedAlphabetically, UsingCodeFixProvider>.CSharpTest
296+
{
297+
TestCode = source,
298+
Settings = this.useSystemUsingDirectivesFirst ? TestSettings : null,
299+
};
300+
301+
test.ExpectedDiagnostics.AddRange(expected);
302+
return test.RunAsync(cancellationToken);
303+
}
304+
305+
private Task VerifyCSharpFixAsync(string source, DiagnosticResult[] expected, string fixedSource, CancellationToken cancellationToken)
306+
{
307+
var test = new StyleCopCodeFixVerifier<SA1217UsingStaticDirectivesMustBeOrderedAlphabetically, UsingCodeFixProvider>.CSharpTest
308+
{
309+
TestCode = source,
310+
FixedCode = fixedSource,
311+
Settings = this.useSystemUsingDirectivesFirst ? TestSettings : null,
312+
};
223313

224-
await VerifyCSharpFixAsync(testCode, expected, fixedTestCode, CancellationToken.None).ConfigureAwait(false);
314+
test.ExpectedDiagnostics.AddRange(expected);
315+
return test.RunAsync(cancellationToken);
225316
}
226317
}
227318
}

0 commit comments

Comments
 (0)