Skip to content

Commit af356f9

Browse files
committed
Avoid reporting SA1141 (Use tuple syntax) in expression trees
Fixes #3305
1 parent 23db6c0 commit af356f9

File tree

5 files changed

+212
-36
lines changed

5 files changed

+212
-36
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1141CodeFixProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
7777
break;
7878
}
7979

80-
var newSyntaxRoot = syntaxRoot.ReplaceNode(node, newNode).WithAdditionalAnnotations(Simplifier.Annotation);
80+
var newSyntaxRoot = syntaxRoot.ReplaceNode(node, newNode.WithAdditionalAnnotations(Simplifier.Annotation));
8181
return document.WithSyntaxRoot(newSyntaxRoot.WithoutFormatting());
8282
}
8383

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1141CSharp7UnitTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,49 @@ public void TestMethod()
182182
await VerifyCSharpFixAsync(testCode, expectedDiagnostics, fixedCode, CancellationToken.None).ConfigureAwait(false);
183183
}
184184

185+
/// <summary>
186+
/// Validates that the usage of <see cref="System.ValueTuple"/> within LINQ expression trees will produce no
187+
/// diagnostics.
188+
/// </summary>
189+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
190+
[Fact]
191+
[WorkItem(3305, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3305")]
192+
public async Task ValidateValueTupleUsageInExpressionTreeAsync()
193+
{
194+
var testCode = @"using System;
195+
using System.Linq.Expressions;
196+
197+
public class TestClass
198+
{
199+
Expression<Func<(int, int)>> expression1 = () => ValueTuple.Create(10, 20);
200+
Expression<Func<(int, int)>> expression2 = () => new ValueTuple<int, int>(10, 20);
201+
Expression<Func<((int, int), int)>> expression3 = () => new ValueTuple<ValueTuple<int, int>, int>(ValueTuple.Create(10, 10), 20);
202+
Expression<Func<(int, int)>> expression4 = () => new System.ValueTuple<int, int>(10, 20);
203+
Expression<Func<(int, int), (int, int)>> expression5 = arg => new System.ValueTuple<int, int>(arg.Item1, arg.Item2);
204+
Expression<Func<(int, int), (int, int)>> expression6 = (arg) => new System.ValueTuple<int, int>(arg.Item1, arg.Item2);
205+
206+
Expression<Func<[|ValueTuple<int, int>|], [|ValueTuple<int, int>|]>> expression7 = ([|ValueTuple<int, int>|] arg) => ValueTuple.Create(arg.Item1, arg.Item2);
207+
}
208+
";
209+
var fixedCode = @"using System;
210+
using System.Linq.Expressions;
211+
212+
public class TestClass
213+
{
214+
Expression<Func<(int, int)>> expression1 = () => ValueTuple.Create(10, 20);
215+
Expression<Func<(int, int)>> expression2 = () => new ValueTuple<int, int>(10, 20);
216+
Expression<Func<((int, int), int)>> expression3 = () => new ValueTuple<ValueTuple<int, int>, int>(ValueTuple.Create(10, 10), 20);
217+
Expression<Func<(int, int)>> expression4 = () => new System.ValueTuple<int, int>(10, 20);
218+
Expression<Func<(int, int), (int, int)>> expression5 = arg => new System.ValueTuple<int, int>(arg.Item1, arg.Item2);
219+
Expression<Func<(int, int), (int, int)>> expression6 = (arg) => new System.ValueTuple<int, int>(arg.Item1, arg.Item2);
220+
221+
Expression<Func<(int, int), (int, int)>> expression7 = ((int, int) arg) => ValueTuple.Create(arg.Item1, arg.Item2);
222+
}
223+
";
224+
225+
await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(false);
226+
}
227+
185228
/// <summary>
186229
/// Validates that the usage of <see cref="System.ValueTuple"/> within pattern matching will produce no diagnostics.
187230
/// </summary>

StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1141UnitTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace StyleCop.Analyzers.Test.ReadabilityRules
55
{
6+
using System;
67
using System.Threading;
78
using System.Threading.Tasks;
89
using Microsoft.CodeAnalysis.Testing;
@@ -20,7 +21,7 @@ namespace StyleCop.Analyzers.Test.ReadabilityRules
2021
public class SA1141UnitTests
2122
{
2223
/// <summary>
23-
/// Verifies that usage of the ValueTuple type will not produce a diagnostic.
24+
/// Verifies that usage of <see cref="ValueTuple{T1, T2}"/> will not produce a diagnostic.
2425
/// </summary>
2526
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
2627
[Fact]
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
#nullable enable
5+
6+
namespace StyleCop.Analyzers.Helpers
7+
{
8+
using System.Threading;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
13+
internal static class SyntaxNodeExtensions
14+
{
15+
public static bool IsInExpressionTree(
16+
this SyntaxNode? node,
17+
SemanticModel semanticModel,
18+
INamedTypeSymbol? expressionType,
19+
CancellationToken cancellationToken)
20+
{
21+
if (expressionType != null)
22+
{
23+
for (var current = node; current != null; current = current.Parent)
24+
{
25+
if (current.IsAnyLambda())
26+
{
27+
var typeInfo = semanticModel.GetTypeInfo(current, cancellationToken);
28+
if (expressionType.Equals(typeInfo.ConvertedType?.OriginalDefinition))
29+
{
30+
return true;
31+
}
32+
}
33+
else if (current is SelectOrGroupClauseSyntax or OrderingSyntax)
34+
{
35+
var info = semanticModel.GetSymbolInfo(current, cancellationToken);
36+
if (AnyTakesExpressionTree(info, expressionType))
37+
{
38+
return true;
39+
}
40+
}
41+
else if (current is QueryClauseSyntax queryClause)
42+
{
43+
var info = semanticModel.GetQueryClauseInfo(queryClause, cancellationToken);
44+
if (AnyTakesExpressionTree(info.CastInfo, expressionType)
45+
|| AnyTakesExpressionTree(info.OperationInfo, expressionType))
46+
{
47+
return true;
48+
}
49+
}
50+
}
51+
}
52+
53+
return false;
54+
55+
static bool AnyTakesExpressionTree(SymbolInfo info, INamedTypeSymbol expressionType)
56+
{
57+
if (TakesExpressionTree(info.Symbol, expressionType))
58+
{
59+
return true;
60+
}
61+
62+
foreach (var symbol in info.CandidateSymbols)
63+
{
64+
if (TakesExpressionTree(symbol, expressionType))
65+
{
66+
return true;
67+
}
68+
}
69+
70+
return false;
71+
}
72+
73+
static bool TakesExpressionTree(ISymbol symbol, INamedTypeSymbol expressionType)
74+
{
75+
if (symbol is IMethodSymbol method
76+
&& method.Parameters.Length > 0
77+
&& expressionType.Equals(method.Parameters[0].Type?.OriginalDefinition))
78+
{
79+
return true;
80+
}
81+
82+
return false;
83+
}
84+
}
85+
86+
public static bool IsAnyLambda(this SyntaxNode? node)
87+
{
88+
return node.IsKind(SyntaxKind.ParenthesizedLambdaExpression)
89+
|| node.IsKind(SyntaxKind.SimpleLambdaExpression);
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)