Skip to content

Commit 50fae1c

Browse files
authored
Merge pull request #4061 from sharwell/target-typed-new
Update rules for target-typed new in C# 9
2 parents ff14d11 + 34151d6 commit 50fae1c

12 files changed

+243
-0
lines changed

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,48 @@ public async Task TestPrimaryConstructorBaseListWithoutParametersAsync(string ty
130130
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
131131
}
132132

133+
[Fact]
134+
[WorkItem(3972, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3972")]
135+
public async Task TestTargetTypedNewOpeningParenthesisOnNextLineAsync()
136+
{
137+
var testCode = @"
138+
class TestClass
139+
{
140+
public TestClass(int value)
141+
{
142+
}
143+
}
144+
145+
class Test
146+
{
147+
void M()
148+
{
149+
TestClass value = new
150+
{|#0:(|}1);
151+
}
152+
}";
153+
154+
var fixedCode = @"
155+
class TestClass
156+
{
157+
public TestClass(int value)
158+
{
159+
}
160+
}
161+
162+
class Test
163+
{
164+
void M()
165+
{
166+
TestClass value = new(
167+
1);
168+
}
169+
}";
170+
171+
var expected = Diagnostic().WithLocation(0);
172+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
173+
}
174+
133175
protected virtual DiagnosticResult[] GetExpectedResultTestPrimaryConstructor()
134176
{
135177
return new[]

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,47 @@ public async Task TestPrimaryConstructorBaseListWithoutArgumentsAsync(string typ
9595
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
9696
}
9797

98+
[Fact]
99+
[WorkItem(3972, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3972")]
100+
public async Task TestTargetTypedNewClosingParenthesisOnNextLineAsync()
101+
{
102+
var testCode = @"
103+
class TestClass
104+
{
105+
public TestClass(int value)
106+
{
107+
}
108+
}
109+
110+
class Test
111+
{
112+
void M()
113+
{
114+
TestClass value = new(1
115+
{|#0:)|};
116+
}
117+
}";
118+
119+
var fixedCode = @"
120+
class TestClass
121+
{
122+
public TestClass(int value)
123+
{
124+
}
125+
}
126+
127+
class Test
128+
{
129+
void M()
130+
{
131+
TestClass value = new(1);
132+
}
133+
}";
134+
135+
var expected = Diagnostic().WithLocation(0);
136+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
137+
}
138+
98139
protected virtual DiagnosticResult[] GetExpectedResultTestPrimaryConstructorWithParameter()
99140
{
100141
return new[]

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,51 @@
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.SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis,
14+
StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>;
715

816
public partial class SA1112CSharp9UnitTests : SA1112CSharp8UnitTests
917
{
18+
[Fact]
19+
[WorkItem(3972, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3972")]
20+
public async Task TestTargetTypedNewClosingParenthesisAsync()
21+
{
22+
var testCode = @"
23+
struct TestStruct
24+
{
25+
}
26+
27+
class Test
28+
{
29+
void M()
30+
{
31+
TestStruct value = new(
32+
{|#0:)|};
33+
}
34+
}";
35+
36+
var fixedCode = @"
37+
struct TestStruct
38+
{
39+
}
40+
41+
class Test
42+
{
43+
void M()
44+
{
45+
TestStruct value = new();
46+
}
47+
}";
48+
49+
var expected = Diagnostic().WithLocation(0);
50+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
51+
}
1052
}
1153
}

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,58 @@
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.SA1113CommaMustBeOnSameLineAsPreviousParameter,
14+
StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>;
715

816
public partial class SA1113CSharp9UnitTests : SA1113CSharp8UnitTests
917
{
18+
[Fact]
19+
[WorkItem(3972, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3972")]
20+
public async Task TestTargetTypedNewCommaPlacementAsync()
21+
{
22+
var testCode = @"
23+
class TestClass
24+
{
25+
public TestClass(int first, int second)
26+
{
27+
}
28+
}
29+
30+
class Test
31+
{
32+
void M()
33+
{
34+
TestClass value = new(1
35+
{|#0:,|} 2);
36+
}
37+
}";
38+
39+
var fixedCode = @"
40+
class TestClass
41+
{
42+
public TestClass(int first, int second)
43+
{
44+
}
45+
}
46+
47+
class Test
48+
{
49+
void M()
50+
{
51+
TestClass value = new(1,
52+
2);
53+
}
54+
}";
55+
56+
var expected = Diagnostic().WithLocation(0);
57+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
58+
}
1059
}
1160
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ internal class SA1110OpeningParenthesisMustBeOnDeclarationLine : DiagnosticAnaly
6060
private static readonly Action<SyntaxNodeAnalysisContext> ConstructorDeclarationAction = HandleConstructorDeclaration;
6161
private static readonly Action<SyntaxNodeAnalysisContext> InvocationExpressionAction = HandleInvocationExpression;
6262
private static readonly Action<SyntaxNodeAnalysisContext> ObjectCreationExpressionAction = HandleObjectCreationExpression;
63+
private static readonly Action<SyntaxNodeAnalysisContext> ImplicitObjectCreationExpressionAction = HandleImplicitObjectCreationExpression;
6364
private static readonly Action<SyntaxNodeAnalysisContext> IndexerDeclarationAction = HandleIndexerDeclaration;
6465
private static readonly Action<SyntaxNodeAnalysisContext> ElementAccessExpressionAction = HandleElementAccessExpression;
6566
private static readonly Action<SyntaxNodeAnalysisContext> AttributeAction = HandleAttribute;
@@ -86,6 +87,7 @@ public override void Initialize(AnalysisContext context)
8687
context.RegisterSyntaxNodeAction(ConstructorDeclarationAction, SyntaxKind.ConstructorDeclaration);
8788
context.RegisterSyntaxNodeAction(InvocationExpressionAction, SyntaxKind.InvocationExpression);
8889
context.RegisterSyntaxNodeAction(ObjectCreationExpressionAction, SyntaxKind.ObjectCreationExpression);
90+
context.RegisterSyntaxNodeAction(ImplicitObjectCreationExpressionAction, SyntaxKindEx.ImplicitObjectCreationExpression);
8991
context.RegisterSyntaxNodeAction(IndexerDeclarationAction, SyntaxKind.IndexerDeclaration);
9092
context.RegisterSyntaxNodeAction(ElementAccessExpressionAction, SyntaxKind.ElementAccessExpression);
9193
context.RegisterSyntaxNodeAction(AttributeAction, SyntaxKind.Attribute);
@@ -258,6 +260,24 @@ private static void HandleObjectCreationExpression(SyntaxNodeAnalysisContext con
258260
}
259261
}
260262

263+
private static void HandleImplicitObjectCreationExpression(SyntaxNodeAnalysisContext context)
264+
{
265+
var implicitObjectCreation = (ImplicitObjectCreationExpressionSyntaxWrapper)context.Node;
266+
if (implicitObjectCreation.NewKeyword.IsMissing)
267+
{
268+
return;
269+
}
270+
271+
var argumentList = implicitObjectCreation.ArgumentList;
272+
273+
if (argumentList != null
274+
&& !argumentList.OpenParenToken.IsMissing)
275+
{
276+
bool preserveLayout = argumentList.Arguments.Any();
277+
CheckIfLocationOfPreviousTokenAndOpenTokenAreTheSame(context, argumentList.OpenParenToken, preserveLayout);
278+
}
279+
}
280+
261281
private static SyntaxToken? GetIdentifier(ObjectCreationExpressionSyntax objectCreationExpressionSyntax)
262282
{
263283
switch (objectCreationExpressionSyntax.Type.Kind())

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ internal class SA1111ClosingParenthesisMustBeOnLineOfLastParameter : DiagnosticA
6666
private static readonly Action<SyntaxNodeAnalysisContext> LocalFunctionStatementAction = HandleLocalFunctionStatement;
6767
private static readonly Action<SyntaxNodeAnalysisContext> InvocationExpressionAction = HandleInvocationExpression;
6868
private static readonly Action<SyntaxNodeAnalysisContext> ObjectCreationExpressionAction = HandleObjectCreationExpression;
69+
private static readonly Action<SyntaxNodeAnalysisContext> ImplicitObjectCreationExpressionAction = HandleImplicitObjectCreationExpression;
6970
private static readonly Action<SyntaxNodeAnalysisContext> IndexerDeclarationAction = HandleIndexerDeclaration;
7071
private static readonly Action<SyntaxNodeAnalysisContext> ElementAccessExpressionAction = HandleElementAccessExpression;
7172
private static readonly Action<SyntaxNodeAnalysisContext> DelegateDeclarationAction = HandleDelegateDeclaration;
@@ -90,6 +91,7 @@ public override void Initialize(AnalysisContext context)
9091
context.RegisterSyntaxNodeAction(LocalFunctionStatementAction, SyntaxKindEx.LocalFunctionStatement);
9192
context.RegisterSyntaxNodeAction(InvocationExpressionAction, SyntaxKind.InvocationExpression);
9293
context.RegisterSyntaxNodeAction(ObjectCreationExpressionAction, SyntaxKind.ObjectCreationExpression);
94+
context.RegisterSyntaxNodeAction(ImplicitObjectCreationExpressionAction, SyntaxKindEx.ImplicitObjectCreationExpression);
9395
context.RegisterSyntaxNodeAction(IndexerDeclarationAction, SyntaxKind.IndexerDeclaration);
9496
context.RegisterSyntaxNodeAction(ElementAccessExpressionAction, SyntaxKind.ElementAccessExpression);
9597
context.RegisterSyntaxNodeAction(DelegateDeclarationAction, SyntaxKind.DelegateDeclaration);
@@ -190,6 +192,12 @@ private static void HandleObjectCreationExpression(SyntaxNodeAnalysisContext con
190192
CheckArgumentList(context, objectCreation.ArgumentList);
191193
}
192194

195+
private static void HandleImplicitObjectCreationExpression(SyntaxNodeAnalysisContext context)
196+
{
197+
var implicitObjectCreation = (ImplicitObjectCreationExpressionSyntaxWrapper)context.Node;
198+
CheckArgumentList(context, implicitObjectCreation.ArgumentList);
199+
}
200+
193201
private static void HandleIndexerDeclaration(SyntaxNodeAnalysisContext context)
194202
{
195203
var indexerDeclaration = (IndexerDeclarationSyntax)context.Node;

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ internal class SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis : Diagno
5151
private static readonly Action<SyntaxNodeAnalysisContext> ConstructorDeclarationAction = HandleConstructorDeclaration;
5252
private static readonly Action<SyntaxNodeAnalysisContext> InvocationExpressionAction = HandleInvocationExpression;
5353
private static readonly Action<SyntaxNodeAnalysisContext> ObjectCreationExpressionAction = HandleObjectCreationExpression;
54+
private static readonly Action<SyntaxNodeAnalysisContext> ImplicitObjectCreationExpressionAction = HandleImplicitObjectCreationExpression;
5455

5556
/// <inheritdoc/>
5657
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
@@ -67,6 +68,7 @@ public override void Initialize(AnalysisContext context)
6768
context.RegisterSyntaxNodeAction(ConstructorDeclarationAction, SyntaxKind.ConstructorDeclaration);
6869
context.RegisterSyntaxNodeAction(InvocationExpressionAction, SyntaxKind.InvocationExpression);
6970
context.RegisterSyntaxNodeAction(ObjectCreationExpressionAction, SyntaxKind.ObjectCreationExpression);
71+
context.RegisterSyntaxNodeAction(ImplicitObjectCreationExpressionAction, SyntaxKindEx.ImplicitObjectCreationExpression);
7072
}
7173

7274
private static void HandleObjectCreationExpression(SyntaxNodeAnalysisContext context)
@@ -89,6 +91,28 @@ private static void HandleObjectCreationExpression(SyntaxNodeAnalysisContext con
8991
}
9092
}
9193

94+
private static void HandleImplicitObjectCreationExpression(SyntaxNodeAnalysisContext context)
95+
{
96+
var implicitObjectCreation = (ImplicitObjectCreationExpressionSyntaxWrapper)context.Node;
97+
var argumentList = implicitObjectCreation.ArgumentList;
98+
99+
if (argumentList == null ||
100+
argumentList.IsMissing ||
101+
argumentList.Arguments.Count > 0)
102+
{
103+
return;
104+
}
105+
106+
if (!argumentList.OpenParenToken.IsMissing &&
107+
!argumentList.CloseParenToken.IsMissing)
108+
{
109+
CheckIfLocationOfOpenAndCloseTokensAreTheSame(
110+
context,
111+
argumentList.OpenParenToken,
112+
argumentList.CloseParenToken);
113+
}
114+
}
115+
92116
private static void HandleInvocationExpression(SyntaxNodeAnalysisContext context)
93117
{
94118
var invocationExpression = (InvocationExpressionSyntax)context.Node;

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1113CommaMustBeOnSameLineAsPreviousParameter.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ internal class SA1113CommaMustBeOnSameLineAsPreviousParameter : DiagnosticAnalyz
6363
private static readonly Action<SyntaxNodeAnalysisContext> LocalFunctionStatementAction = HandleLocalFunctionStatement;
6464
private static readonly Action<SyntaxNodeAnalysisContext> InvocationExpressionAction = HandleInvocationExpression;
6565
private static readonly Action<SyntaxNodeAnalysisContext> ObjectCreationExpressionAction = HandleObjectCreationExpression;
66+
private static readonly Action<SyntaxNodeAnalysisContext> ImplicitObjectCreationExpressionAction = HandleImplicitObjectCreationExpression;
6667
private static readonly Action<SyntaxNodeAnalysisContext> IndexerDeclarationAction = HandleIndexerDeclaration;
6768
private static readonly Action<SyntaxNodeAnalysisContext> ElementAccessExpressionAction = HandleElementAccessExpression;
6869
private static readonly Action<SyntaxNodeAnalysisContext> AnonymousMethodExpressionAction = HandleAnonymousMethodExpression;
@@ -87,6 +88,7 @@ public override void Initialize(AnalysisContext context)
8788
context.RegisterSyntaxNodeAction(LocalFunctionStatementAction, SyntaxKindEx.LocalFunctionStatement);
8889
context.RegisterSyntaxNodeAction(InvocationExpressionAction, SyntaxKind.InvocationExpression);
8990
context.RegisterSyntaxNodeAction(ObjectCreationExpressionAction, SyntaxKind.ObjectCreationExpression);
91+
context.RegisterSyntaxNodeAction(ImplicitObjectCreationExpressionAction, SyntaxKindEx.ImplicitObjectCreationExpression);
9092
context.RegisterSyntaxNodeAction(IndexerDeclarationAction, SyntaxKind.IndexerDeclaration);
9193
context.RegisterSyntaxNodeAction(ElementAccessExpressionAction, SyntaxKind.ElementAccessExpression);
9294
context.RegisterSyntaxNodeAction(AnonymousMethodExpressionAction, SyntaxKind.AnonymousMethodExpression);
@@ -187,6 +189,12 @@ private static void HandleObjectCreationExpression(SyntaxNodeAnalysisContext con
187189
HandleBaseArgumentListSyntax(context, objectCreationExpression.ArgumentList);
188190
}
189191

192+
private static void HandleImplicitObjectCreationExpression(SyntaxNodeAnalysisContext context)
193+
{
194+
var implicitObjectCreation = (ImplicitObjectCreationExpressionSyntaxWrapper)context.Node;
195+
HandleBaseArgumentListSyntax(context, implicitObjectCreation.ArgumentList);
196+
}
197+
190198
private static void HandleInvocationExpression(SyntaxNodeAnalysisContext context)
191199
{
192200
var invocationEpression = (InvocationExpressionSyntax)context.Node;

documentation/SA1110.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ not placed on the same line as the method or indexer name.
2525
A violation of this rule occurs when the opening bracket of a method or indexer call or declaration is not placed on the
2626
same line as the method or indexer. The following examples show correct placement of the opening bracket:
2727

28+
This rule also applies to constructor invocations and object creation expressions, including target-typed `new` expressions
29+
introduced in C# 9. In all of these cases, the `(` should appear on the same line as the `new` keyword.
30+
2831
```csharp
2932
public string JoinName(string first, string last)
3033
{

documentation/SA1111.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ The closing parenthesis or bracket in a call to a C# method or indexer, or the d
2323

2424
A violation of this rule occurs when the closing bracket of a method or indexer call or declaration is not placed on the same line as the last parameter. The following examples show correct placement of the bracket:
2525

26+
Constructor invocations and object creation expressions, including target-typed `new`, are covered by this rule as well. The closing `)` for these expressions should appear on the same line as the final argument when one exists.
27+
2628
```csharp
2729
public string JoinName(string first, string last)
2830
{

0 commit comments

Comments
 (0)