BizHawk/ExternalProjects/BizHawk.Analyzer/OrderBySelfAnalyzer.cs

71 lines
3.2 KiB
C#

namespace BizHawk.Analyzers;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OrderBySelfAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor DiagUseOrderBySelfExt = new(
id: "BHI3101",
title: "Use .Order()/.OrderDescending() shorthand",
messageFormat: "Replace .OrderBy{0}(e => e) with .Order{0}()",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagUseOrderBySelfExt);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(initContext =>
{
if (initContext.Compilation.GetTypeByMetadataName("BizHawk.Common.CollectionExtensions.CollectionExtensions") is null) return; // project does not have BizHawk.Common dependency
var linqExtClassSym = initContext.Compilation.GetTypeByMetadataName("System.Linq.Enumerable")!;
var orderByAscSym = linqExtClassSym.GetMembers("OrderBy").Cast<IMethodSymbol>().First(sym => sym.Parameters.Length is 2);
var orderByDescSym = linqExtClassSym.GetMembers("OrderByDescending").Cast<IMethodSymbol>().First(sym => sym.Parameters.Length is 2);
initContext.RegisterOperationAction(
oac =>
{
static bool IsSelfReturnLambda(AnonymousFunctionExpressionSyntax afes)
{
ParameterSyntax paramSyn;
switch (afes)
{
case AnonymousMethodExpressionSyntax ames: // banned in BizHawk but included for completeness
if ((ames.ParameterList?.Parameters)?.Count is not 1) return false;
paramSyn = ames.ParameterList.Parameters[0];
break;
case ParenthesizedLambdaExpressionSyntax ples:
if (ples.ParameterList.Parameters.Count is not 1) return false;
paramSyn = ples.ParameterList.Parameters[0];
break;
case SimpleLambdaExpressionSyntax sles:
paramSyn = sles.Parameter;
break;
default:
return false;
}
bool Matches(IdentifierNameSyntax ins)
=> ins.Identifier.ValueText == paramSyn.Identifier.ValueText;
if (afes.ExpressionBody is not null) return afes.ExpressionBody is IdentifierNameSyntax ins && Matches(ins);
return afes.Block!.Statements.Count is 1 && afes.Block.Statements[0] is ReturnStatementSyntax { Expression: IdentifierNameSyntax ins1 } && Matches(ins1);
}
var operation = (IInvocationOperation) oac.Operation;
var calledSym = operation.TargetMethod.ConstructedFrom;
if (!(orderByAscSym!.Matches(calledSym) || orderByDescSym!.Matches(calledSym))) return;
if (((ArgumentSyntax) operation.Arguments[1].Syntax).Expression is not AnonymousFunctionExpressionSyntax afes) return;
if (IsSelfReturnLambda(afes)) oac.ReportDiagnostic(Diagnostic.Create(DiagUseOrderBySelfExt, afes.GetLocation(), orderByDescSym.Matches(calledSym) ? "Descending" : string.Empty));
},
OperationKind.Invocation);
});
}
}