diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/SA1206CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/SA1206CodeFixProvider.cs index 85005dc44..772f5be76 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/SA1206CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/SA1206CodeFixProvider.cs @@ -14,6 +14,7 @@ namespace StyleCop.Analyzers.OrderingRules using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using StyleCop.Analyzers.Helpers; + using StyleCop.Analyzers.Lightup; using static StyleCop.Analyzers.OrderingRules.ModifierOrderHelper; /// @@ -53,29 +54,42 @@ private static async Task GetTransformedDocumentAsync(Document documen { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var memberDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf(); - if (memberDeclaration == null) + var declaration = FindDeclaration(syntaxRoot, diagnostic); + if (declaration == null) { return document; } - var modifierTokenToFix = memberDeclaration.FindToken(diagnostic.Location.SourceSpan.Start); + var modifierTokenToFix = declaration.FindToken(diagnostic.Location.SourceSpan.Start); if (GetModifierType(modifierTokenToFix) == ModifierType.None) { return document; } - var newModifierList = PartiallySortModifiers(memberDeclaration.GetModifiers(), modifierTokenToFix); - syntaxRoot = UpdateSyntaxRoot(memberDeclaration, newModifierList, syntaxRoot); + var newModifierList = PartiallySortModifiers(DeclarationModifiersHelper.GetModifiers(declaration), modifierTokenToFix); + syntaxRoot = UpdateSyntaxRoot(declaration, newModifierList, syntaxRoot); return document.WithSyntaxRoot(syntaxRoot); } - private static SyntaxNode UpdateSyntaxRoot(MemberDeclarationSyntax memberDeclaration, SyntaxTokenList newModifiers, SyntaxNode syntaxRoot) + /// + /// Finds the declaration a diagnostic was reported on. A local function is a statement rather than a member + /// declaration, so it cannot be found by looking for a alone. + /// + /// The root of the syntax tree. + /// The diagnostic to find the declaration for. + /// The declaration, or if none was found. + private static SyntaxNode FindDeclaration(SyntaxNode syntaxRoot, Diagnostic diagnostic) { - var newDeclaration = memberDeclaration.WithModifiers(newModifiers); + return syntaxRoot.FindNode(diagnostic.Location.SourceSpan) + .AncestorsAndSelf() + .FirstOrDefault(node => node is MemberDeclarationSyntax || LocalFunctionStatementSyntaxWrapper.IsInstance(node)); + } - return syntaxRoot.ReplaceNode(memberDeclaration, newDeclaration); + private static SyntaxNode UpdateSyntaxRoot(SyntaxNode declaration, SyntaxTokenList newModifiers, SyntaxNode syntaxRoot) + { + var newDeclaration = DeclarationModifiersHelper.WithModifiers(declaration, newModifiers); + return syntaxRoot.ReplaceNode(declaration, newDeclaration); } /// @@ -173,31 +187,31 @@ private class FixAll : DocumentBasedFixAllProvider // because all modifiers can be fixed in one run, we // only need to store each declaration once - var trackedDiagnosticMembers = new HashSet(); + var trackedDiagnosticMembers = new HashSet(); foreach (var diagnostic in diagnostics) { - var memberDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf(); - if (memberDeclaration == null) + var declaration = FindDeclaration(syntaxRoot, diagnostic); + if (declaration == null) { continue; } - var modifierToken = memberDeclaration.FindToken(diagnostic.Location.SourceSpan.Start); + var modifierToken = declaration.FindToken(diagnostic.Location.SourceSpan.Start); if (GetModifierType(modifierToken) == ModifierType.None) { continue; } - trackedDiagnosticMembers.Add(memberDeclaration); + trackedDiagnosticMembers.Add(declaration); } syntaxRoot = syntaxRoot.TrackNodes(trackedDiagnosticMembers); foreach (var member in trackedDiagnosticMembers) { - var memberDeclaration = syntaxRoot.GetCurrentNode(member); - var newModifierList = FullySortModifiers(memberDeclaration.GetModifiers()); - syntaxRoot = UpdateSyntaxRoot(memberDeclaration, newModifierList, syntaxRoot); + var declaration = syntaxRoot.GetCurrentNode(member); + var newModifierList = FullySortModifiers(DeclarationModifiersHelper.GetModifiers(declaration)); + syntaxRoot = UpdateSyntaxRoot(declaration, newModifierList, syntaxRoot); } return syntaxRoot; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp6/ReadabilityRules/SA1122UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp6/ReadabilityRules/SA1122UnitTests.cs index 08183f938..d31ceb384 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp6/ReadabilityRules/SA1122UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp6/ReadabilityRules/SA1122UnitTests.cs @@ -6,6 +6,7 @@ namespace StyleCop.Analyzers.Test.CSharp6.ReadabilityRules using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; + using StyleCop.Analyzers.Lightup; using StyleCop.Analyzers.ReadabilityRules; using Xunit; using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< @@ -16,8 +17,99 @@ namespace StyleCop.Analyzers.Test.CSharp6.ReadabilityRules /// This class contains unit tests for and /// . /// + // TODO: Check if this can be simplified, using the theory tests public class SA1122UnitTests { + public static TheoryData EmptyStringLiterals + { + get + { + var data = new TheoryData() + { + "\"\"", + "@\"\"", + "$\"\"", + "$@\"\"", + }; + + if (LightupHelpers.SupportsCSharp8) + { + data.Add("@$\"\""); + } + + if (LightupHelpers.SupportsCSharp11) + { + // Only the multi-line form of a raw string literal can be empty, written as a single blank line + // between the delimiters. + data.Add("\"\"\"\r\n\r\n \"\"\""); + } + + return data; + } + } + + public static TheoryData NotReportedStringLiterals + { + get + { + var data = new TheoryData() + { + "\"text\"", + "@\"text\"", + "$\"text\"", + "$@\"text\"", + "$\"{value}\"", + }; + + if (LightupHelpers.SupportsCSharp8) + { + data.Add("@$\"text\""); + } + + if (LightupHelpers.SupportsCSharp11) + { + data.Add("\"\"\"text\"\"\""); + + // Two blank lines is the boundary of the empty case above: the newline ending the last content + // line belongs to the closing delimiter, so this value is a single line break, not empty. + data.Add("\"\"\"\r\n\r\n\r\n \"\"\""); + + // A UTF-8 string literal is a ReadOnlySpan rather than a string, so string.Empty can never replace it. + data.Add("\"\"u8"); + data.Add("\"text\"u8"); + } + + return data; + } + } + + public static TheoryData EmptyStringLiteralsAllowedAsConstant + { + get + { + var data = new TheoryData() + { + "\"\"", + "@\"\"", + }; + + // An interpolated string is only a constant expression from C# 10 onwards. + if (LightupHelpers.SupportsCSharp10) + { + data.Add("$\"\""); + data.Add("$@\"\""); + data.Add("@$\"\""); + } + + if (LightupHelpers.SupportsCSharp11) + { + data.Add("\"\"\"\r\n\r\n \"\"\""); + } + + return data; + } + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -70,6 +162,60 @@ public void Bar() await VerifyCSharpFixAsync(oldSource, expected, newSource, CancellationToken.None).ConfigureAwait(true); } + [Theory] + [MemberData(nameof(EmptyStringLiterals))] + public async Task TestEmptyStringLiteralIsReportedAsync(string literal) + { + var testCode = $@"public class Foo +{{ + public void Bar(string value) + {{ + var test = [|{literal}|]; + }} +}}"; + var fixedCode = @"public class Foo +{ + public void Bar(string value) + { + var test = string.Empty; + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + [Theory] + [MemberData(nameof(NotReportedStringLiterals))] + public async Task TestStringLiteralIsNotReportedAsync(string literal) + { + var testCode = $@"public class Foo +{{ + public void Bar(string value) + {{ + var test = {literal}; + }} +}}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + + [Theory] + [MemberData(nameof(EmptyStringLiteralsAllowedAsConstant))] + public async Task TestEmptyStringLiteralAsConstantIsNotReportedAsync(string literal) + { + var testCode = $@"public class Foo +{{ + private const string TestField = {literal}; + + public void Bar() + {{ + const string test = {literal}; + }} +}}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs index fb3dabd1d..47fc4112e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs @@ -3,11 +3,55 @@ namespace StyleCop.Analyzers.Test.CSharp8.DocumentationRules { + using System.Threading; + using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.DocumentationRules.SA1600ElementsMustBeDocumented, + StyleCop.Analyzers.DocumentationRules.SA1600CodeFixProvider>; public partial class SA1600CSharp8UnitTests { // Using 'Default' here makes sure that later test projects also run these tests with their own language version, without having to override this property protected override LanguageVersion LanguageVersion => LanguageVersion.Default; + + /// + /// Verifies that the members an interface may hold from C# 8 onwards need documentation just like any other + /// interface member. + /// + /// A representing the asynchronous unit test. + // TODO: Investigate this behavior (the private members)! + [Fact] + public async Task TestInterfaceMembersWithoutDocumentationAsync() + { + var testCode = @"/// +/// A summary. +/// +public interface ITest +{ + void [|TestMethod1|]() + { + } + + private void [|TestMethod2|]() + { + } + + static void [|TestMethod3|]() + { + } + + private static void [|TestMethod4|]() + { + } +} +"; + + // Only the diagnostic is verified, as in every other SA1600 test: undocumented members also produce + // CS1591 warnings, which the code fix verification would require to be declared here as well. + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs new file mode 100644 index 000000000..8b3305173 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1500BracesForMultiLineStatementsMustNotShareLine, + StyleCop.Analyzers.LayoutRules.SA1500CodeFixProvider>; + + public partial class SA1500CSharp8UnitTests + { + /// + /// Verifies that a single-line switch expression is not inspected. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionSingleLineAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch { 0 => 0, _ => 1 }; + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that diagnostics will be reported for the braces of a multi-line switch expression when they share a line with other code. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionInvalidAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch [|{|] + 0 => 0, + _ => 1 [|}|]; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, + _ => 1 + }; + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs new file mode 100644 index 000000000..7147dbae2 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1501StatementMustNotBeOnASingleLine, + StyleCop.Analyzers.LayoutRules.SA1501CodeFixProvider>; + + public partial class SA1501CSharp8UnitTests + { + /// + /// Verifies that a single-line switch expression is not inspected. + /// The analyzer registers statement kinds, and a switch expression is an expression. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch { 0 => 0, _ => 1 }; + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs new file mode 100644 index 000000000..1b6e8ae5c --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1502ElementMustNotBeOnASingleLine, + StyleCop.Analyzers.LayoutRules.SA1502CodeFixProvider>; + + public partial class SA1502CSharp8UnitTests + { + /// + /// Verifies that the default implementation of an interface member must not be on a single line. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestDefaultInterfaceMethodAsync() + { + var testCode = @"public interface ITest +{ + void DefaultMethod() [|{|] } +} +"; + + var fixedCode = @"public interface ITest +{ + void DefaultMethod() + { + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that a static local function must not be on a single line. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStaticLocalFunctionAsync() + { + var testCode = @"public class TestClass +{ + public void TestMethod() + { + static int LocalFunction() [|{|] return 0; } + } +} +"; + + var fixedCode = @"public class TestClass +{ + public void TestMethod() + { + static int LocalFunction() + { + return 0; + } + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs index 59f5c451a..25cfc4ac4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs @@ -28,5 +28,43 @@ public void Method() await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that the body of an await foreach statement must be enclosed in braces. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestAwaitForEachStatementAsync() + { + var testCode = @"using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +public class Test +{ + public async Task MethodAsync(IAsyncEnumerable values) + { + await foreach (var value in values) + [|Console.WriteLine(value);|] + } +}"; + + var fixedCode = @"using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +public class Test +{ + public async Task MethodAsync(IAsyncEnumerable values) + { + await foreach (var value in values) + { + Console.WriteLine(value); + } + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs new file mode 100644 index 000000000..14c530378 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1505OpeningBracesMustNotBeFollowedByBlankLine, + StyleCop.Analyzers.LayoutRules.SA1505CodeFixProvider>; + + public partial class SA1505CSharp8UnitTests + { + /// + /// Verifies that a blank line between an opening brace and a nullable directive is reported. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestBlankLineBeforeNullableDirectiveAsync() + { + var testCode = @"public class TestClass +[|{|] + +#nullable enable + public void Method() + { + } +} +"; + + var fixedCode = @"public class TestClass +{ +#nullable enable + public void Method() + { + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that the opening brace of a switch expression must not be followed by a blank line. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + [|{|] + + 0 => 0, + _ => 1, + }; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, + _ => 1, + }; + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs new file mode 100644 index 000000000..4fc8c0e61 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs @@ -0,0 +1,54 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1507CodeMustNotContainMultipleBlankLinesInARow, + StyleCop.Analyzers.LayoutRules.SA1507CodeFixProvider>; + + public partial class SA1507CSharp8UnitTests + { + /// + /// Verifies that multiple blank lines before a nullable directive are reported. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestMultipleBlankLinesBeforeNullableDirectiveAsync() + { + var testCode = @"public class TestClass +{ + public void First() + { + } +[| + + +|]#nullable enable + public void Second() + { + } +} +"; + + var fixedCode = @"public class TestClass +{ + public void First() + { + } + +#nullable enable + public void Second() + { + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs new file mode 100644 index 000000000..4c34ed757 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1508ClosingBracesMustNotBePrecededByBlankLine, + StyleCop.Analyzers.LayoutRules.SA1508CodeFixProvider>; + + public partial class SA1508CSharp8UnitTests + { + /// + /// Verifies that the closing brace of a switch expression must not be preceded by a blank line. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, + _ => 1, + + [|}|]; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, + _ => 1, + }; + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs new file mode 100644 index 000000000..498cd755d --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1516ElementsMustBeSeparatedByBlankLine, + StyleCop.Analyzers.LayoutRules.SA1516CodeFixProvider>; + + public partial class SA1516CSharp8UnitTests + { + /// + /// Verifies that a nullable directive does not count as the blank line required between two elements. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestNullableDirectiveBetweenElementsAsync() + { + var testCode = @"public class TestClass +{ + public void First() + { + } +[|#nullable enable|] + public void Second() + { + } +} +"; + + var fixedCode = @"public class TestClass +{ + public void First() + { + } + +#nullable enable + public void Second() + { + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1400CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1400CSharp8UnitTests.cs new file mode 100644 index 000000000..98205fb32 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1400CSharp8UnitTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.MaintainabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.MaintainabilityRules.SA1400AccessModifierMustBeDeclared, + StyleCop.Analyzers.MaintainabilityRules.SA1400CodeFixProvider>; + + public partial class SA1400CSharp8UnitTests + { + /// + /// Verifies that no access modifier is required on an interface member, including the kinds of member that + /// C# 8 added: a method with a default implementation, a static method and a static field. Interface + /// members are implicitly public, so the rule deliberately leaves all of them alone. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestInterfaceMembersAsync() + { + var testCode = @"public interface ITest +{ + static int Field; + + static ITest() { } + + int Property { get; set; } + + event System.EventHandler Event; + + void Method(); + + void DefaultMethod() + { + } + + static void StaticMethod() + { + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs new file mode 100644 index 000000000..582629094 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.MaintainabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.MaintainabilityRules.SA1407ArithmeticExpressionsMustDeclarePrecedence, + StyleCop.Analyzers.MaintainabilityRules.SA1407SA1408CodeFixProvider>; + + public partial class SA1407CSharp8UnitTests + { + /// + /// Verifies that arithmetic precedence is still checked on the right hand side of a null-coalescing assignment. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestNullCoalescingAssignmentAsync() + { + var testCode = @"public class Foo +{ + public void Bar(int? x) + { + x ??= 1 + [|1 * 1|]; + } +}"; + + var fixedCode = @"public class Foo +{ + public void Bar(int? x) + { + x ??= 1 + (1 * 1); + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs new file mode 100644 index 000000000..8e379a93a --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.MaintainabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.MaintainabilityRules.SA1408ConditionalExpressionsMustDeclarePrecedence, + StyleCop.Analyzers.MaintainabilityRules.SA1407SA1408CodeFixProvider>; + + public partial class SA1408CSharp8UnitTests + { + /// + /// Verifies that conditional precedence is still checked on the right hand side of a null-coalescing assignment. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestNullCoalescingAssignmentAsync() + { + var testCode = @"public class Foo +{ + public void Bar(bool? x) + { + x ??= [|true && false|] || true; + } +}"; + + var fixedCode = @"public class Foo +{ + public void Bar(bool? x) + { + x ??= (true && false) || true; + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs new file mode 100644 index 000000000..301ee3732 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.NamingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.NamingRules.SA1300ElementMustBeginWithUpperCaseLetter, + StyleCop.Analyzers.NamingRules.RenameToUpperCaseCodeFixProvider>; + + public partial class SA1300CSharp8UnitTests + { + /// + /// Verifies that a static local function must begin with an upper-case letter. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStaticLocalFunctionAsync() + { + var testCode = @"public class TestClass +{ + public void TestMethod() + { + static int [|localFunction|]() + { + return 0; + } + } +} +"; + + var fixedCode = @"public class TestClass +{ + public void TestMethod() + { + static int LocalFunction() + { + return 0; + } + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs new file mode 100644 index 000000000..8f4e141e1 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.NamingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.NamingRules.SA1316TupleElementNamesShouldUseCorrectCasing, + StyleCop.Analyzers.NamingRules.SA1316CodeFixProvider>; + + public partial class SA1316CSharp8UnitTests + { + // TODO: Use from base class instead + private const string PascalCaseTestSettings = @" +{ + ""settings"": { + ""namingRules"": { + ""tupleElementNameCasing"": ""PascalCase"" + } + } +} +"; + + /// + /// Verifies that the names of an await foreach deconstruction are exempt from the + /// configured casing just like those of an ordinary foreach deconstruction. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestAwaitForEachDeconstructionAsync() + { + var testCode = @" +using System.Collections.Generic; +using System.Threading.Tasks; + +public class TypeName +{ + public async Task MethodNameAsync(IAsyncEnumerable<(string Name, string Value)> list) + { + await foreach ((string name, string value) in list) + { + } + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, PascalCaseTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs new file mode 100644 index 000000000..fe881aaec --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.OrderingRules.SA1201ElementsMustAppearInTheCorrectOrder, + StyleCop.Analyzers.OrderingRules.ElementOrderCodeFixProvider>; + + public partial class SA1201CSharp8UnitTests + { + /// + /// Verifies that readonly instance members are ordered by element kind like any other member. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestReadonlyInstanceMemberAsync() + { + var testCode = @"public struct TestStruct +{ + public readonly int Method() => 0; + + public readonly int {|#0:Property|} => 0; +} +"; + + var fixedCode = @"public struct TestStruct +{ + public readonly int Property => 0; + + public readonly int Method() => 0; +} +"; + + var expected = Diagnostic().WithLocation(0).WithArguments("property", "method"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that the members an interface may hold from C# 8 onwards are ordered by element kind like the + /// members of any other type. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestInterfaceMembersAsync() + { + var testCode = @"public interface ITest +{ + void Method() + { + } + + int {|#0:Property|} { get; set; } +} +"; + + var fixedCode = @"public interface ITest +{ + int Property { get; set; } + + void Method() + { + } +} +"; + + var expected = Diagnostic().WithLocation(0).WithArguments("property", "method"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs index bc8b393e3..46591affe 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs @@ -56,5 +56,33 @@ public async Task TestPropertiesOfInterfaceAsync() NumberOfFixAllIterations = 2, }.RunAsync(CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that readonly instance members are ordered by access like any other member. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestReadonlyInstanceMemberAsync() + { + var testCode = @"public struct TestStruct +{ + private readonly int Method() => 0; + + public readonly int {|#0:OtherMethod|}() => 0; +} +"; + + var fixedCode = @"public struct TestStruct +{ + public readonly int OtherMethod() => 0; + + private readonly int Method() => 0; +} +"; + + var expected = Diagnostic().WithLocation(0).WithArguments("public", "private"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs new file mode 100644 index 000000000..3b6f5807e --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.OrderingRules.SA1204StaticElementsMustAppearBeforeInstanceElements, + StyleCop.Analyzers.OrderingRules.ElementOrderCodeFixProvider>; + + public partial class SA1204CSharp8UnitTests + { + /// + /// Verifies that a readonly instance member is ordered as an instance member. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestReadonlyInstanceMemberAsync() + { + var testCode = @"public struct TestStruct +{ + public readonly int Method() => 0; + + public static int [|StaticMethod|]() => 0; +} +"; + + var fixedCode = @"public struct TestStruct +{ + public static int StaticMethod() => 0; + + public readonly int Method() => 0; +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that a static interface member must appear before an instance one. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestInterfaceMembersAsync() + { + var testCode = @"public interface ITest +{ + void Method() + { + } + + static void [|StaticMethod|]() + { + } +} +"; + + var fixedCode = @"public interface ITest +{ + static void StaticMethod() + { + } + + void Method() + { + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs new file mode 100644 index 000000000..0ba002fbf --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.OrderingRules.SA1206DeclarationKeywordsMustFollowOrder, + StyleCop.Analyzers.OrderingRules.SA1206CodeFixProvider>; + + public partial class SA1206CSharp8UnitTests + { + /// + /// Verifies that an access modifier must precede the readonly keyword of a readonly instance member. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestReadonlyInstanceMemberAsync() + { + var testCode = @"public struct TestStruct +{ + readonly {|#0:public|} int Method() => 0; +} +"; + + var fixedCode = @"public struct TestStruct +{ + public readonly int Method() => 0; +} +"; + + var expected = Diagnostic().WithLocation(0).WithArguments("public", "readonly"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that the static keyword of a static local function must precede the other modifiers. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStaticLocalFunctionAsync() + { + var testCode = @"using System.Threading.Tasks; + +public class TestClass +{ + public void TestMethod() + { + async {|#0:static|} Task LocalFunction() => await Task.CompletedTask; + } +} +"; + + var fixedCode = @"using System.Threading.Tasks; + +public class TestClass +{ + public void TestMethod() + { + static async Task LocalFunction() => await Task.CompletedTask; + } +} +"; + + var expected = Diagnostic().WithLocation(0).WithArguments("static", "async"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs new file mode 100644 index 000000000..0c6f3581f --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.OrderingRules.SA1214ReadonlyElementsMustAppearBeforeNonReadonlyElements, + StyleCop.Analyzers.OrderingRules.ElementOrderCodeFixProvider>; + + public partial class SA1214CSharp8UnitTests + { + /// + /// Verifies that the rule orders readonly fields only, and that a readonly instance member + /// is not treated as a readonly element. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestReadonlyInstanceMemberAsync() + { + var testCode = @"public struct TestStruct +{ + public int Field; + + public readonly int [|ReadonlyField|]; + + public readonly int Method() => 0; +} +"; + + var fixedCode = @"public struct TestStruct +{ + public readonly int ReadonlyField; + + public int Field; + + public readonly int Method() => 0; +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs index 83c8d12dd..3d74fb2e3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs @@ -29,5 +29,82 @@ public bool Method(Test arg) await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that a local call in the body of an await foreach statement must be prefixed with this. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestAwaitForEachStatementAsync() + { + var testCode = @"using System.Collections.Generic; +using System.Threading.Tasks; + +public class Test +{ + public async Task MethodAsync(IAsyncEnumerable values) + { + await foreach (var value in values) + { + [|Handle|](value); + } + } + + public void Handle(int value) + { + } +}"; + + var fixedCode = @"using System.Collections.Generic; +using System.Threading.Tasks; + +public class Test +{ + public async Task MethodAsync(IAsyncEnumerable values) + { + await foreach (var value in values) + { + this.Handle(value); + } + } + + public void Handle(int value) + { + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that a local call in the default implementation of an interface member + /// must be prefixed with this. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestDefaultInterfaceMethodAsync() + { + var testCode = @"public interface ITest +{ + void Method(); + + void DefaultMethod() + { + [|Method|](); + } +}"; + + var fixedCode = @"public interface ITest +{ + void Method(); + + void DefaultMethod() + { + this.Method(); + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs new file mode 100644 index 000000000..27387c6ad --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1111ClosingParenthesisMustBeOnLineOfLastParameter, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1111CSharp8UnitTests + { + /// + /// Verifies that the closing parenthesis of a positional pattern is inspected the + /// same way as the closing parenthesis of a parameter list. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + 2 + [|)|]; + } +} +"; + + var fixedCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + 2); + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs new file mode 100644 index 000000000..13a5438d6 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1112CSharp8UnitTests + { + /// + /// Verifies that the parentheses of an empty positional pattern are inspected the + /// same way as the parentheses of an empty parameter list. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct() + { + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point( + [|)|]; + } +} +"; + + var fixedCode = @"public class Point +{ + public void Deconstruct() + { + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(); + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs new file mode 100644 index 000000000..3f203026f --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1113CommaMustBeOnSameLineAsPreviousParameter, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1113CSharp8UnitTests + { + /// + /// Verifies that the commas of a positional pattern are inspected the same way as + /// the commas of a parameter list. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1 + [|,|] 2); + } +} +"; + + var fixedCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + 2); + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs new file mode 100644 index 000000000..0a0485a3b --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopDiagnosticVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1115ParameterMustFollowComma>; + + public partial class SA1115CSharp8UnitTests + { + /// + /// Verifies that the subpatterns of a positional pattern are inspected the same way + /// as the parameters of a parameter list. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + + [|2|]); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs new file mode 100644 index 000000000..1547574ba --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs @@ -0,0 +1,68 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1116SplitParametersMustStartOnLineAfterDeclaration, + StyleCop.Analyzers.ReadabilityRules.SA1116CodeFixProvider>; + + public partial class SA1116CSharp8UnitTests + { + /// + /// Verifies that the subpatterns of a positional pattern are inspected the same way + /// as the parameters of a parameter list. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point([|1|], + 2 + ); + } +} +"; + + var fixedCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point( + 1, + 2 + ); + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs new file mode 100644 index 000000000..d9470f6d2 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopDiagnosticVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1117ParametersMustBeOnSameLineOrSeparateLines>; + + public partial class SA1117CSharp8UnitTests + { + /// + /// Verifies that the subpatterns of a two-element positional pattern are inspected + /// the same way as the parameters of a parameter list. With only two elements, any relative placement of + /// the first and second subpattern establishes a valid line pattern by definition, so no diagnostic is + /// produced (matching the behavior for a two-parameter parameter list). + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + 2 + ); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that a diagnostic is produced when the subpatterns of a positional pattern + /// are not all on the same line or each on a separate line. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestMultiLinePositionalPatternInvalidAsync() + { + var testCode = @"public class Point3 +{ + public void Deconstruct(out int x, out int y, out int z) + { + x = 0; + y = 0; + z = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point3(1, 2, + [|3|]); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1137CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1137CSharp8UnitTests.cs new file mode 100644 index 000000000..8ad454d6f --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1137CSharp8UnitTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1137ElementsShouldHaveTheSameIndentation, + StyleCop.Analyzers.ReadabilityRules.IndentationCodeFixProvider>; + + public partial class SA1137CSharp8UnitTests + { + /// + /// Verifies that the arms of a switch expression, which C# 8 introduced, are not required to share indentation. The analyzer registers SwitchStatement but not SwitchExpression. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, +[| |]_ => 1, + }; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, + _ => 1, + }; + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs new file mode 100644 index 000000000..10c705c93 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1141UseTupleSyntax, + StyleCop.Analyzers.ReadabilityRules.SA1141CodeFixProvider>; + + public partial class SA1141CSharp8UnitTests + { + /// + /// Verifies that a tuple pattern is not reported. The analyzer inspects type + /// syntax, not patterns, so the pattern itself is never a candidate for tuple syntax. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestTuplePatternAsync() + { + var testCode = @"public class TestClass +{ + public bool TestMethod((int, int) value) + { + return value is (1, 2); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs new file mode 100644 index 000000000..b457487e1 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SX1101DoNotPrefixLocalMembersWithThis, + StyleCop.Analyzers.ReadabilityRules.SX1101CodeFixProvider>; + + public partial class SX1101CSharp8UnitTests + { + /// + /// Verifies that a this prefix in the default implementation of an interface member + /// is detected and removed. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestDefaultInterfaceMethodAsync() + { + var testCode = @"public interface ITest +{ + void Method(); + + void DefaultMethod() + { + [|this|].Method(); + } +}"; + + var fixedCode = @"public interface ITest +{ + void Method(); + + void DefaultMethod() + { + Method(); + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs new file mode 100644 index 000000000..d25afc320 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs @@ -0,0 +1,284 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.SpacingRules.SA1000KeywordsMustBeSpacedCorrectly, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1000CSharp8UnitTests + { + /// + /// Verifies the handling of the stackalloc keyword before a constructed unmanaged type. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStackAllocOfConstructedUnmanagedTypeAsync() + { + var testCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + Foo* data1 = {|#0:stackalloc|}@Foo[3]; + } +} +"; + + var fixedCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + Foo* data1 = stackalloc @Foo[3]; + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("stackalloc", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of the sizeof keyword applied to a constructed unmanaged type. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSizeOfConstructedUnmanagedTypeAsync() + { + var testCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + var size1 = {|#0:sizeof|} (Foo); + } +} +"; + + var fixedCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + var size1 = sizeof(Foo); + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("sizeof", " not", "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of the stackalloc keyword in a nested expression. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStackAllocInNestedExpressionAsync() + { + var testCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar({|#0:stackalloc|}@Int32[3]); + } + + public void Bar(Span value) + { + } +} +"; + + var fixedCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc @Int32[3]); + } + + public void Bar(Span value) + { + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("stackalloc", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of the using keyword of a using declaration. + /// The keyword is followed by a type rather than by an opening parenthesis here. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestUsingDeclarationAsync() + { + var testCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + {|#0:using|}@IDisposable resource = null; + } +} +"; + + var fixedCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + using @IDisposable resource = null; + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("using", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of the using keyword of an await using declaration. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestAwaitUsingDeclarationAsync() + { + var testCode = @"using System; +using System.Threading.Tasks; + +public class TestClass +{ + public async Task TestMethodAsync() + { + await {|#0:using|}@IAsyncDisposable resource = null; + } +} +"; + + var fixedCode = @"using System; +using System.Threading.Tasks; + +public class TestClass +{ + public async Task TestMethodAsync() + { + await using @IAsyncDisposable resource = null; + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("using", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of the foreach keyword of an await foreach statement. The + /// await and foreach keywords are adjacent here. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestAwaitForEachStatementAsync() + { + var testCode = @"using System.Collections.Generic; +using System.Threading.Tasks; + +public class TestClass +{ + public async Task TestMethodAsync(IAsyncEnumerable values) + { + await {|#0:foreach|}(var value in values) + { + } + } +} +"; + + var fixedCode = @"using System.Collections.Generic; +using System.Threading.Tasks; + +public class TestClass +{ + public async Task TestMethodAsync(IAsyncEnumerable values) + { + await foreach (var value in values) + { + } + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("foreach", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of the switch keyword of a switch expression. The keyword is + /// followed by a brace here rather than by an opening parenthesis, and is preceded by the governing + /// expression. Only the side after the keyword is checked, so the second case is not reported. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionAsync() + { + var testCode = @"public class TestClass +{ + public void TestMethod(int value) + { + var result1 = value {|#0:switch|}{ _ => 0 }; + var result2 = value switch { _ => 0 }; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public void TestMethod(int value) + { + var result1 = value switch { _ => 0 }; + var result2 = value switch { _ => 0 }; + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("switch", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs index 094b3528a..f66b32e23 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs @@ -52,5 +52,43 @@ public void TestMethod(object?[] arguments) await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of the semicolon of a using declaration. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestUsingDeclarationAsync() + { + var testCode = @"namespace TestNamespace +{ + using System; + + public class TestClass + { + public void TestMethod() + { + using IDisposable resource = null [|;|] + } + } +} +"; + + var fixedCode = @"namespace TestNamespace +{ + using System; + + public class TestClass + { + public void TestMethod() + { + using IDisposable resource = null; + } + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs index b41d44672..9f44c7bbd 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs @@ -217,5 +217,39 @@ public void TestMethod(System.Action? x) await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that the arrow of a switch expression arm must be surrounded by whitespace. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionArmAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch { _{|#0:=>|}0 }; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch { _ => 0 }; + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorPrecededByWhitespace).WithLocation(0).WithArguments("=>"), + Diagnostic(DescriptorFollowedByWhitespace).WithLocation(0).WithArguments("=>"), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1009CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1009CSharp8UnitTests.cs index e76c0e5b5..020721498 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1009CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1009CSharp8UnitTests.cs @@ -194,5 +194,40 @@ public string TestMethod() var expected = Diagnostic(DescriptorNotFollowed).WithLocation(0); await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of a closing parenthesis inside an interpolation of an interpolated verbatim + /// string, which C# 8 allows to be written as @$"..." as well as $@"...". + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestClosingParenthesisInInterpolatedVerbatimStringAsync() + { + const string testCode = @" +public class Foo +{ + public string TestMethod() + { + return @$""{Bar( [|)|]}""; + } + + public string Bar() => ""x""; +} +"; + + const string fixedCode = @" +public class Foo +{ + public string TestMethod() + { + return @$""{Bar()}""; + } + + public string Bar() => ""x""; +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs new file mode 100644 index 000000000..259bfdb32 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs @@ -0,0 +1,238 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.SpacingRules.SA1010OpeningSquareBracketsMustBeSpacedCorrectly; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.SpacingRules.SA1010OpeningSquareBracketsMustBeSpacedCorrectly, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1010CSharp8UnitTests + { + /// + /// Verifies the handling of a stackalloc of a constructed unmanaged type. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStackAllocOfConstructedUnmanagedTypeAsync() + { + var testCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + Foo* data1 = stackalloc Foo {|#0:[|}3]; + Foo* data2 = stackalloc Foo{|#1:[|} 3]; + } +} +"; + + var fixedCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + Foo* data1 = stackalloc Foo[3]; + Foo* data2 = stackalloc Foo[3]; + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of an array of a constructed unmanaged type accessed through a pointer. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestPointerIndexingOfConstructedUnmanagedTypeAsync() + { + var testCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod(Foo* data) + { + var value1 = data {|#0:[|}0].Value; + var value2 = data{|#1:[|} 0].Value; + } +} +"; + + var fixedCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod(Foo* data) + { + var value1 = data[0].Value; + var value2 = data[0].Value; + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of the opening bracket of a stackalloc in a nested expression. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStackAllocInNestedExpressionAsync() + { + var testCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc int {|#0:[|}3]); + Bar(stackalloc int{|#1:[|} 3]); + } + + public void Bar(Span value) + { + } +} +"; + + var fixedCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc int[3]); + Bar(stackalloc int[3]); + } + + public void Bar(Span value) + { + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of an index-from-end argument. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestIndexFromEndAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int[] values) + { + var value1 = values {|#0:[|}^1]; + var value2 = values{|#1:[|} ^1]; + return value1 + value2 + values[^1]; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int[] values) + { + var value1 = values[^1]; + var value2 = values[^1]; + return value1 + value2 + values[^1]; + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of a range argument. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestRangeAsync() + { + var testCode = @"public class TestClass +{ + public void TestMethod(int[] values) + { + var range1 = values {|#0:[|}1..2]; + var range2 = values{|#1:[|} ..^1]; + var range3 = TestMethod2(values {|#2:[|}..]); + } + + public int TestMethod2(int[] values) => 0; +} +"; + + var fixedCode = @"public class TestClass +{ + public void TestMethod(int[] values) + { + var range1 = values[1..2]; + var range2 = values[..^1]; + var range3 = TestMethod2(values[..]); + } + + public int TestMethod2(int[] values) => 0; +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + Diagnostic(DescriptorNotPreceded).WithLocation(2), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs index 6d492c20e..db6c777c7 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs @@ -119,5 +119,47 @@ public void TestMethod(int[] arg) await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of the closing bracket of a stackalloc in a nested expression. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStackAllocInNestedExpressionAsync() + { + var testCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc int[3 {|#0:]|}); + } + + public void Bar(Span value) + { + } +} +"; + + var fixedCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc int[3]); + } + + public void Bar(Span value) + { + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments(" not", "preceded"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1013CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1013CSharp8UnitTests.cs index 24df05fb0..37e08567b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1013CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1013CSharp8UnitTests.cs @@ -88,5 +88,36 @@ public void TestMethod() await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Validates the handling of the closing brace of an interpolation in an interpolated verbatim string, which + /// C# 8 allows to be written as @$"..." as well as $@"...". + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestClosingBraceInInterpolatedVerbatimStringAsync() + { + const string testCode = @" +public class Foo +{ + public void TestMethod(string value) + { + var a = @$""{value [|}|]""; + } +} +"; + + const string fixedCode = @" +public class Foo +{ + public void TestMethod(string value) + { + var a = @$""{value}""; + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs new file mode 100644 index 000000000..635b6e9cf --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.SpacingRules.SA1026CodeMustNotContainSpaceAfterNewKeywordInImplicitlyTypedArrayAllocation, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1026CSharp8UnitTests + { + /// + /// Verifies the handling of an implicitly typed stackalloc in a nested expression. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestImplicitStackAllocInNestedExpressionAsync() + { + var testCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar({|#0:stackalloc|} [] { 1, 2, 3 }); + } + + public void Bar(Span value) + { + } +} +"; + + var fixedCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc[] { 1, 2, 3 }); + } + + public void Bar(Span value) + { + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("stackalloc"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs new file mode 100644 index 000000000..3537a9301 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.SpacingRules.SA1027UseTabsCorrectly, + StyleCop.Analyzers.SpacingRules.SA1027CodeFixProvider>; + + public partial class SA1027CSharp8UnitTests + { + /// + /// Verifies that a tab inside a nullable directive is reported. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestTabInNullableDirectiveAsync() + { + // Written with escapes rather than a verbatim string because the test code contains a tab. + var testCode = + "#nullable[|\t|]enable\r\n" + + "public class TestClass\r\n" + + "{\r\n" + + "}\r\n"; + + var fixedCode = + "#nullable enable\r\n" + + "public class TestClass\r\n" + + "{\r\n" + + "}\r\n"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs new file mode 100644 index 000000000..7e013ce2a --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.SpacingRules.SA1028CodeMustNotContainTrailingWhitespace, + StyleCop.Analyzers.SpacingRules.SA1028CodeFixProvider>; + + public partial class SA1028CSharp8UnitTests + { + /// + /// Verifies that trailing whitespace after a nullable directive is reported. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestTrailingWhitespaceAfterNullableDirectiveAsync() + { + var testCode = @"#nullable enable[| |] +public class TestClass +{ +} +"; + + var fixedCode = @"#nullable enable +public class TestClass +{ +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/DeclarationModifiersHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/DeclarationModifiersHelper.cs index dcf01acb4..96825229d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/DeclarationModifiersHelper.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/DeclarationModifiersHelper.cs @@ -96,7 +96,7 @@ internal static SyntaxTokenList AddModifiers(SyntaxTokenList modifiers, ref Synt return modifiers; } - internal static SyntaxTokenList GetModifiers(this MemberDeclarationSyntax syntax) + internal static SyntaxTokenList GetModifiers(this SyntaxNode syntax) { if (syntax is BaseMethodDeclarationSyntax) { @@ -122,6 +122,10 @@ internal static SyntaxTokenList GetModifiers(this MemberDeclarationSyntax syntax { return ((IncompleteMemberSyntax)syntax).Modifiers; } + else if (LocalFunctionStatementSyntaxWrapper.IsInstance(syntax)) + { + return ((LocalFunctionStatementSyntaxWrapper)syntax).Modifiers; + } return default; } @@ -179,6 +183,9 @@ internal static SyntaxNode WithModifiers(this SyntaxNode node, SyntaxTokenList m case SyntaxKind.EventFieldDeclaration: return ((EventFieldDeclarationSyntax)node).WithModifiers(modifiers); + case SyntaxKindEx.LocalFunctionStatement: + return ((LocalFunctionStatementSyntaxWrapper)node).WithModifiers(modifiers); + default: return node; } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs index 6ab64cca2..273eea98e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs @@ -74,6 +74,7 @@ internal class SA1500BracesForMultiLineStatementsMustNotShareLine : DiagnosticAn private static readonly Action AccessorListAction = HandleAccessorList; private static readonly Action BlockAction = HandleBlock; private static readonly Action SwitchStatementAction = HandleSwitchStatement; + private static readonly Action SwitchExpressionAction = HandleSwitchExpression; private static readonly Action InitializerExpressionAction = HandleInitializerExpression; private static readonly Action AnonymousObjectCreationExpressionAction = HandleAnonymousObjectCreationExpression; @@ -94,6 +95,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(AccessorListAction, SyntaxKind.AccessorList); context.RegisterSyntaxNodeAction(BlockAction, SyntaxKind.Block); context.RegisterSyntaxNodeAction(SwitchStatementAction, SyntaxKind.SwitchStatement); + context.RegisterSyntaxNodeAction(SwitchExpressionAction, SyntaxKindEx.SwitchExpression); context.RegisterSyntaxNodeAction(InitializerExpressionAction, SyntaxKinds.InitializerExpression); context.RegisterSyntaxNodeAction(AnonymousObjectCreationExpressionAction, SyntaxKind.AnonymousObjectCreationExpression); } @@ -128,6 +130,12 @@ private static void HandleSwitchStatement(SyntaxNodeAnalysisContext context, Sty CheckBraces(context, settings, syntax.OpenBraceToken, syntax.CloseBraceToken); } + private static void HandleSwitchExpression(SyntaxNodeAnalysisContext context, StyleCopSettings settings) + { + var syntax = (SwitchExpressionSyntaxWrapper)context.Node; + CheckBraces(context, settings, syntax.OpenBraceToken, syntax.CloseBraceToken); + } + private static void HandleInitializerExpression(SyntaxNodeAnalysisContext context, StyleCopSettings settings) { var syntax = (InitializerExpressionSyntax)context.Node; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1505OpeningBracesMustNotBeFollowedByBlankLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1505OpeningBracesMustNotBeFollowedByBlankLine.cs index 0d56eb280..f98521a62 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1505OpeningBracesMustNotBeFollowedByBlankLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1505OpeningBracesMustNotBeFollowedByBlankLine.cs @@ -56,6 +56,7 @@ internal class SA1505OpeningBracesMustNotBeFollowedByBlankLine : DiagnosticAnaly private static readonly Action InitializerExpressionAction = HandleInitializerExpression; private static readonly Action AnonymousObjectCreationExpressionAction = HandleAnonymousObjectCreationExpression; private static readonly Action SwitchStatementAction = HandleSwitchStatement; + private static readonly Action SwitchExpressionAction = HandleSwitchExpression; private static readonly Action NamespaceDeclarationAction = HandleNamespaceDeclaration; private static readonly Action BaseTypeDeclarationAction = HandleBaseTypeDeclaration; private static readonly Action AccessorListAction = HandleAccessorList; @@ -71,6 +72,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(InitializerExpressionAction, SyntaxKinds.InitializerExpression); context.RegisterSyntaxNodeAction(AnonymousObjectCreationExpressionAction, SyntaxKind.AnonymousObjectCreationExpression); context.RegisterSyntaxNodeAction(SwitchStatementAction, SyntaxKind.SwitchStatement); + context.RegisterSyntaxNodeAction(SwitchExpressionAction, SyntaxKindEx.SwitchExpression); context.RegisterSyntaxNodeAction(NamespaceDeclarationAction, SyntaxKind.NamespaceDeclaration); context.RegisterSyntaxNodeAction(BaseTypeDeclarationAction, SyntaxKinds.BaseTypeDeclaration); context.RegisterSyntaxNodeAction(BaseTypeDeclarationAction, SyntaxKindEx.ExtensionBlockDeclaration); @@ -105,6 +107,12 @@ private static void HandleSwitchStatement(SyntaxNodeAnalysisContext context) AnalyzeOpenBrace(context, switchStatement.OpenBraceToken); } + private static void HandleSwitchExpression(SyntaxNodeAnalysisContext context) + { + var switchExpression = (SwitchExpressionSyntaxWrapper)context.Node; + AnalyzeOpenBrace(context, switchExpression.OpenBraceToken); + } + private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context) { var namespaceDeclaration = (NamespaceDeclarationSyntax)context.Node; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1508ClosingBracesMustNotBePrecededByBlankLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1508ClosingBracesMustNotBePrecededByBlankLine.cs index e68f697ed..51ff67ca3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1508ClosingBracesMustNotBePrecededByBlankLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1508ClosingBracesMustNotBePrecededByBlankLine.cs @@ -56,6 +56,7 @@ internal class SA1508ClosingBracesMustNotBePrecededByBlankLine : DiagnosticAnaly private static readonly Action InitializerExpressionAction = HandleInitializerExpression; private static readonly Action AnonymousObjectCreationExpressionAction = HandleAnonymousObjectCreationExpression; private static readonly Action SwitchStatementAction = HandleSwitchStatement; + private static readonly Action SwitchExpressionAction = HandleSwitchExpression; private static readonly Action NamespaceDeclarationAction = HandleNamespaceDeclaration; private static readonly Action BaseTypeDeclarationAction = HandleBaseTypeDeclaration; private static readonly Action AccessorListAction = HandleAccessorList; @@ -71,6 +72,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(InitializerExpressionAction, SyntaxKinds.InitializerExpression); context.RegisterSyntaxNodeAction(AnonymousObjectCreationExpressionAction, SyntaxKind.AnonymousObjectCreationExpression); context.RegisterSyntaxNodeAction(SwitchStatementAction, SyntaxKind.SwitchStatement); + context.RegisterSyntaxNodeAction(SwitchExpressionAction, SyntaxKindEx.SwitchExpression); context.RegisterSyntaxNodeAction(NamespaceDeclarationAction, SyntaxKind.NamespaceDeclaration); context.RegisterSyntaxNodeAction(BaseTypeDeclarationAction, SyntaxKinds.BaseTypeDeclaration); context.RegisterSyntaxNodeAction(BaseTypeDeclarationAction, SyntaxKindEx.ExtensionBlockDeclaration); @@ -105,6 +107,12 @@ private static void HandleSwitchStatement(SyntaxNodeAnalysisContext context) AnalyzeCloseBrace(context, switchStatement.CloseBraceToken); } + private static void HandleSwitchExpression(SyntaxNodeAnalysisContext context) + { + var switchExpression = (SwitchExpressionSyntaxWrapper)context.Node; + AnalyzeCloseBrace(context, switchExpression.CloseBraceToken); + } + private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context) { var namespaceDeclaration = (NamespaceDeclarationSyntax)context.Node; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs index ded535a8f..402b8c75a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs @@ -27,6 +27,7 @@ internal static class SyntaxKindEx public const SyntaxKind AnnotationsKeyword = (SyntaxKind)8489; public const SyntaxKind VarKeyword = (SyntaxKind)8490; public const SyntaxKind UnderscoreToken = (SyntaxKind)8491; + public const SyntaxKind MultiLineRawStringLiteralToken = (SyntaxKind)8519; public const SyntaxKind ConflictMarkerTrivia = (SyntaxKind)8564; public const SyntaxKind IsPatternExpression = (SyntaxKind)8657; public const SyntaxKind RangeExpression = (SyntaxKind)8658; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs b/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs index adf748b53..7bf25ae2c 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs @@ -66,6 +66,7 @@ internal class SA1206DeclarationKeywordsMustFollowOrder : DiagnosticAnalyzerBase SyntaxKind.ConstructorDeclaration); private static readonly Action DeclarationAction = HandleDeclaration; + private static readonly Action LocalFunctionStatementAction = HandleLocalFunctionStatement; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -78,6 +79,8 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c // Register UnionDeclaration separately (with a duplicate-node guard, see the helper for why it is needed). context.RegisterSyntaxNodeActionWithDuplicateNodeGuard(DeclarationAction, SyntaxKindEx.UnionDeclaration); + + context.RegisterSyntaxNodeAction(LocalFunctionStatementAction, SyntaxKindEx.LocalFunctionStatement); } private static void HandleDeclaration(SyntaxNodeAnalysisContext context) @@ -86,6 +89,12 @@ private static void HandleDeclaration(SyntaxNodeAnalysisContext context) CheckModifiersOrderAndReportDiagnostics(context, modifiers); } + private static void HandleLocalFunctionStatement(SyntaxNodeAnalysisContext context) + { + var localFunctionStatement = (LocalFunctionStatementSyntaxWrapper)context.Node; + CheckModifiersOrderAndReportDiagnostics(context, localFunctionStatement.Modifiers); + } + private static void CheckModifiersOrderAndReportDiagnostics(SyntaxNodeAnalysisContext context, SyntaxTokenList modifiers) { var previousModifierType = ModifierType.None; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs index 2d3ff9d5e..72bcd8783 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs @@ -70,6 +70,7 @@ internal class SA1111ClosingParenthesisMustBeOnLineOfLastParameter : DiagnosticA private static readonly Action AnonymousMethodExpressionAction = HandleAnonymousMethodExpression; private static readonly Action ParenthesizedLambdaExpressionAction = HandleParenthesizedLambdaExpression; private static readonly Action ArrayCreationExpressionAction = HandleArrayCreationExpression; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -96,6 +97,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(AnonymousMethodExpressionAction, SyntaxKind.AnonymousMethodExpression); context.RegisterSyntaxNodeAction(ParenthesizedLambdaExpressionAction, SyntaxKind.ParenthesizedLambdaExpression); context.RegisterSyntaxNodeAction(ArrayCreationExpressionAction, SyntaxKind.ArrayCreationExpression); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); } private static void HandleArrayCreationExpression(SyntaxNodeAnalysisContext context) @@ -231,6 +233,22 @@ private static void HandlePrimaryConstructorBaseType(SyntaxNodeAnalysisContext c CheckArgumentList(context, typeDeclarationSyntax.ArgumentList); } + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + var subpatterns = positionalPatternClause.Subpatterns; + + if (!subpatterns.Any()) + { + return; + } + + CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( + context, + subpatterns.Last(), + positionalPatternClause.CloseParenToken); + } + private static void CheckParameterList(SyntaxNodeAnalysisContext context, ParameterListSyntax? parameterList) { if (parameterList == null || parameterList.IsMissing || !parameterList.Parameters.Any()) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis.cs index b233b3d62..740fa226f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis.cs @@ -48,6 +48,7 @@ internal class SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis : Diagno private static readonly Action ConstructorDeclarationAction = HandleConstructorDeclaration; private static readonly Action InvocationExpressionAction = HandleInvocationExpression; private static readonly Action ObjectCreationExpressionAction = HandleObjectCreationExpression; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -61,6 +62,26 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(ConstructorDeclarationAction, SyntaxKind.ConstructorDeclaration); context.RegisterSyntaxNodeAction(InvocationExpressionAction, SyntaxKind.InvocationExpression); context.RegisterSyntaxNodeAction(ObjectCreationExpressionAction, SyntaxKind.ObjectCreationExpression); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); + } + + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + + if (positionalPatternClause.Subpatterns.Any()) + { + return; + } + + if (!positionalPatternClause.OpenParenToken.IsMissing && + !positionalPatternClause.CloseParenToken.IsMissing) + { + CheckIfLocationOfOpenAndCloseTokensAreTheSame( + context, + positionalPatternClause.OpenParenToken, + positionalPatternClause.CloseParenToken); + } } private static void HandleObjectCreationExpression(SyntaxNodeAnalysisContext context) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1113CommaMustBeOnSameLineAsPreviousParameter.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1113CommaMustBeOnSameLineAsPreviousParameter.cs index 50752b05c..87987969e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1113CommaMustBeOnSameLineAsPreviousParameter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1113CommaMustBeOnSameLineAsPreviousParameter.cs @@ -70,6 +70,7 @@ internal class SA1113CommaMustBeOnSameLineAsPreviousParameter : DiagnosticAnalyz private static readonly Action ArrayCreationExpressionAction = HandleArrayCreationExpression; private static readonly Action ConstructorInitializerAction = HandleConstructorInitializer; private static readonly Action WithElementAction = HandleWithElement; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -92,6 +93,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(ArrayCreationExpressionAction, SyntaxKind.ArrayCreationExpression); context.RegisterSyntaxNodeAction(ConstructorInitializerAction, SyntaxKinds.ConstructorInitializer); context.RegisterSyntaxNodeAction(WithElementAction, SyntaxKindEx.WithElement); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); } private static void HandleArrayCreationExpression(SyntaxNodeAnalysisContext context) @@ -213,6 +215,17 @@ private static void HandleWithElement(SyntaxNodeAnalysisContext context) HandleBaseArgumentListSyntax(context, withElement.ArgumentList); } + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + var subpatterns = positionalPatternClause.Subpatterns; + + if (subpatterns.Count > 1) + { + CheckIfCommasAreAtTheSameLineAsThePreviousParameter(context, subpatterns.GetWithSeparators()); + } + } + private static void HandleBaseArgumentListSyntax(SyntaxNodeAnalysisContext context, BaseArgumentListSyntax argumentList) { if (argumentList != null && !argumentList.IsMissing) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1115ParameterMustFollowComma.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1115ParameterMustFollowComma.cs index 75b2639bb..03fe352b5 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1115ParameterMustFollowComma.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1115ParameterMustFollowComma.cs @@ -73,6 +73,7 @@ internal class SA1115ParameterMustFollowComma : DiagnosticAnalyzerBase private static readonly Action ElementBindingExpressionAction = HandleElementBindingExpression; private static readonly Action ImplicitElementAccessAction = HandleImplicitElementAccess; private static readonly Action WithElementAction = HandleWithElement; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -97,6 +98,13 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(ElementBindingExpressionAction, SyntaxKind.ElementBindingExpression); context.RegisterSyntaxNodeAction(ImplicitElementAccessAction, SyntaxKind.ImplicitElementAccess); context.RegisterSyntaxNodeAction(WithElementAction, SyntaxKindEx.WithElement); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); + } + + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + AnalyzeSyntaxList(context, SyntaxFactory.SeparatedList(positionalPatternClause.Subpatterns.GetWithSeparators())); } private static void HandleWithElement(SyntaxNodeAnalysisContext context) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1116SplitParametersMustStartOnLineAfterDeclaration.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1116SplitParametersMustStartOnLineAfterDeclaration.cs index 2fb441c35..389717bab 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1116SplitParametersMustStartOnLineAfterDeclaration.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1116SplitParametersMustStartOnLineAfterDeclaration.cs @@ -69,6 +69,7 @@ internal class SA1116SplitParametersMustStartOnLineAfterDeclaration : Diagnostic private static readonly Action AnonymousMethodExpressionAction = HandleAnonymousMethodExpression; private static readonly Action ParenthesizedLambdaExpressionAction = HandleParenthesizedLambdaExpression; private static readonly Action WithElementAction = HandleWithElement; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -93,6 +94,18 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(AnonymousMethodExpressionAction, SyntaxKind.AnonymousMethodExpression); context.RegisterSyntaxNodeAction(ParenthesizedLambdaExpressionAction, SyntaxKind.ParenthesizedLambdaExpression); context.RegisterSyntaxNodeAction(WithElementAction, SyntaxKindEx.WithElement); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); + } + + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + var subpatterns = positionalPatternClause.Subpatterns; + + if (subpatterns.Count > 1) + { + Analyze(context, positionalPatternClause.OpenParenToken, subpatterns[0], subpatterns[1]); + } } private static void HandleWithElement(SyntaxNodeAnalysisContext context) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1117ParametersMustBeOnSameLineOrSeparateLines.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1117ParametersMustBeOnSameLineOrSeparateLines.cs index 67b3f77ea..1310457db 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1117ParametersMustBeOnSameLineOrSeparateLines.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1117ParametersMustBeOnSameLineOrSeparateLines.cs @@ -79,6 +79,7 @@ internal class SA1117ParametersMustBeOnSameLineOrSeparateLines : DiagnosticAnaly private static readonly Action AnonymousMethodExpressionAction = HandleAnonymousMethodExpression; private static readonly Action ParenthesizedLambdaExpressionAction = HandleParenthesizedLambdaExpression; private static readonly Action WithElementAction = HandleWithElement; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } @@ -102,6 +103,13 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(AnonymousMethodExpressionAction, SyntaxKind.AnonymousMethodExpression); context.RegisterSyntaxNodeAction(ParenthesizedLambdaExpressionAction, SyntaxKind.ParenthesizedLambdaExpression); context.RegisterSyntaxNodeAction(WithElementAction, SyntaxKindEx.WithElement); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); + } + + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + Analyze(context, SyntaxFactory.SeparatedList(positionalPatternClause.Subpatterns.GetWithSeparators())); } private static void HandleWithElement(SyntaxNodeAnalysisContext context) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1122UseStringEmptyForEmptyStrings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1122UseStringEmptyForEmptyStrings.cs index 3f9a1c593..0339bf286 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1122UseStringEmptyForEmptyStrings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1122UseStringEmptyForEmptyStrings.cs @@ -43,6 +43,7 @@ internal class SA1122UseStringEmptyForEmptyStrings : DiagnosticAnalyzerBase CreateDiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, Description); private static readonly Action StringLiteralExpressionAction = HandleStringLiteralExpression; + private static readonly Action InterpolatedStringExpressionAction = HandleInterpolatedStringExpression; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -52,20 +53,23 @@ internal class SA1122UseStringEmptyForEmptyStrings : DiagnosticAnalyzerBase protected override void HandleCompilationStart(CompilationStartAnalysisContext context) { context.RegisterSyntaxNodeAction(StringLiteralExpressionAction, SyntaxKind.StringLiteralExpression); + context.RegisterSyntaxNodeAction(InterpolatedStringExpressionAction, SyntaxKind.InterpolatedStringExpression); } private static void HandleStringLiteralExpression(SyntaxNodeAnalysisContext context) { LiteralExpressionSyntax literalExpression = (LiteralExpressionSyntax)context.Node; - var token = literalExpression.Token; - if (token.IsKind(SyntaxKind.StringLiteralToken)) + + // TODO: Skip check of syntax kind? Might not be necessary. + if (token.IsKind(SyntaxKind.StringLiteralToken) || token.IsKind(SyntaxKindEx.MultiLineRawStringLiteralToken)) { if (HasToBeConstant(literalExpression)) { return; } + // TODO: Check this first instead? Should be faster. if (token.ValueText == string.Empty) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, literalExpression.GetLocation())); @@ -73,9 +77,27 @@ private static void HandleStringLiteralExpression(SyntaxNodeAnalysisContext cont } } - private static bool HasToBeConstant(LiteralExpressionSyntax literalExpression) + private static void HandleInterpolatedStringExpression(SyntaxNodeAnalysisContext context) + { + var interpolatedStringExpression = (InterpolatedStringExpressionSyntax)context.Node; + + // Only an interpolated string without any content at all is considered empty + if (interpolatedStringExpression.Contents.Count > 0) + { + return; + } + + if (HasToBeConstant(interpolatedStringExpression)) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create(Descriptor, interpolatedStringExpression.GetLocation())); + } + + private static bool HasToBeConstant(ExpressionSyntax expression) { - ExpressionSyntax outermostExpression = FindOutermostExpression(literalExpression); + ExpressionSyntax outermostExpression = FindOutermostExpression(expression); if (outermostExpression.Parent.IsKind(SyntaxKind.AttributeArgument) || outermostExpression.Parent.IsKind(SyntaxKind.CaseSwitchLabel) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1137ElementsShouldHaveTheSameIndentation.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1137ElementsShouldHaveTheSameIndentation.cs index 4cb30a678..27d5c1351 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1137ElementsShouldHaveTheSameIndentation.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1137ElementsShouldHaveTheSameIndentation.cs @@ -44,6 +44,7 @@ internal class SA1137ElementsShouldHaveTheSameIndentation : DiagnosticAnalyzerBa private static readonly Action AttributeArgumentListAction = HandleAttributeArgumentList; private static readonly Action BlockAction = HandleBlock; private static readonly Action SwitchStatementAction = HandleSwitchStatement; + private static readonly Action SwitchExpressionAction = HandleSwitchExpression; private static readonly Action InitializerExpressionAction = HandleInitializerExpression; private static readonly Action CollectionExpressionAction = HandleCollectionExpression; private static readonly Action AnonymousObjectCreationExpressionAction = HandleAnonymousObjectCreationExpression; @@ -76,6 +77,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(AttributeArgumentListAction, SyntaxKind.AttributeArgumentList); context.RegisterSyntaxNodeAction(BlockAction, SyntaxKind.Block); context.RegisterSyntaxNodeAction(SwitchStatementAction, SyntaxKind.SwitchStatement); + context.RegisterSyntaxNodeAction(SwitchExpressionAction, SyntaxKindEx.SwitchExpression); context.RegisterSyntaxNodeAction(InitializerExpressionAction, SyntaxKinds.InitializerExpression); context.RegisterSyntaxNodeAction(CollectionExpressionAction, SyntaxKindEx.CollectionExpression); context.RegisterSyntaxNodeAction(AnonymousObjectCreationExpressionAction, SyntaxKind.AnonymousObjectCreationExpression); @@ -263,6 +265,14 @@ private static void HandleInitializerExpression(SyntaxNodeAnalysisContext contex CheckElements(context, initializerExpression.Expressions); } + private static void HandleSwitchExpression(SyntaxNodeAnalysisContext context) + { + var switchExpression = (SwitchExpressionSyntaxWrapper)context.Node; + + CheckBraces(context, switchExpression.OpenBraceToken, switchExpression.CloseBraceToken); + CheckElements(context, switchExpression.Arms); + } + private static void HandleCollectionExpression(SyntaxNodeAnalysisContext context) { var collectionExpression = (CollectionExpressionSyntaxWrapper)context.Node; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1003SymbolsMustBeSpacedCorrectly.cs b/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1003SymbolsMustBeSpacedCorrectly.cs index 8ba542f56..ccfd257f4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1003SymbolsMustBeSpacedCorrectly.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1003SymbolsMustBeSpacedCorrectly.cs @@ -135,6 +135,7 @@ internal class SA1003SymbolsMustBeSpacedCorrectly : DiagnosticAnalyzerBase private static readonly Action EqualsValueClauseAction = HandleEqualsValueClause; private static readonly Action LambdaExpressionAction = HandleLambdaExpression; private static readonly Action ArrowExpressionClauseAction = HandleArrowExpressionClause; + private static readonly Action SwitchExpressionArmAction = HandleSwitchExpressionArm; /// /// Gets the descriptor for prefix unary expression that may not be followed by a comment. @@ -208,6 +209,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(EqualsValueClauseAction, SyntaxKind.EqualsValueClause); context.RegisterSyntaxNodeAction(LambdaExpressionAction, SyntaxKinds.LambdaExpression); context.RegisterSyntaxNodeAction(ArrowExpressionClauseAction, SyntaxKind.ArrowExpressionClause); + context.RegisterSyntaxNodeAction(SwitchExpressionArmAction, SyntaxKindEx.SwitchExpressionArm); } private static void HandleConstructorDeclaration(SyntaxNodeAnalysisContext context) @@ -379,6 +381,12 @@ private static void HandleArrowExpressionClause(SyntaxNodeAnalysisContext contex CheckToken(context, arrowExpressionClause.ArrowToken, true, true, true); } + private static void HandleSwitchExpressionArm(SyntaxNodeAnalysisContext context) + { + var switchExpressionArm = (SwitchExpressionArmSyntaxWrapper)context.Node; + CheckToken(context, switchExpressionArm.EqualsGreaterThanToken, true, true, true); + } + private static void CheckToken(SyntaxNodeAnalysisContext context, SyntaxToken token, bool withLeadingWhitespace, bool allowAtEndOfLine, bool withTrailingWhitespace, string? tokenText = null) { tokenText = tokenText ?? token.Text; diff --git a/documentation/SA1122.md b/documentation/SA1122.md index 928d035bd..c9383f739 100644 --- a/documentation/SA1122.md +++ b/documentation/SA1122.md @@ -33,6 +33,23 @@ This will cause the compiler to embed an empty string into the compiled code. Ra string s = string.Empty; ``` +Every form of empty string literal is reported, including verbatim, interpolated and raw ones: + +```csharp +string s1 = @""; +string s2 = $""; +string s3 = @$""; +string s4 = """ + + """; +``` + +A UTF-8 string literal such as `""u8` is not reported, because it is a `ReadOnlySpan` rather than a string. + +A violation does not occur where the language requires a constant, whichever form it is written in, because +`string.Empty` is a static read-only field rather than a constant and cannot be used in its place. That covers +attribute arguments, `case` labels, constant patterns, default parameter values, and `const` fields and locals. + ## How to fix violations To fix a violation of this rule, replace the hard-coded empty string with string.Empty. diff --git a/documentation/SA1206.md b/documentation/SA1206.md index fbe74c35f..e9c5d6849 100644 --- a/documentation/SA1206.md +++ b/documentation/SA1206.md @@ -35,6 +35,12 @@ Within an element declaration, keywords should appear in the following order: Using a standard ordering scheme for element declaration keywords can make the code more readable by highlighting the access level of each element. This can help prevent elements from being given a higher access level than needed. +The rule also applies to a local function: + +```csharp +static async Task LocalFunction() => await Task.CompletedTask; +``` + ## How To Fix Violations To fix an instance of this violation, order the keywords in the element's declaration as described above.