From b11d47ac2cbdbcda05b591f6ff8eaa5a9274a180 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Sat, 6 Dec 2025 11:01:00 -0600 Subject: [PATCH 1/5] Update SA1300 tests for record structs --- .../NamingRules/SA1300CSharp10UnitTests.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) 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); + } } } From f868720e7cd2edfddddf82e380e8903cde5b9970 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 5 Jan 2026 08:38:53 -0600 Subject: [PATCH 2/5] Update SA1201 documentation for record structs --- documentation/SA1201.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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. From ae5cb957c563f6af506b373b83336fc798b6b69f Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 5 Jan 2026 08:39:21 -0600 Subject: [PATCH 3/5] Update SA1204 for record structs --- .../OrderingRules/SA1204UnitTests.cs | 80 +++++-------------- ...lementsMustAppearBeforeInstanceElements.cs | 7 +- 2 files changed, 26 insertions(+), 61 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1204UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1204UnitTests.cs index c9a5b2667..54c69bb9b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1204UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1204UnitTests.cs @@ -9,6 +9,7 @@ namespace StyleCop.Analyzers.Test.OrderingRules using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.OrderingRules; + using StyleCop.Analyzers.Test.Helpers; using Xunit; using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< StyleCop.Analyzers.OrderingRules.SA1204StaticElementsMustAppearBeforeInstanceElements, @@ -128,63 +129,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 = @@ -194,16 +155,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/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; From 73d9e96f29f63847dbaba386560b1771c39353ba Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 5 Jan 2026 08:39:30 -0600 Subject: [PATCH 4/5] Update SA1214 for record structs --- .../OrderingRules/SA1214UnitTests.cs | 72 ++++++++----------- ...entsMustAppearBeforeNonReadonlyElements.cs | 7 +- 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1214UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1214UnitTests.cs index ff7f98063..c019d9e04 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1214UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1214UnitTests.cs @@ -8,6 +8,7 @@ namespace StyleCop.Analyzers.Test.OrderingRules using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; + using StyleCop.Analyzers.Test.Helpers; using Xunit; using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< StyleCop.Analyzers.OrderingRules.SA1214ReadonlyElementsMustAppearBeforeNonReadonlyElements, @@ -84,27 +85,28 @@ private void TestMethod10() { } await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } - [Fact] - public async Task TestTwoFieldsInClassStaticReadonlyFieldPlacedAfterStaticNonReadonlyAsync() + [Theory] + [MemberData(nameof(CommonMemberData.DataTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task TestTwoFieldsInClassStaticReadonlyFieldPlacedAfterStaticNonReadonlyAsync(string keyword) { - var testCode = @" -public class Foo -{ + var testCode = $@" +public {keyword} Foo +{{ private static int i = 0; - private static readonly int j = 0; -}"; + private static readonly int {{|#0:j|}} = 0; +}}"; var expected = new[] { - Diagnostic().WithLocation(5, 33), + Diagnostic().WithLocation(0), }; - var fixTestCode = @" -public class Foo -{ + 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); } @@ -143,36 +145,24 @@ public class Foo await VerifyCSharpFixAsync(testCode, expected, fixTestCode, CancellationToken.None).ConfigureAwait(false); } - [Fact] - public async Task TestTwoFieldsInClassStaticReadonlyFieldPlacedBeforeStaticNonReadonlyAsync() + [Theory] + [MemberData(nameof(CommonMemberData.DataTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task TestTwoFieldsInClassNonStaticReadonlyFieldPlacedAfterNonStaticNonReadonlyAsync(string keyword) { - 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() - { - 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/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; From 2fbedaab1ebeee410135bcfbfb58648611b4118f Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 5 Jan 2026 08:48:10 -0600 Subject: [PATCH 5/5] Update SA1642 documentation for record structs --- documentation/SA1642.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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: