Skip to content

Commit f1f6d83

Browse files
Update SA1110 to also check the parameter list in primary constructors
#3784
1 parent f5f4ca9 commit f1f6d83

File tree

5 files changed

+121
-0
lines changed

5 files changed

+121
-0
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/ReadabilityRules/SA1110CSharp11UnitTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,20 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.ReadabilityRules
55
{
6+
using Microsoft.CodeAnalysis.Testing;
67
using StyleCop.Analyzers.Test.CSharp10.ReadabilityRules;
8+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
9+
StyleCop.Analyzers.ReadabilityRules.SA1110OpeningParenthesisMustBeOnDeclarationLine,
10+
StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>;
711

812
public partial class SA1110CSharp11UnitTests : SA1110CSharp10UnitTests
913
{
14+
protected override DiagnosticResult[] GetExpectedResultTestPrimaryConstructor()
15+
{
16+
return new[]
17+
{
18+
Diagnostic().WithLocation(0),
19+
};
20+
}
1021
}
1122
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1110CSharp9UnitTests.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,66 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp9.ReadabilityRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.Testing;
69
using StyleCop.Analyzers.Test.CSharp8.ReadabilityRules;
10+
using StyleCop.Analyzers.Test.Helpers;
11+
using Xunit;
12+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
13+
StyleCop.Analyzers.ReadabilityRules.SA1110OpeningParenthesisMustBeOnDeclarationLine,
14+
StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>;
715

816
public partial class SA1110CSharp9UnitTests : SA1110CSharp8UnitTests
917
{
18+
[Theory]
19+
[MemberData(nameof(CommonMemberData.TypeKeywordsWhichSupportPrimaryConstructors), MemberType = typeof(CommonMemberData))]
20+
[WorkItem(3784, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3784")]
21+
public async Task TestPrimaryConstructorWithoutParametersAsync(string typeKeyword)
22+
{
23+
var testCode = $@"
24+
{typeKeyword} Foo
25+
{{|#0:(|}})
26+
{{
27+
}}";
28+
29+
var fixedCode = $@"
30+
{typeKeyword} Foo()
31+
{{
32+
}}";
33+
34+
var expected = this.GetExpectedResultTestPrimaryConstructor();
35+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
36+
}
37+
38+
[Theory]
39+
[MemberData(nameof(CommonMemberData.TypeKeywordsWhichSupportPrimaryConstructors), MemberType = typeof(CommonMemberData))]
40+
[WorkItem(3784, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3784")]
41+
public async Task TestPrimaryConstructorWithParametersAsync(string typeKeyword)
42+
{
43+
var testCode = $@"
44+
{typeKeyword} Foo
45+
{{|#0:(|}}int x)
46+
{{
47+
}}";
48+
49+
var fixedCode = $@"
50+
{typeKeyword} Foo(
51+
int x)
52+
{{
53+
}}";
54+
55+
var expected = this.GetExpectedResultTestPrimaryConstructor();
56+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
57+
}
58+
59+
protected virtual DiagnosticResult[] GetExpectedResultTestPrimaryConstructor()
60+
{
61+
return new[]
62+
{
63+
Diagnostic().WithLocation(0),
64+
Diagnostic().WithLocation(0),
65+
};
66+
}
1067
}
1168
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,28 @@ public static IEnumerable<object[]> GenericTypeDeclarationKeywords
114114
.Concat(new[] { new[] { "delegate" } });
115115
}
116116
}
117+
118+
public static IEnumerable<object[]> TypeKeywordsWhichSupportPrimaryConstructors
119+
{
120+
get
121+
{
122+
if (LightupHelpers.SupportsCSharp9)
123+
{
124+
yield return new[] { "record" };
125+
}
126+
127+
if (LightupHelpers.SupportsCSharp10)
128+
{
129+
yield return new[] { "record class" };
130+
yield return new[] { "record struct" };
131+
}
132+
133+
if (LightupHelpers.SupportsCSharp12)
134+
{
135+
yield return new[] { "class" };
136+
yield return new[] { "struct" };
137+
}
138+
}
139+
}
117140
}
118141
}

StyleCop.Analyzers/StyleCop.Analyzers/Lightup/TypeDeclarationSyntaxExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ static TypeDeclarationSyntaxExtensions()
1919

2020
public static ParameterListSyntax? ParameterList(this TypeDeclarationSyntax syntax)
2121
{
22+
if (!LightupHelpers.SupportsCSharp12)
23+
{
24+
// Prior to C# 12, the ParameterList property in RecordDeclarationSyntax did not override a base method.
25+
switch (syntax.Kind())
26+
{
27+
case SyntaxKindEx.RecordDeclaration:
28+
case SyntaxKindEx.RecordStructDeclaration:
29+
return ((RecordDeclarationSyntaxWrapper)syntax).ParameterList;
30+
31+
default:
32+
return null;
33+
}
34+
}
35+
2236
return ParameterListAccessor(syntax);
2337
}
2438

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1110OpeningParenthesisMustBeOnDeclarationLine.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ internal class SA1110OpeningParenthesisMustBeOnDeclarationLine : DiagnosticAnaly
5353
private static readonly DiagnosticDescriptor Descriptor =
5454
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
5555

56+
private static readonly Action<SyntaxNodeAnalysisContext> TypeDeclarationAction = HandleTypeDeclaration;
5657
private static readonly Action<SyntaxNodeAnalysisContext> MethodDeclarationAction = HandleMethodDeclaration;
5758
private static readonly Action<SyntaxNodeAnalysisContext> LocalFunctionStatementAction = HandleLocalFunctionStatement;
5859
private static readonly Action<SyntaxNodeAnalysisContext> ConstructorDeclarationAction = HandleConstructorDeclaration;
@@ -77,6 +78,7 @@ public override void Initialize(AnalysisContext context)
7778
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
7879
context.EnableConcurrentExecution();
7980

81+
context.RegisterSyntaxNodeAction(TypeDeclarationAction, SyntaxKinds.TypeDeclaration);
8082
context.RegisterSyntaxNodeAction(MethodDeclarationAction, SyntaxKind.MethodDeclaration);
8183
context.RegisterSyntaxNodeAction(LocalFunctionStatementAction, SyntaxKindEx.LocalFunctionStatement);
8284
context.RegisterSyntaxNodeAction(ConstructorDeclarationAction, SyntaxKind.ConstructorDeclaration);
@@ -329,6 +331,20 @@ private static void HandleLocalFunctionStatement(SyntaxNodeAnalysisContext conte
329331
}
330332
}
331333

334+
private static void HandleTypeDeclaration(SyntaxNodeAnalysisContext context)
335+
{
336+
var typeDeclaration = (TypeDeclarationSyntax)context.Node;
337+
var parameterList = typeDeclaration.ParameterList();
338+
339+
if (parameterList != null
340+
&& !parameterList.OpenParenToken.IsMissing
341+
&& !typeDeclaration.Identifier.IsMissing)
342+
{
343+
bool preserveLayout = parameterList.Parameters.Any();
344+
CheckIfLocationOfPreviousTokenAndOpenTokenAreTheSame(context, parameterList.OpenParenToken, preserveLayout);
345+
}
346+
}
347+
332348
private static void CheckIfLocationOfPreviousTokenAndOpenTokenAreTheSame(SyntaxNodeAnalysisContext context, SyntaxToken openToken, bool preserveLayout)
333349
{
334350
var previousToken = openToken.GetPreviousToken();

0 commit comments

Comments
 (0)