diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/NamingRules/SA1300CSharp10UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/NamingRules/SA1300CSharp10UnitTests.cs index a73681caa..52afca31d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/NamingRules/SA1300CSharp10UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/NamingRules/SA1300CSharp10UnitTests.cs @@ -95,5 +95,38 @@ public async Task TestAllowedLowerCaseComplicatedFileScopedNamespaceIsNotReporte Settings = customTestSettings, }.RunAsync(CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(3979, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3979")] + public async Task TestRecordStructNameMustStartWithUpperCaseLetterAsync() + { + var testCode = @" +public record struct {|#0:r|}(int A) +{ + public {|#1:r|}(int a, int b) + : this(A: a) + { + } +} +"; + + var fixedCode = @" +public record struct R(int A) +{ + public R(int a, int b) + : this(A: a) + { + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic().WithLocation(0).WithArguments("r"), + Diagnostic().WithLocation(1).WithArguments("r"), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1204UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1204UnitTests.cs index fb7769f31..f7e82f18d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1204UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1204UnitTests.cs @@ -132,63 +132,23 @@ public class TestClass1 { } } /// - /// Verifies that the analyzer will properly handle non-static elements before static in a class. + /// Verifies that the analyzer will properly handle non-static elements before static in a type. /// + /// The keyword used to declare the type. /// A representing the asynchronous unit test. - [Fact] - public async Task TestOrderingInClassAsync() - { - var testCode = @"public class TestClass -{ - public int TestField1; - public static int TestField2; - public int TestProperty1 { get; set; } - public static int TestProperty2 { get; set; } - public void TestMethod1() { } - public static void TestMethod2() { } - -} -"; - - DiagnosticResult[] expected = - { - Diagnostic().WithLocation(4, 23), - Diagnostic().WithLocation(6, 23), - Diagnostic().WithLocation(8, 24), - }; - - var fixedCode = @"public class TestClass -{ - public static int TestField2; - public int TestField1; - public static int TestProperty2 { get; set; } - public int TestProperty1 { get; set; } - public static void TestMethod2() { } - public void TestMethod1() { } - -} -"; - - await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); - } - - /// - /// Verifies that the analyzer will properly handle non-static elements before static in a struct. - /// - /// A representing the asynchronous unit test. - [Fact] - public async Task TestOrderingInStructAsync() + [Theory] + [MemberData(nameof(CommonMemberData.DataTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task TestOrderingInClassAsync(string keyword) { - var testCode = @"public struct TestStruct -{ + var testCode = $@"public {keyword} TestClass +{{ public int TestField1; public static int TestField2; - public int TestProperty1 { get; set; } - public static int TestProperty2 { get; set; } - public void TestMethod1() { } - public static void TestMethod2() { } - -} + public int TestProperty1 {{ get; set; }} + public static int TestProperty2 {{ get; set; }} + public void TestMethod1() {{ }} + public static void TestMethod2() {{ }} +}} "; DiagnosticResult[] expected = @@ -198,16 +158,15 @@ public static void TestMethod2() { } Diagnostic().WithLocation(8, 24), }; - var fixedCode = @"public struct TestStruct -{ + var fixedCode = $@"public {keyword} TestClass +{{ public static int TestField2; public int TestField1; - public static int TestProperty2 { get; set; } - public int TestProperty1 { get; set; } - public static void TestMethod2() { } - public void TestMethod1() { } - -} + public static int TestProperty2 {{ get; set; }} + public int TestProperty1 {{ get; set; }} + public static void TestMethod2() {{ }} + public void TestMethod1() {{ }} +}} "; await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1214UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1214UnitTests.cs index e1c66ed4d..5cff234fd 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1214UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1214UnitTests.cs @@ -85,10 +85,35 @@ private void TestMethod10() { } await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + [Theory] + [MemberData(nameof(CommonMemberData.DataTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task TestTwoFieldsInClassStaticReadonlyFieldPlacedAfterStaticNonReadonlyAsync(string keyword) + { + var testCode = $@" +public {keyword} Foo +{{ + private static int i = 0; + private static readonly int {{|#0:j|}} = 0; +}}"; + + var expected = new[] + { + Diagnostic().WithLocation(0), + }; + + var fixTestCode = $@" +public {keyword} Foo +{{ + private static readonly int j = 0; + private static int i = 0; +}}"; + await VerifyCSharpFixAsync(testCode, expected, fixTestCode, CancellationToken.None).ConfigureAwait(false); + } + [Theory] [InlineData("\n")] [InlineData("\r\n")] - public async Task TestTwoFieldsInClassStaticReadonlyFieldPlacedAfterStaticNonReadonlyAsync(string lineEnding) + public async Task TestTwoFieldsInClassStaticReadonlyFieldPlacedAfterStaticNonReadonlyWithLineEndingAsync(string lineEnding) { var testCode = @" public class Foo @@ -146,36 +171,24 @@ public class Foo await VerifyCSharpFixAsync(testCode, expected, fixTestCode, CancellationToken.None).ConfigureAwait(false); } - [Fact] - public async Task TestTwoFieldsInClassStaticReadonlyFieldPlacedBeforeStaticNonReadonlyAsync() - { - var testCode = @" -public class Foo -{ - private static readonly int i = 0; - private static int j = 0; -}"; - - await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); - } - - [Fact] - public async Task TestTwoFieldsInClassNonStaticReadonlyFieldPlacedAfterNonStaticNonReadonlyAsync() + [Theory] + [MemberData(nameof(CommonMemberData.DataTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task TestTwoFieldsInClassNonStaticReadonlyFieldPlacedAfterNonStaticNonReadonlyAsync(string keyword) { - var testCode = @" -public class Foo -{ - private int i = 0; - private readonly int j = 0; -}"; - var fixedCode = @" -public class Foo -{ - private readonly int j = 0; - private int i = 0; -}"; - - var expected = Diagnostic().WithLocation(5, 26); + var testCode = $@" +public {keyword} Foo +{{ + private int i; + private readonly int {{|#0:j|}}; +}}"; + var fixedCode = $@" +public {keyword} Foo +{{ + private readonly int j; + private int i; +}}"; + + var expected = Diagnostic().WithLocation(0); await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1204StaticElementsMustAppearBeforeInstanceElements.cs b/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1204StaticElementsMustAppearBeforeInstanceElements.cs index 1e8ff1830..e348a3801 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1204StaticElementsMustAppearBeforeInstanceElements.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1204StaticElementsMustAppearBeforeInstanceElements.cs @@ -41,7 +41,12 @@ internal class SA1204StaticElementsMustAppearBeforeInstanceElements : Diagnostic new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.OrderingRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink); private static readonly ImmutableArray TypeDeclarationKinds = - ImmutableArray.Create(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.InterfaceDeclaration); + ImmutableArray.Create( + SyntaxKind.ClassDeclaration, + SyntaxKind.StructDeclaration, + SyntaxKind.InterfaceDeclaration, + SyntaxKindEx.RecordDeclaration, + SyntaxKindEx.RecordStructDeclaration); private static readonly Action CompilationUnitAction = HandleCompilationUnit; private static readonly Action BaseNamespaceDeclarationAction = HandleBaseNamespaceDeclaration; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1214ReadonlyElementsMustAppearBeforeNonReadonlyElements.cs b/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1214ReadonlyElementsMustAppearBeforeNonReadonlyElements.cs index a732a62f9..d0661ac9e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1214ReadonlyElementsMustAppearBeforeNonReadonlyElements.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1214ReadonlyElementsMustAppearBeforeNonReadonlyElements.cs @@ -12,6 +12,7 @@ namespace StyleCop.Analyzers.OrderingRules using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using StyleCop.Analyzers.Helpers; + using StyleCop.Analyzers.Lightup; using StyleCop.Analyzers.Settings.ObjectModel; /// @@ -34,7 +35,11 @@ internal class SA1214ReadonlyElementsMustAppearBeforeNonReadonlyElements : Diagn new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.OrderingRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink); private static readonly ImmutableArray TypeDeclarationKinds = - ImmutableArray.Create(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration); + ImmutableArray.Create( + SyntaxKind.ClassDeclaration, + SyntaxKind.StructDeclaration, + SyntaxKindEx.RecordDeclaration, + SyntaxKindEx.RecordStructDeclaration); private static readonly Action TypeDeclarationAction = HandleTypeDeclaration; diff --git a/documentation/SA1201.md b/documentation/SA1201.md index ccf2d2c51..f1db9a7a1 100644 --- a/documentation/SA1201.md +++ b/documentation/SA1201.md @@ -47,9 +47,9 @@ Within a class, struct, or interface, elements should be positioned in the follo * Indexers * Methods * Structs -* Classes* +* Classes -> 📝 For ordering purposes, C# 9 records are treated as classes. +> 📝 For ordering purposes, C# 9 records are treated as classes, and C# 10 record structs are treated as structs. Complying with a standard ordering scheme based on element type can increase the readability and maintainability of the file and encourage code reuse. diff --git a/documentation/SA1642.md b/documentation/SA1642.md index cdacbb95f..2e2ae326b 100644 --- a/documentation/SA1642.md +++ b/documentation/SA1642.md @@ -52,7 +52,19 @@ public MyStruct() } ``` -For C# 9 record types, constructors follow the same pattern: record classes use the word 'class' in the summary text, while record structs use 'struct'. +For record types, constructors follow the same pattern: record classes use the word 'class' in the summary text, while record structs use 'struct'. For example: + +```csharp +public record struct Customer +{ + /// + /// Initializes a new instance of the struct. + /// + public Customer() + { + } +} +``` If the class contains generic parameters, these can be annotated within the `cref` link using either of the following two formats: