You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
100 lines
3.2 KiB
100 lines
3.2 KiB
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()/First()/etc. 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 ())), |
|
Pattern.AnyString)); |
|
|
|
public IEnumerable<CodeIssue> 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 || !HasPredicateVersion(anyResolve.Member)) |
|
return; |
|
var whereInvoke = match.Get<InvocationExpression> ("whereInvoke").Single (); |
|
var whereResolve = ctx.Resolve (whereInvoke) as InvocationResolveResult; |
|
if (whereResolve == null || whereResolve.Member.Name != "Where" || !IsQueryExtensionClass(whereResolve.Member.DeclaringTypeDefinition)) |
|
return; |
|
if (whereResolve.Member.Parameters.Count != 2) |
|
return; |
|
var predResolve = whereResolve.Member.Parameters [1]; |
|
if (predResolve.Type.TypeParameterCount != 2) |
|
return; |
|
|
|
AddIssue ( |
|
anyInvoke, string.Format("Redundant Where() call with predicate followed by {0}()", anyResolve.Member.Name), |
|
script => { |
|
var arg = whereInvoke.Arguments.Single ().Clone (); |
|
var target = match.Get<Expression> ("target").Single ().Clone (); |
|
script.Replace (anyInvoke, new InvocationExpression (new MemberReferenceExpression (target, anyResolve.Member.Name), arg)); |
|
}); |
|
} |
|
|
|
bool IsQueryExtensionClass(ITypeDefinition typeDef) |
|
{ |
|
if (typeDef == null || typeDef.Namespace != "System.Linq") |
|
return false; |
|
switch (typeDef.Name) { |
|
case "Enumerable": |
|
case "ParallelEnumerable": |
|
case "Queryable": |
|
return true; |
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
bool HasPredicateVersion(IParameterizedMember member) |
|
{ |
|
if (!IsQueryExtensionClass(member.DeclaringTypeDefinition)) |
|
return false; |
|
switch (member.Name) { |
|
case "Any": |
|
case "Count": |
|
case "First": |
|
case "FirstOrDefault": |
|
case "Last": |
|
case "LastOrDefault": |
|
case "LongCount": |
|
case "Single": |
|
case "SingleOrDefault": |
|
return true; |
|
default: |
|
return false; |
|
} |
|
} |
|
} |
|
} |
|
}
|
|
|