Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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