BizHawk/ExternalProjects/BizHawk.Analyzer/ExprBodiedMemberFlowAnalyze...

118 lines
4.1 KiB
C#

namespace BizHawk.Analyzers;
using System.Collections.Immutable;
using System.Linq;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ExprBodiedMemberFlowAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor DiagExprBodiedMemberFlow = new(
id: "BHI1120",
title: "Expression-bodied member should be flowed to next line correctly",
messageFormat: "{0}",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create(/*HawkSourceAnalyzer.DiagWTF,*/ DiagExprBodiedMemberFlow);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
var ARROW_ONE_LINE = (' ', ' ');
// var ARROW_POST_SIG = (' ', '\n');
var ARROW_PRE_BODY = ('\n', ' ');
context.RegisterSyntaxNodeAction(
snac =>
{
var aecs = (ArrowExpressionClauseSyntax) snac.Node;
(char Before, char After) expectedWhitespace;
string kind;
var parent = aecs.Parent;
if (parent is null)
{
HawkSourceAnalyzer.ReportWTF(aecs, snac, message: $"[{nameof(ExprBodiedMemberFlowAnalyzer)}] Syntax node for expression-bodied member was orphaned?");
return;
}
switch (parent)
{
case MethodDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "method";
break;
case PropertyDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "get-only prop";
break;
case AccessorDeclarationSyntax ads:
expectedWhitespace = ARROW_ONE_LINE;
switch (ads.Keyword.Text)
{
case "get":
kind = ads.Parent?.Parent is IndexerDeclarationSyntax ? "get-indexer" : "getter";
break;
case "set":
kind = ads.Parent?.Parent is IndexerDeclarationSyntax ? "set-indexer" : "setter";
break;
case "init":
kind = "setter";
break;
case "add":
kind = "event sub";
break;
case "remove":
kind = "event unsub";
break;
default:
HawkSourceAnalyzer.ReportWTF(parent, snac, message: $"[{nameof(ExprBodiedMemberFlowAnalyzer)}] Expression-bodied accessor was of an unexpected kind: {ads.Parent!.Parent!.GetType().FullName}");
return;
}
break;
case ConstructorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "constructor";
break;
case LocalFunctionStatementSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "local method";
break;
case IndexerDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "get-only indexer";
break;
case OperatorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "overloaded operator";
break;
case ConversionOperatorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "overloaded cast operator";
break;
case DestructorDeclarationSyntax:
expectedWhitespace = ARROW_PRE_BODY;
kind = "finalizer";
break;
default:
HawkSourceAnalyzer.ReportWTF(parent, snac, message: $"[{nameof(ExprBodiedMemberFlowAnalyzer)}] Expression-bodied member was of an unexpected kind: {parent.GetType().FullName}");
return;
}
static string EscapeChar(char c)
=> c is '\n' ? "\\n" : c.ToString();
void Fail()
=> DiagExprBodiedMemberFlow.ReportAt(parent, snac, $"Whitespace around {kind} arrow syntax should be `{EscapeChar(expectedWhitespace.Before)}=>{EscapeChar(expectedWhitespace.After)}`");
if ((aecs.ArrowToken.HasLeadingTrivia ? '\n' : ' ') != expectedWhitespace.Before)
{
Fail();
return;
}
#pragma warning disable BHI3102 // LINQ `Contains(char)` is fine here
var hasLineBreakAfterArrow = aecs.ArrowToken.HasTrailingTrivia && aecs.ArrowToken.TrailingTrivia.ToFullString().Contains('\n');
#pragma warning restore BHI3102
if ((hasLineBreakAfterArrow ? '\n' : ' ') != expectedWhitespace.After) Fail();
},
SyntaxKind.ArrowExpressionClause);
}
}