Skip to content

Commit

Permalink
Avoid reporting SA1141 (Use tuple syntax) in expression trees
Browse files Browse the repository at this point in the history
Fixes #3305
  • Loading branch information
sharwell committed Feb 24, 2021
1 parent 23db6c0 commit 2eb87fe
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
break;
}

var newSyntaxRoot = syntaxRoot.ReplaceNode(node, newNode).WithAdditionalAnnotations(Simplifier.Annotation);
var newSyntaxRoot = syntaxRoot.ReplaceNode(node, newNode.WithAdditionalAnnotations(Simplifier.Annotation));
return document.WithSyntaxRoot(newSyntaxRoot.WithoutFormatting());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,49 @@ public void TestMethod()
await VerifyCSharpFixAsync(testCode, expectedDiagnostics, fixedCode, CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Validates that the usage of <see cref="System.ValueTuple"/> within LINQ expression trees will produce no
/// diagnostics.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
[WorkItem(3305, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3305")]
public async Task ValidateValueTupleUsageInExpressionTreeAsync()
{
var testCode = @"using System;
using System.Linq.Expressions;
public class TestClass
{
Expression<Func<(int, int)>> expression1 = () => ValueTuple.Create(10, 20);
Expression<Func<(int, int)>> expression2 = () => new ValueTuple<int, int>(10, 20);
Expression<Func<((int, int), int)>> expression3 = () => new ValueTuple<ValueTuple<int, int>, int>(ValueTuple.Create(10, 10), 20);
Expression<Func<(int, int)>> expression4 = () => new System.ValueTuple<int, int>(10, 20);
Expression<Func<(int, int), (int, int)>> expression5 = arg => new System.ValueTuple<int, int>(arg.Item1, arg.Item2);
Expression<Func<(int, int), (int, int)>> expression6 = (arg) => new System.ValueTuple<int, int>(arg.Item1, arg.Item2);
Expression<Func<[|ValueTuple<int, int>|], [|ValueTuple<int, int>|]>> expression7 = ([|ValueTuple<int, int>|] arg) => ValueTuple.Create(arg.Item1, arg.Item2);
}
";
var fixedCode = @"using System;
using System.Linq.Expressions;
public class TestClass
{
Expression<Func<(int, int)>> expression1 = () => ValueTuple.Create(10, 20);
Expression<Func<(int, int)>> expression2 = () => new ValueTuple<int, int>(10, 20);
Expression<Func<((int, int), int)>> expression3 = () => new ValueTuple<ValueTuple<int, int>, int>(ValueTuple.Create(10, 10), 20);
Expression<Func<(int, int)>> expression4 = () => new System.ValueTuple<int, int>(10, 20);
Expression<Func<(int, int), (int, int)>> expression5 = arg => new System.ValueTuple<int, int>(arg.Item1, arg.Item2);
Expression<Func<(int, int), (int, int)>> expression6 = (arg) => new System.ValueTuple<int, int>(arg.Item1, arg.Item2);
Expression<Func<(int, int), (int, int)>> expression7 = ((int, int) arg) => ValueTuple.Create(arg.Item1, arg.Item2);
}
";

await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Validates that the usage of <see cref="System.ValueTuple"/> within pattern matching will produce no diagnostics.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace StyleCop.Analyzers.Test.ReadabilityRules
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
Expand All @@ -20,7 +21,7 @@ namespace StyleCop.Analyzers.Test.ReadabilityRules
public class SA1141UnitTests
{
/// <summary>
/// Verifies that usage of the ValueTuple type will not produce a diagnostic.
/// Verifies that usage of <see cref="ValueTuple{T1, T2}"/> will not produce a diagnostic.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

#nullable enable

namespace StyleCop.Analyzers.Helpers
{
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

internal static class SyntaxNodeExtensions
{
public static bool IsInExpressionTree(
this SyntaxNode? node,
SemanticModel semanticModel,
INamedTypeSymbol? expressionType,
CancellationToken cancellationToken)
{
if (expressionType != null)
{
for (var current = node; current != null; current = current.Parent)
{
if (current.IsAnyLambda())
{
var typeInfo = semanticModel.GetTypeInfo(current, cancellationToken);
if (expressionType.Equals(typeInfo.ConvertedType?.OriginalDefinition))
{
return true;
}
}
else if (current is SelectOrGroupClauseSyntax or OrderingSyntax)
{
var info = semanticModel.GetSymbolInfo(current, cancellationToken);
if (AnyTakesExpressionTree(info, expressionType))
{
return true;
}
}
else if (current is QueryClauseSyntax queryClause)
{
var info = semanticModel.GetQueryClauseInfo(queryClause, cancellationToken);
if (AnyTakesExpressionTree(info.CastInfo, expressionType)
|| AnyTakesExpressionTree(info.OperationInfo, expressionType))
{
return true;
}
}
}
}

return false;

static bool AnyTakesExpressionTree(SymbolInfo info, INamedTypeSymbol expressionType)
{
if (TakesExpressionTree(info.Symbol, expressionType))
{
return true;
}

foreach (var symbol in info.CandidateSymbols)
{
if (TakesExpressionTree(symbol, expressionType))
{
return true;
}
}

return false;
}

static bool TakesExpressionTree(ISymbol symbol, INamedTypeSymbol expressionType)
{
if (symbol is IMethodSymbol method
&& method.Parameters.Length > 0
&& expressionType.Equals(method.Parameters[0].Type?.OriginalDefinition))
{
return true;
}

return false;
}
}

public static bool IsAnyLambda(this SyntaxNode? node)
{
return node.IsKind(SyntaxKind.ParenthesizedLambdaExpression)
|| node.IsKind(SyntaxKind.SimpleLambdaExpression);
}
}
}
Loading

0 comments on commit 2eb87fe

Please sign in to comment.