Skip to content

Commit

Permalink
MA0029 should not report when for IQueryable when the predicate is a …
Browse files Browse the repository at this point in the history
…reference
  • Loading branch information
meziantou committed May 18, 2024
1 parent 23b9118 commit ed6845a
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 12 deletions.
39 changes: 27 additions & 12 deletions src/Meziantou.Analyzer.CodeFixers/Rules/OptimizeLinqUsageFixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -594,22 +594,37 @@ private static async Task<Document> CombineWhereWithNextMethod(Document document

if (argument1 is null)
return argument2?.Syntax;
if (argument1.Value is not IDelegateCreationOperation value1 || argument2.Value is not IDelegateCreationOperation value2)
return null;
if (argument1.Value is IDelegateCreationOperation value1 && argument2.Value is IDelegateCreationOperation value2)
{
var anonymousMethod1 = value1.Target as IAnonymousFunctionOperation;
var anonymousMethod2 = value2.Target as IAnonymousFunctionOperation;

var newParameterName =
anonymousMethod1?.Symbol.Parameters.ElementAtOrDefault(0)?.Name ??
anonymousMethod2?.Symbol.Parameters.ElementAtOrDefault(0)?.Name ??
"x";

var anonymousMethod1 = value1.Target as IAnonymousFunctionOperation;
var anonymousMethod2 = value2.Target as IAnonymousFunctionOperation;
var left = PrepareSyntaxNode(generator, value1, newParameterName);
var right = PrepareSyntaxNode(generator, value2, newParameterName);

var newParameterName =
anonymousMethod1?.Symbol.Parameters.ElementAtOrDefault(0)?.Name ??
anonymousMethod2?.Symbol.Parameters.ElementAtOrDefault(0)?.Name ??
"x";
return generator.ValueReturningLambdaExpression(newParameterName,
generator.LogicalAndExpression(left, right));
}
else if (argument1.Value.UnwrapConversionOperations() is IAnonymousFunctionOperation anonymousMethod1 && argument2.Value.UnwrapImplicitConversionOperations() is IAnonymousFunctionOperation anonymousMethod2)
{
var newParameterName =
anonymousMethod1.Symbol.Parameters.ElementAtOrDefault(0)?.Name ??
anonymousMethod2.Symbol.Parameters.ElementAtOrDefault(0)?.Name ??
"x";

var left = PrepareSyntaxNode(generator, value1, newParameterName);
var right = PrepareSyntaxNode(generator, value2, newParameterName);
var left = ReplaceParameter(anonymousMethod1, newParameterName);
var right = ReplaceParameter(anonymousMethod2, newParameterName);

return generator.ValueReturningLambdaExpression(newParameterName,
generator.LogicalAndExpression(left, right));
return generator.ValueReturningLambdaExpression(newParameterName,
generator.LogicalAndExpression(left, right));
}

return null;
}

static SyntaxNode PrepareSyntaxNode(SyntaxGenerator generator, IDelegateCreationOperation delegateCreationOperation, string parameterName)
Expand Down
18 changes: 18 additions & 0 deletions src/Meziantou.Analyzer/Rules/OptimizeLinqUsageAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public AnalyzerContext(Compilation compilation)
ExtensionMethodOwnerTypes.AddIfNotNull(EnumerableSymbol);
ExtensionMethodOwnerTypes.AddIfNotNull(QueryableSymbol);

ExpressionOfTSymbol = compilation.GetBestTypeByMetadataName("System.Linq.Expressions.Expression`1");
ICollectionOfTSymbol = compilation.GetBestTypeByMetadataName("System.Collections.Generic.ICollection`1");
IReadOnlyCollectionOfTSymbol = compilation.GetBestTypeByMetadataName("System.Collections.Generic.IReadOnlyCollection`1");
ListOfTSymbol = compilation.GetBestTypeByMetadataName("System.Collections.Generic.List`1");
Expand All @@ -144,6 +145,7 @@ public AnalyzerContext(Compilation compilation)

private INamedTypeSymbol? EnumerableSymbol { get; set; }
private INamedTypeSymbol? QueryableSymbol { get; set; }
private INamedTypeSymbol? ExpressionOfTSymbol { get; set; }
private INamedTypeSymbol? ICollectionOfTSymbol { get; set; }
private INamedTypeSymbol? IReadOnlyCollectionOfTSymbol { get; set; }
private INamedTypeSymbol? ListOfTSymbol { get; set; }
Expand Down Expand Up @@ -432,12 +434,28 @@ private void CombineWhereWithNextMethod(OperationAnalysisContext context, IInvoc
.Add("LastOperationLength", parent.Syntax.Span.Length.ToString(CultureInfo.InvariantCulture))
.Add("MethodName", parent.TargetMethod.Name);

if (parent.Arguments.Length > 1 && IsExpressionPredicateReference(parent.Arguments[1].Value))
return;

if (operation.Arguments.Length > 1 && IsExpressionPredicateReference(operation.Arguments[1].Value))
return;

context.ReportDiagnostic(CombineLinqMethodsRule, properties, parent, operation.TargetMethod.Name, parent.TargetMethod.Name);
}
}
}
}

private bool IsExpressionPredicateReference(IOperation operation)
{
if (operation.Type is null || !operation.Type.OriginalDefinition.IsEqualTo(ExpressionOfTSymbol))
return false;

operation = operation.UnwrapImplicitConversionOperations();

return operation.Kind is not OperationKind.AnonymousFunction;
}

private void RemoveTwoConsecutiveOrderBy(OperationAnalysisContext context, IInvocationOperation operation)
{
if (operation.TargetMethod.Name == nameof(Enumerable.OrderBy) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,56 @@ public Test()
.ValidateAsync();
}

[Fact]
public async Task CombineWhereWithTheFollowingWhereMethod_ExpressionWithPredicate()
{
await CreateProjectBuilder()
.WithSourceCode("""
using System;
using System.Linq;
using System.Linq.Expressions;
class Test
{
public Test(Expression<Func<int, bool>> predicate)
{
IQueryable<int> queryable = null!;
queryable.Where(x => x == 0).Where(predicate);
queryable.Where(predicate).Where(x => x == 0);
}
}

""")
.ValidateAsync();
}

[Fact]
public async Task CombineWhereWithTheFollowingWhereMethod_IQueryable()
{
await CreateProjectBuilder()
.WithSourceCode(@"using System.Linq;
class Test
{
public Test()
{
System.Linq.IQueryable<int> enumerable = null;
[||]enumerable.Where(x => x == 0).Where(y => true);
}
}
")
.ShouldReportDiagnosticWithMessage($"Combine 'Where' with 'Where'")
.ShouldFixCodeWith(@"using System.Linq;
class Test
{
public Test()
{
System.Linq.IQueryable<int> enumerable = null;
enumerable.Where(x => x == 0 && true);
}
}
")
.ValidateAsync();
}

[Fact]
public async Task CombineWhereWithTheFollowingMethod_CombineLambdaWithNothing()
{
Expand Down

0 comments on commit ed6845a

Please sign in to comment.