diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/SpacingRules/SA1008CSharp9UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/SpacingRules/SA1008CSharp9UnitTests.cs index 2b1c749db..c379e3f03 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/SpacingRules/SA1008CSharp9UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/SpacingRules/SA1008CSharp9UnitTests.cs @@ -151,5 +151,83 @@ void M(int value) }; await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(3965, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3965")] + public async Task TestRecordInheritanceAsync() + { + const string testCode = @" +public abstract record BaseQuery; +public record MyQuery1 {|#0:(|} ) : BaseQuery; +public record MyQuery2{|#1:(|} ) : BaseQuery; +public record MyQuery3 {|#2:(|}) : BaseQuery;"; + const string fixedCode = @" +public abstract record BaseQuery; +public record MyQuery1() : BaseQuery; +public record MyQuery2() : BaseQuery; +public record MyQuery3() : BaseQuery;"; + + await new CSharpTest() + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = + { + // /0/Test0.cs(3,24): warning SA1008: Opening parenthesis should not be preceded by a space. + Diagnostic(DescriptorNotPreceded).WithLocation(0), + + // /0/Test0.cs(3,24): warning SA1008: Opening parenthesis should not be followed by a space. + Diagnostic(DescriptorNotFollowed).WithLocation(0), + + // /0/Test0.cs(4,23): warning SA1008: Opening parenthesis should not be followed by a space. + Diagnostic(DescriptorNotFollowed).WithLocation(1), + + // /0/Test0.cs(5,24): warning SA1008: Opening parenthesis should not be preceded by a space. + Diagnostic(DescriptorNotPreceded).WithLocation(2), + }, + TestCode = testCode, + FixedCode = fixedCode, + }.RunAsync(CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(3965, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3965")] + public async Task TestRecordBaseArgumentsWithMultilineSpacingAsync() + { + const string testCode = @" +public abstract record BaseRecord(string Text); + +public record Derived1(string Text) + : BaseRecord {|#0:(|} + Text) +{ +} + +public record Derived2(string Text) + : BaseRecord {|#1:(|} + Text); +"; + + const string fixedCode = @" +public abstract record BaseRecord(string Text); + +public record Derived1(string Text) + : BaseRecord( + Text) +{ +} + +public record Derived2(string Text) + : BaseRecord( + Text); +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotPreceded).WithLocation(1), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/SpacingRules/SA1009CSharp9UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/SpacingRules/SA1009CSharp9UnitTests.cs index 73cbb50ff..753345521 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/SpacingRules/SA1009CSharp9UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/SpacingRules/SA1009CSharp9UnitTests.cs @@ -53,5 +53,47 @@ public record MyQuery3() : BaseQuery;"; FixedCode = fixedCode, }.RunAsync(CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(3965, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3965")] + public async Task TestRecordBaseArgumentsWithMultilineSpacingAsync() + { + const string testCode = @" +public abstract record BaseRecord(string Text); + +public record Derived1(string Text) + : BaseRecord( + Text {|#0:)|} +{ +} + +public record Derived2(string Text) + : BaseRecord( + Text {|#1:)|} ; +"; + + const string fixedCode = @" +public abstract record BaseRecord(string Text); + +public record Derived1(string Text) + : BaseRecord( + Text) +{ +} + +public record Derived2(string Text) + : BaseRecord( + Text); +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotPreceded).WithLocation(1), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/documentation/SA1202.md b/documentation/SA1202.md index 81f015f1d..1c0813663 100644 --- a/documentation/SA1202.md +++ b/documentation/SA1202.md @@ -46,6 +46,11 @@ Default interface members (including members with implementations and static int ordering as class members. Within an interface, `public` members should appear before `internal`, `protected internal`, `protected`, `private protected`, and `private` members. +### Records and primary constructors + +Records (including positional records) and record structs follow the same ordering rules as classes and structs. Primary +constructor parameters that become properties do not change the expected access ordering for the remaining members. + ## How to fix violations To fix an instance of this violation, order the elements in the file in the order described above. diff --git a/documentation/SA1313.md b/documentation/SA1313.md index ff11fb3c5..db95fa307 100644 --- a/documentation/SA1313.md +++ b/documentation/SA1313.md @@ -25,9 +25,18 @@ The name of a parameter in C# does not begin with a lower-case letter. A violation of this rule occurs when the name of a parameter does not begin with a lower-case letter. +### Discards + An exception to this rule is made for lambda parameters named `_` and `__`. These parameters are often used to designate a placeholder parameter which is not actually used in the body of the lambda expression. +### Records and primary constructors + +Positional record parameters become public properties, so PascalCase names are acceptable for those parameters even +though they appear in the primary constructor signature. + +### Interop code + If the parameter name is intended to match the name of an item associated with Win32 or COM, and thus needs to begin with an upper-case letter, place the parameter within a special `NativeMethods` class. A `NativeMethods` class is any class which contains a name ending in `NativeMethods`, and is intended as a placeholder for Win32 or COM wrappers. diff --git a/documentation/SA1600.md b/documentation/SA1600.md index e7d96a7f1..bb7452900 100644 --- a/documentation/SA1600.md +++ b/documentation/SA1600.md @@ -25,6 +25,10 @@ C# syntax provides a mechanism for inserting documentation for classes and eleme A violation of this rule occurs if an element is completely missing a documentation header, or if the header is empty. In C# the following types of elements can have documentation headers: classes, constructors, delegates, enums, events, finalizers, indexers, interfaces, methods, properties, records, and structs. +### Records and primary constructors + +Record classes and record structs follow the same expectations as classes and structs. Positional parameters in a record declaration become properties; when documenting a public record, include `` tags for those parameters in addition to the `` text for the record itself. + ### Default interface members Interface members are implicitly `public` unless otherwise specified. Default interface members (methods, properties, indexers, events, etc. that include implementations) follow these rules: diff --git a/documentation/SA1642.md b/documentation/SA1642.md index f12da3238..cdacbb95f 100644 --- a/documentation/SA1642.md +++ b/documentation/SA1642.md @@ -52,6 +52,8 @@ 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'. + If the class contains generic parameters, these can be annotated within the `cref` link using either of the following two formats: