diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index a62692406f..65cbb19826 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -498,6 +498,7 @@ + diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/RedundantWhereWithPredicateIssue.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/RedundantWhereWithPredicateIssue.cs new file mode 100644 index 0000000000..45c930748f --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeIssues/RedundantWhereWithPredicateIssue.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.PatternMatching; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + [IssueDescription("Any() should be used with predicate and Where() removed", + Description= "Detects redundant Where() with predicate calls followed by Any().", + Category = IssueCategories.CodeQualityIssues, + Severity = Severity.Hint)] + public class RedundantWhereWithPredicateIssue : ICodeIssueProvider + { + static readonly AstNode pattern = + new InvocationExpression ( + new MemberReferenceExpression ( + new NamedNode ("whereInvoke", + new InvocationExpression ( + new MemberReferenceExpression (new AnyNode ("target"), "Where"), + new AnyNode ())), + "Any")); + + public IEnumerable GetIssues(BaseRefactoringContext context) + { + return new GatherVisitor(context).GetIssues(); + } + + class GatherVisitor : GatherVisitorBase + { + public GatherVisitor (BaseRefactoringContext ctx) : base (ctx) + { + } + + public override void VisitInvocationExpression (InvocationExpression anyInvoke) + { + base.VisitInvocationExpression (anyInvoke); + + var match = pattern.Match (anyInvoke); + if (!match.Success) + return; + + var anyResolve = ctx.Resolve (anyInvoke) as InvocationResolveResult; + if (anyResolve == null || anyResolve.Member.FullName != "System.Linq.Enumerable.Any") + return; + var whereInvoke = match.Get ("whereInvoke").Single (); + var whereResolve = ctx.Resolve (whereInvoke) as InvocationResolveResult; + if (whereResolve == null || whereResolve.Member.FullName != "System.Linq.Enumerable.Where") + return; + if (whereResolve.Member.Parameters.Count != 2) + return; + var predResolve = whereResolve.Member.Parameters [1]; + if (predResolve.Type.TypeParameterCount != 2) + return; + + AddIssue (anyInvoke, "Redundant Where() call with predicate followed by Any()", script => { + var arg = whereInvoke.Arguments.Single ().Clone (); + var target = match.Get ("target").Single ().Clone (); + script.Replace (anyInvoke, new InvocationExpression (new MemberReferenceExpression (target, "Any"), arg)); + }); + } + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/RedundantWhereWithPredicateIssueTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/RedundantWhereWithPredicateIssueTests.cs new file mode 100644 index 0000000000..9c712e6726 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeIssues/RedundantWhereWithPredicateIssueTests.cs @@ -0,0 +1,70 @@ +using System; +using NUnit.Framework; +using ICSharpCode.NRefactory.CSharp.Refactoring; +using ICSharpCode.NRefactory.CSharp.CodeActions; + +namespace ICSharpCode.NRefactory.CSharp.CodeIssues +{ + [TestFixture] + public class RedundantWhereWithPredicateIssueTests : InspectionActionTestBase + { + [Test] + public void TestWhereAnyCase1 () + { + var input = @"using System.Linq; +public class CSharpDemo { + public void Bla () { + int[] arr; + var bla = arr.Where (x => x < 4).Any (); + } +}"; + + TestRefactoringContext context; + var issues = GetIssues (new RedundantWhereWithPredicateIssue (), input, out context); + Assert.AreEqual (1, issues.Count); + CheckFix (context, issues, @"using System.Linq; +public class CSharpDemo { + public void Bla () { + int[] arr; + var bla = arr.Any (x => x < 4); + } +}"); + } + + [Test] + public void TestWhereAnyWrongWhere1 () + { + var input = @"using System.Linq; +public class CSharpDemo { + public void Bla () { + int[] arr; + var bla = arr.Where ((x, i) => x + i < 4).Any (); + } +}"; + + TestRefactoringContext context; + var issues = GetIssues (new RedundantWhereWithPredicateIssue (), input, out context); + Assert.AreEqual (0, issues.Count); + } + + [Test] + public void TestWhereAnyWrongWhere2 () + { + var input = @"using System; +using System.Linq; +public class X +{ + X Where (Func f) { return null; } + bool Any () { return false; } + public void Bla () { + X ex = null; + var bla = ex.Where (x => x + 1).Any (); + } +}"; + + TestRefactoringContext context; + var issues = GetIssues (new RedundantWhereWithPredicateIssue (), input, out context); + Assert.AreEqual (0, issues.Count); + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 37f23a1085..f738c7aa97 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -359,6 +359,7 @@ +