Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,63 +132,23 @@ public class TestClass1 { }
}

/// <summary>
/// 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.
/// </summary>
/// <param name="keyword">The keyword used to declare the type.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[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);
}

/// <summary>
/// Verifies that the analyzer will properly handle non-static elements before static in a struct.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[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 =
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SyntaxKind> 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<SyntaxNodeAnalysisContext, StyleCopSettings> CompilationUnitAction = HandleCompilationUnit;
private static readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> BaseNamespaceDeclarationAction = HandleBaseNamespaceDeclaration;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
Expand All @@ -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<SyntaxKind> TypeDeclarationKinds =
ImmutableArray.Create(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration);
ImmutableArray.Create(
SyntaxKind.ClassDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKindEx.RecordDeclaration,
SyntaxKindEx.RecordStructDeclaration);

private static readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> TypeDeclarationAction = HandleTypeDeclaration;

Expand Down
4 changes: 2 additions & 2 deletions documentation/SA1201.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
14 changes: 13 additions & 1 deletion documentation/SA1642.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// Initializes a new instance of the <see cref="Customer"/> struct.
/// </summary>
public Customer()
{
}
}
```

If the class contains generic parameters, these can be annotated within the `cref` link using either of the following
two formats:
Expand Down
Loading