diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.UsingsSorter.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.UsingsSorter.cs index 4d0b96f79..4199ac9ec 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.UsingsSorter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.UsingsSorter.cs @@ -35,6 +35,7 @@ private class UsingsSorter private readonly Dictionary> systemUsings = new Dictionary>(); private readonly Dictionary> namespaceUsings = new Dictionary>(); private readonly Dictionary> aliases = new Dictionary>(); + private readonly Dictionary> systemStaticImports = new Dictionary>(); private readonly Dictionary> staticImports = new Dictionary>(); public UsingsSorter(StyleCopSettings settings, SemanticModel semanticModel, CompilationUnitSyntax compilationUnit, ImmutableArray fileHeader) @@ -76,6 +77,11 @@ public List GetContainedUsings(TreeTextSpan directiveSpan) result.AddRange(usingsList); } + if (this.systemStaticImports.TryGetValue(directiveSpan, out usingsList)) + { + result.AddRange(usingsList); + } + if (this.staticImports.TryGetValue(directiveSpan, out usingsList)) { result.AddRange(usingsList); @@ -91,6 +97,7 @@ public SyntaxList GenerateGroupedUsings(TreeTextSpan direc usingList.AddRange(this.GenerateUsings(this.systemUsings, directiveSpan, indentation, triviaToMove, qualifyNames)); usingList.AddRange(this.GenerateUsings(this.namespaceUsings, directiveSpan, indentation, triviaToMove, qualifyNames)); + usingList.AddRange(this.GenerateUsings(this.systemStaticImports, directiveSpan, indentation, triviaToMove, qualifyNames)); usingList.AddRange(this.GenerateUsings(this.staticImports, directiveSpan, indentation, triviaToMove, qualifyNames)); usingList.AddRange(this.GenerateUsings(this.aliases, directiveSpan, indentation, triviaToMove, qualifyNames)); @@ -116,6 +123,7 @@ public SyntaxList GenerateGroupedUsings(List usingDirect { this.AddUsingDirective(this.aliases, usingDirective, containingSpan); } - else if (!usingDirective.StaticKeyword.IsKind(SyntaxKind.None)) + else if (usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) { - this.AddUsingDirective(this.staticImports, usingDirective, containingSpan); + if (this.IsSeparatedStaticSystemUsing(usingDirective)) + { + this.AddUsingDirective(this.systemStaticImports, usingDirective, containingSpan); + } + else + { + this.AddUsingDirective(this.staticImports, usingDirective, containingSpan); + } } else if (this.IsSeparatedSystemUsing(usingDirective)) { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.cs index b824d3ce9..b0e20c5d0 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.cs @@ -29,7 +29,9 @@ internal sealed partial class UsingCodeFixProvider : CodeFixProvider private static readonly List EmptyUsingsList = new List(); private static readonly SyntaxAnnotation UsingCodeFixAnnotation = new SyntaxAnnotation(nameof(UsingCodeFixAnnotation)); - private static readonly SymbolDisplayFormat FullNamespaceDisplayFormat = SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); + private static readonly SymbolDisplayFormat FullNamespaceDisplayFormat = SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted) + .WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers); /// public override ImmutableArray FixableDiagnosticIds { get; } = diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1217UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1217UnitTests.cs index c563d0bd1..60f9e8386 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1217UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1217UnitTests.cs @@ -7,17 +7,36 @@ namespace StyleCop.Analyzers.Test.OrderingRules using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.OrderingRules; - using TestHelper; + using StyleCop.Analyzers.Test.Verifiers; using Xunit; - using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< - StyleCop.Analyzers.OrderingRules.SA1217UsingStaticDirectivesMustBeOrderedAlphabetically, - StyleCop.Analyzers.OrderingRules.UsingCodeFixProvider>; /// /// Unit tests for . /// public class SA1217UnitTests { + private const string TestSettings = @" +{ + ""settings"": { + ""orderingRules"": { + ""systemUsingDirectivesFirst"": true + } + } +} +"; + + private const string TestSettingsNoSystemDirectivesFirst = @" +{ + ""settings"": { + ""orderingRules"": { + ""systemUsingDirectivesFirst"": false + } + } +} +"; + + private bool useSystemUsingDirectivesFirst; + /// /// Verifies that the analyzer will not produce diagnostics for correctly ordered using directives inside a namespace. /// @@ -34,7 +53,7 @@ public async Task TestValidUsingDirectivesInNamespaceAsync() } "; - await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// @@ -61,7 +80,7 @@ namespace Bar } "; - await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// @@ -81,7 +100,7 @@ public class Foo } "; - await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// @@ -131,7 +150,7 @@ namespace Bar Diagnostic().WithLocation(11, 5).WithArguments("System.Math", "System.Array"), }; - await VerifyCSharpFixAsync(testCode, expectedDiagnostics, fixedTestCode, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, expectedDiagnostics, fixedTestCode, CancellationToken.None).ConfigureAwait(false); } /// @@ -150,7 +169,7 @@ public async Task TestValidUsingDirectivesWithInlineCommentsAsync() } "; - await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// @@ -178,9 +197,12 @@ public async Task TestInvalidUsingDirectivesWithGlobalPrefixAsync() } "; - var expectedDiagnostic = Diagnostic().WithLocation(5, 5).WithArguments("System.Math", "global::System.Array"); + DiagnosticResult[] expectedDiagnostic = + { + Diagnostic().WithLocation(5, 5).WithArguments("System.Math", "global::System.Array"), + }; - await VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false); } /// @@ -191,8 +213,8 @@ public async Task TestInvalidUsingDirectivesWithGlobalPrefixAsync() public async Task TestPreprocessorDirectivesAsync() { var testCode = @" -using System; using Microsoft.Win32; +using System; using MyList = System.Collections.Generic.List; using static System.Tuple; @@ -205,8 +227,8 @@ public async Task TestPreprocessorDirectivesAsync() #endif"; var fixedTestCode = @" -using System; using Microsoft.Win32; +using System; using static System.Tuple; using MyList = System.Collections.Generic.List; @@ -219,9 +241,136 @@ public async Task TestPreprocessorDirectivesAsync() #endif"; // else block is skipped - var expected = Diagnostic().WithLocation(8, 1).WithArguments("System.String", "System.Math"); + DiagnosticResult[] expectedDiagnostic = + { + Diagnostic().WithLocation(8, 1).WithArguments("System.String", "System.Math"), + }; + + await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verify that the systemUsingDirectivesFirst setting is honored correctly. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(2163, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2163")] + public async Task VerifySystemUsingDirectivesFirstAsync() + { + this.useSystemUsingDirectivesFirst = true; + + var testCode = @" +using static MyNamespace.TestClass; +using static System.Math; + +namespace MyNamespace +{ + public static class TestClass + { + public static void TestMethod() + { + } + } +} +"; + + var fixedTestCode = @" +using static System.Math; +using static MyNamespace.TestClass; + +namespace MyNamespace +{ + public static class TestClass + { + public static void TestMethod() + { + } + } +} +"; + + DiagnosticResult[] expectedDiagnostic = + { + Diagnostic().WithLocation(2, 1).WithArguments("MyNamespace.TestClass", "System.Math"), + }; + + await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verify that the systemUsingDirectivesFirst setting is honored correctly when using multiple static system usings. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(2163, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2163")] + public async Task VerifyMultipleStaticSystemUsingDirectivesAsync() + { + this.useSystemUsingDirectivesFirst = true; + + var testCode = @" +using static System.Math; +using static System.Activator; + +namespace MyNamespace +{ + public static class TestClass + { + public static void TestMethod() + { + } + } +} +"; + + var fixedTestCode = @" +using static System.Activator; +using static System.Math; + +namespace MyNamespace +{ + public static class TestClass + { + public static void TestMethod() + { + } + } +} +"; + + DiagnosticResult[] expectedDiagnostic = + { + Diagnostic().WithLocation(2, 1).WithArguments("System.Math", "System.Activator"), + }; + + await this.VerifyCSharpFixAsync(testCode, expectedDiagnostic, fixedTestCode, CancellationToken.None).ConfigureAwait(false); + } + + private static DiagnosticResult Diagnostic() + => StyleCopCodeFixVerifier.Diagnostic(); + + private Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken) + { + var test = new StyleCopCodeFixVerifier.CSharpTest + { + TestCode = source, + Settings = this.useSystemUsingDirectivesFirst ? TestSettings : TestSettingsNoSystemDirectivesFirst, + }; + + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(cancellationToken); + } + + private Task VerifyCSharpFixAsync(string source, DiagnosticResult[] expected, string fixedSource, CancellationToken cancellationToken) + { + var test = new StyleCopCodeFixVerifier.CSharpTest + { + TestCode = source, + FixedCode = fixedSource, + Settings = this.useSystemUsingDirectivesFirst ? TestSettings : TestSettingsNoSystemDirectivesFirst, + }; - await VerifyCSharpFixAsync(testCode, expected, fixedTestCode, CancellationToken.None).ConfigureAwait(false); + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(cancellationToken); } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1217UsingStaticDirectivesMustBeOrderedAlphabetically.cs b/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1217UsingStaticDirectivesMustBeOrderedAlphabetically.cs index 751b56047..eb1e2da77 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1217UsingStaticDirectivesMustBeOrderedAlphabetically.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1217UsingStaticDirectivesMustBeOrderedAlphabetically.cs @@ -11,6 +11,7 @@ namespace StyleCop.Analyzers.OrderingRules using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using StyleCop.Analyzers.Helpers; + using StyleCop.Analyzers.Settings.ObjectModel; /// /// A static using directive is positioned at the wrong location. @@ -37,8 +38,8 @@ internal class SA1217UsingStaticDirectivesMustBeOrderedAlphabetically : Diagnost private static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.OrderingRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink); - private static readonly Action CompilationUnitAction = HandleCompilationUnit; - private static readonly Action NamespaceDeclarationAction = HandleNamespaceDeclaration; + private static readonly Action CompilationUnitAction = HandleCompilationUnit; + private static readonly Action NamespaceDeclarationAction = HandleNamespaceDeclaration; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -54,47 +55,83 @@ public override void Initialize(AnalysisContext context) context.RegisterSyntaxNodeAction(NamespaceDeclarationAction, SyntaxKind.NamespaceDeclaration); } - private static void HandleCompilationUnit(SyntaxNodeAnalysisContext context) + private static void HandleCompilationUnit(SyntaxNodeAnalysisContext context, StyleCopSettings settings) { var compilationUnit = (CompilationUnitSyntax)context.Node; - CheckUsingDeclarations(context, compilationUnit.Usings); + CheckUsingDeclarations(context, settings.OrderingRules, compilationUnit.Usings); } - private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context) + private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context, StyleCopSettings settings) { var namespaceDirective = (NamespaceDeclarationSyntax)context.Node; - CheckUsingDeclarations(context, namespaceDirective.Usings); + CheckUsingDeclarations(context, settings.OrderingRules, namespaceDirective.Usings); } - private static void CheckUsingDeclarations(SyntaxNodeAnalysisContext context, SyntaxList usingDirectives) + private static void CheckUsingDeclarations(SyntaxNodeAnalysisContext context, OrderingSettings orderingSettings, SyntaxList usingDirectives) { UsingDirectiveSyntax lastStaticUsingDirective = null; + UsingDirectiveSyntax lastSystemStaticUsingDirective = null; + UsingDirectiveSyntax firstNonSystemUsing = null; foreach (var usingDirective in usingDirectives) { if (usingDirective.IsPrecededByPreprocessorDirective()) { lastStaticUsingDirective = null; + lastSystemStaticUsingDirective = null; + firstNonSystemUsing = null; } if (usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) { - if (lastStaticUsingDirective != null) + if (orderingSettings.SystemUsingDirectivesFirst && usingDirective.IsSystemUsingDirective()) { - var firstName = lastStaticUsingDirective.Name; - var secondName = usingDirective.Name; - - if (NameSyntaxHelpers.Compare(firstName, secondName) > 0) + if (firstNonSystemUsing != null) { context.ReportDiagnostic(Diagnostic.Create( Descriptor, - lastStaticUsingDirective.GetLocation(), - new[] { firstName.ToNormalizedString(), secondName.ToNormalizedString() })); + firstNonSystemUsing.GetLocation(), + new[] { firstNonSystemUsing.Name.ToNormalizedString(), usingDirective.Name.ToNormalizedString() })); return; } + + if (lastSystemStaticUsingDirective != null) + { + var firstName = lastSystemStaticUsingDirective.Name; + var secondName = usingDirective.Name; + + if (NameSyntaxHelpers.Compare(firstName, secondName) > 0) + { + context.ReportDiagnostic(Diagnostic.Create( + Descriptor, + lastSystemStaticUsingDirective.GetLocation(), + new[] { firstName.ToNormalizedString(), secondName.ToNormalizedString() })); + return; + } + } + + lastSystemStaticUsingDirective = usingDirective; } + else + { + if (lastStaticUsingDirective != null) + { + var firstName = lastStaticUsingDirective.Name; + var secondName = usingDirective.Name; - lastStaticUsingDirective = usingDirective; + if (NameSyntaxHelpers.Compare(firstName, secondName) > 0) + { + context.ReportDiagnostic(Diagnostic.Create( + Descriptor, + lastStaticUsingDirective.GetLocation(), + new[] { firstName.ToNormalizedString(), secondName.ToNormalizedString() })); + return; + } + } + + lastStaticUsingDirective = usingDirective; + firstNonSystemUsing = firstNonSystemUsing ?? usingDirective; + } } } }