diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1516CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1516CodeFixProvider.cs index 73c5bcfb7..b8d7940fa 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1516CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1516CodeFixProvider.cs @@ -152,6 +152,11 @@ private static SyntaxNode GetRelevantNode(SyntaxNode innerNode) return currentNode; } + if (currentNode is ExternAliasDirectiveSyntax) + { + return currentNode; + } + currentNode = currentNode.Parent; } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/LayoutRules/SA1516CSharp10UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/LayoutRules/SA1516CSharp10UnitTests.cs index baa1ba110..f6020b139 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/LayoutRules/SA1516CSharp10UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/LayoutRules/SA1516CSharp10UnitTests.cs @@ -3,9 +3,241 @@ namespace StyleCop.Analyzers.Test.CSharp10.LayoutRules { + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp9.LayoutRules; + using Xunit; + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1516ElementsMustBeSeparatedByBlankLine, + StyleCop.Analyzers.LayoutRules.SA1516CodeFixProvider>; public class SA1516CSharp10UnitTests : SA1516CSharp9UnitTests { + /// + /// Verifies that SA1516 is reported for usings and extern alias outside a file scoped namespace. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3512, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3512")] + public async Task TestThatDiagnosticIIsReportedOnUsingsAndExternAliasOutsideFileScopedNamespaceAsync() + { + var testCode = @"extern alias corlib; +[|using|] System; +using System.Linq; +using a = System.Collections; +[|namespace|] Foo; +"; + + var fixedCode = @"extern alias corlib; + +using System; +using System.Linq; +using a = System.Collections; + +namespace Foo; +"; + + await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + } + + /// + /// Verifies that SA1516 is reported for usings inside a file scoped namespace. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3512, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3512")] + public async Task TestThatDiagnosticIIsReportedOnSpacingWithUsingsInsideFileScopedNamespaceAsync() + { + var testCode = @"namespace Foo; +[|using|] System; +using System.Linq; +using a = System.Collections; +"; + + var fixedCode = @"namespace Foo; + +using System; +using System.Linq; +using a = System.Collections; +"; + + await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + } + + /// + /// Verifies that SA1516 is reported for member declarations inside a file scoped namespace. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3512, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3512")] + public async Task TestThatDiagnosticIIsReportedOnMemberDeclarationsInsideFileScopedNamespaceAsync() + { + var testCode = @"namespace Foo; +[|public|] class Bar +{ +} +[|public|] enum Foobar +{ +} +"; + + var fixedCode = @"namespace Foo; + +public class Bar +{ +} + +public enum Foobar +{ +} +"; + + await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + } + + /// + /// Verifies that SA1516 is reported for usings and member declarations inside a file scoped namespace. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3512, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3512")] + public async Task TestThatDiagnosticIIsReportedOnUsingsAndMemberDeclarationsInsideFileScopedNamespaceAsync() + { + var testCode = @"namespace Foo; +[|using|] System; +using System.Linq; +using a = System.Collections; +[|public|] class Bar +{ +} +[|public|] enum Foobar +{ +} +"; + + var fixedCode = @"namespace Foo; + +using System; +using System.Linq; +using a = System.Collections; + +public class Bar +{ +} + +public enum Foobar +{ +} +"; + + await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + } + + /// + /// Verifies that SA1516 is reported extern alias inside a file scoped namespace. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3512, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3512")] + public async Task TestThatDiagnosticIIsReportedOnExternAliasInsideFileScopedNamespaceAsync() + { + var testCode = @"namespace Foo; +[|extern|] alias corlib; +"; + + var fixedCode = @"namespace Foo; + +extern alias corlib; +"; + + await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + } + + /// + /// Verifies that SA1516 is reported extern alias and usings inside a file scoped namespace. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3512, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3512")] + public async Task TestThatDiagnosticIIsReportedOnExternAliasAndUsingsInsideFileScopedNamespaceAsync() + { + var testCode = @"namespace Foo; +[|extern|] alias corlib; +[|using|] System; +using System.Linq; +using a = System.Collections; +"; + + var fixedCode = @"namespace Foo; + +extern alias corlib; + +using System; +using System.Linq; +using a = System.Collections; +"; + + await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + } + + /// + /// Verifies that SA1516 is reported extern alias, usings and member declarations + /// inside a file scoped namespace. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3512, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3512")] + public async Task TestThatDiagnosticIIsReportedOnExternAliasUsingsAndMemberDeclarationsInsideFileScopedNamespaceAsync() + { + var testCode = @"namespace Foo; +[|extern|] alias corlib; +[|using|] System; +using System.Linq; +using a = System.Collections; +[|public|] class Bar +{ +} +[|public|] enum Foobar +{ +} +"; + + var fixedCode = @"namespace Foo; + +extern alias corlib; + +using System; +using System.Linq; +using a = System.Collections; + +public class Bar +{ +} + +public enum Foobar +{ +} +"; + + await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + } + + private static Task VerifyCSharpFixAsync(string testCode, string fixedCode) + { + var test = new CSharpTest + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + TestState = + { + Sources = { testCode }, + }, + FixedCode = fixedCode, + }; + + return test.RunAsync(CancellationToken.None); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1516ElementsMustBeSeparatedByBlankLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1516ElementsMustBeSeparatedByBlankLine.cs index bd1ea17ea..2ac731989 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1516ElementsMustBeSeparatedByBlankLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1516ElementsMustBeSeparatedByBlankLine.cs @@ -15,6 +15,7 @@ namespace StyleCop.Analyzers.LayoutRules using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; using StyleCop.Analyzers.Helpers; + using StyleCop.Analyzers.Lightup; using StyleCop.Analyzers.Settings.ObjectModel; /// @@ -87,6 +88,7 @@ internal class SA1516ElementsMustBeSeparatedByBlankLine : DiagnosticAnalyzer private static readonly Action TypeDeclarationAction = HandleTypeDeclaration; private static readonly Action CompilationUnitAction = HandleCompilationUnit; private static readonly Action NamespaceDeclarationAction = HandleNamespaceDeclaration; + private static readonly Action FileScopedNamespaceDeclarationAction = HandleFileScopedNamespaceDeclaration; private static readonly Action BasePropertyDeclarationAction = HandleBasePropertyDeclaration; private static readonly ImmutableDictionary DiagnosticProperties = ImmutableDictionary.Empty.Add(CodeFixActionKey, InsertBlankLineValue); @@ -129,6 +131,7 @@ public override void Initialize(AnalysisContext context) context.RegisterSyntaxNodeAction(TypeDeclarationAction, SyntaxKinds.TypeDeclaration); context.RegisterSyntaxNodeAction(CompilationUnitAction, SyntaxKind.CompilationUnit); context.RegisterSyntaxNodeAction(NamespaceDeclarationAction, SyntaxKind.NamespaceDeclaration); + context.RegisterSyntaxNodeAction(FileScopedNamespaceDeclarationAction, SyntaxKindEx.FileScopedNamespaceDeclaration); context.RegisterSyntaxNodeAction(BasePropertyDeclarationAction, SyntaxKinds.BasePropertyDeclaration); }); } @@ -212,6 +215,42 @@ private static void HandleCompilationUnit(SyntaxNodeAnalysisContext context, Sty } } + private static void HandleFileScopedNamespaceDeclaration(SyntaxNodeAnalysisContext context, StyleCopSettings settings) + { + var namespaceDeclaration = (BaseNamespaceDeclarationSyntaxWrapper)context.Node; + + var usings = namespaceDeclaration.Usings; + var members = namespaceDeclaration.Members; + + HandleUsings(context, usings, settings); + HandleMemberList(context, members); + + if (namespaceDeclaration.Externs.Count > 0) + { + ReportIfThereIsNoBlankLine(context, namespaceDeclaration.Name, namespaceDeclaration.Externs[0]); + } + + if (namespaceDeclaration.Usings.Count > 0) + { + ReportIfThereIsNoBlankLine(context, namespaceDeclaration.Name, namespaceDeclaration.Usings[0]); + + if (namespaceDeclaration.Externs.Count > 0) + { + ReportIfThereIsNoBlankLine(context, namespaceDeclaration.Externs[namespaceDeclaration.Externs.Count - 1], namespaceDeclaration.Usings[0]); + } + } + + if (members.Count > 0) + { + ReportIfThereIsNoBlankLine(context, namespaceDeclaration.Name, members[0]); + + if (namespaceDeclaration.Usings.Count > 0) + { + ReportIfThereIsNoBlankLine(context, usings[usings.Count - 1], members[0]); + } + } + } + private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context, StyleCopSettings settings) { var namespaceDeclaration = (NamespaceDeclarationSyntax)context.Node;