Skip to content

Commit 834d939

Browse files
authored
Merge pull request #3800 from bjornhellander/feature/sa1110-primary-ctor-3784
Update SA1110 to also check the parameter list in primary constructors
2 parents cedfcf9 + 67e8fd5 commit 834d939

File tree

6 files changed

+238
-0
lines changed

6 files changed

+238
-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: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,151 @@
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+
[Theory]
60+
[MemberData(nameof(CommonMemberData.ReferenceTypeKeywordsWhichSupportPrimaryConstructors), MemberType = typeof(CommonMemberData))]
61+
[WorkItem(3784, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3784")]
62+
public async Task TestPrimaryConstructorBaseListWithParametersOnSameLineAsync(string typeKeyword)
63+
{
64+
var testCode = $@"
65+
{typeKeyword} Foo(int x)
66+
{{
67+
}}
68+
69+
{typeKeyword} Bar(int x) : Foo(x)
70+
{{
71+
}}";
72+
73+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
74+
}
75+
76+
[Theory]
77+
[MemberData(nameof(CommonMemberData.ReferenceTypeKeywordsWhichSupportPrimaryConstructors), MemberType = typeof(CommonMemberData))]
78+
[WorkItem(3784, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3784")]
79+
public async Task TestPrimaryConstructorBaseListWithParametersAsync(string typeKeyword)
80+
{
81+
var testCode = $@"
82+
{typeKeyword} Foo(int x)
83+
{{
84+
}}
85+
86+
{typeKeyword} Bar(int x) : Foo
87+
{{|#0:(|}}x)
88+
{{
89+
}}";
90+
91+
var fixedCode = $@"
92+
{typeKeyword} Foo(int x)
93+
{{
94+
}}
95+
96+
{typeKeyword} Bar(int x) : Foo(
97+
x)
98+
{{
99+
}}";
100+
101+
var expected = this.GetExpectedResultTestPrimaryConstructorBaseList();
102+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
103+
}
104+
105+
[Theory]
106+
[MemberData(nameof(CommonMemberData.ReferenceTypeKeywordsWhichSupportPrimaryConstructors), MemberType = typeof(CommonMemberData))]
107+
[WorkItem(3784, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3784")]
108+
public async Task TestPrimaryConstructorBaseListWithoutParametersAsync(string typeKeyword)
109+
{
110+
var testCode = $@"
111+
{typeKeyword} Foo()
112+
{{
113+
}}
114+
115+
{typeKeyword} Bar(int x) : Foo
116+
{{|#0:(|}})
117+
{{
118+
}}";
119+
120+
var fixedCode = $@"
121+
{typeKeyword} Foo()
122+
{{
123+
}}
124+
125+
{typeKeyword} Bar(int x) : Foo()
126+
{{
127+
}}";
128+
129+
var expected = this.GetExpectedResultTestPrimaryConstructorBaseList();
130+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
131+
}
132+
133+
protected virtual DiagnosticResult[] GetExpectedResultTestPrimaryConstructor()
134+
{
135+
return new[]
136+
{
137+
// Diagnostic issued twice because of https://github.com/dotnet/roslyn/issues/53136
138+
Diagnostic().WithLocation(0),
139+
Diagnostic().WithLocation(0),
140+
};
141+
}
142+
143+
protected virtual DiagnosticResult[] GetExpectedResultTestPrimaryConstructorBaseList()
144+
{
145+
return new[]
146+
{
147+
// Diagnostic issued twice because of https://github.com/dotnet/roslyn/issues/70488
148+
Diagnostic().WithLocation(0),
149+
Diagnostic().WithLocation(0),
150+
};
151+
}
10152
}
11153
}

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,36 @@ 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+
}
140+
141+
public static IEnumerable<object[]> ReferenceTypeKeywordsWhichSupportPrimaryConstructors
142+
{
143+
get
144+
{
145+
return TypeKeywordsWhichSupportPrimaryConstructors.Where(x => !((string)x[0]).Contains("struct"));
146+
}
147+
}
117148
}
118149
}

StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ internal static class SyntaxKindEx
6767
public const SyntaxKind WithExpression = (SyntaxKind)9061;
6868
public const SyntaxKind WithInitializerExpression = (SyntaxKind)9062;
6969
public const SyntaxKind RecordDeclaration = (SyntaxKind)9063;
70+
public const SyntaxKind PrimaryConstructorBaseType = (SyntaxKind)9065;
7071
public const SyntaxKind FunctionPointerUnmanagedCallingConventionList = (SyntaxKind)9066;
7172
public const SyntaxKind RecordStructDeclaration = (SyntaxKind)9068;
7273
public const SyntaxKind CollectionExpression = (SyntaxKind)9076;

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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ 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;
57+
private static readonly Action<SyntaxNodeAnalysisContext> PrimaryConstructorBaseTypeAction = HandlePrimaryConstructorBaseType;
5658
private static readonly Action<SyntaxNodeAnalysisContext> MethodDeclarationAction = HandleMethodDeclaration;
5759
private static readonly Action<SyntaxNodeAnalysisContext> LocalFunctionStatementAction = HandleLocalFunctionStatement;
5860
private static readonly Action<SyntaxNodeAnalysisContext> ConstructorDeclarationAction = HandleConstructorDeclaration;
@@ -77,6 +79,8 @@ public override void Initialize(AnalysisContext context)
7779
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
7880
context.EnableConcurrentExecution();
7981

82+
context.RegisterSyntaxNodeAction(TypeDeclarationAction, SyntaxKinds.TypeDeclaration);
83+
context.RegisterSyntaxNodeAction(PrimaryConstructorBaseTypeAction, SyntaxKindEx.PrimaryConstructorBaseType);
8084
context.RegisterSyntaxNodeAction(MethodDeclarationAction, SyntaxKind.MethodDeclaration);
8185
context.RegisterSyntaxNodeAction(LocalFunctionStatementAction, SyntaxKindEx.LocalFunctionStatement);
8286
context.RegisterSyntaxNodeAction(ConstructorDeclarationAction, SyntaxKind.ConstructorDeclaration);
@@ -329,6 +333,41 @@ private static void HandleLocalFunctionStatement(SyntaxNodeAnalysisContext conte
329333
}
330334
}
331335

336+
private static void HandleTypeDeclaration(SyntaxNodeAnalysisContext context)
337+
{
338+
var typeDeclaration = (TypeDeclarationSyntax)context.Node;
339+
var parameterList = typeDeclaration.ParameterList();
340+
341+
if (parameterList != null
342+
&& !parameterList.OpenParenToken.IsMissing
343+
&& !typeDeclaration.Identifier.IsMissing)
344+
{
345+
bool preserveLayout = parameterList.Parameters.Any();
346+
CheckIfLocationOfPreviousTokenAndOpenTokenAreTheSame(context, parameterList.OpenParenToken, preserveLayout);
347+
}
348+
}
349+
350+
private static void HandlePrimaryConstructorBaseType(SyntaxNodeAnalysisContext context)
351+
{
352+
var primaryConstructorBaseType = (PrimaryConstructorBaseTypeSyntaxWrapper)context.Node;
353+
354+
var identifierName = ((BaseTypeSyntax)primaryConstructorBaseType).ChildNodes()
355+
.OfType<IdentifierNameSyntax>()
356+
.FirstOrDefault();
357+
if (identifierName == null || identifierName.Identifier.IsMissing)
358+
{
359+
return;
360+
}
361+
362+
var argumentListSyntax = primaryConstructorBaseType.ArgumentList;
363+
364+
if (argumentListSyntax != null && !argumentListSyntax.OpenParenToken.IsMissing)
365+
{
366+
bool preserveLayout = argumentListSyntax.Arguments.Any();
367+
CheckIfLocationOfPreviousTokenAndOpenTokenAreTheSame(context, argumentListSyntax.OpenParenToken, preserveLayout);
368+
}
369+
}
370+
332371
private static void CheckIfLocationOfPreviousTokenAndOpenTokenAreTheSame(SyntaxNodeAnalysisContext context, SyntaxToken openToken, bool preserveLayout)
333372
{
334373
var previousToken = openToken.GetPreviousToken();

0 commit comments

Comments
 (0)