Fast-fail Analyzer properly, fix typo

thanks to https://www.meziantou.net/working-with-types-in-a-roslyn-analyzer.htm
This commit is contained in:
YoshiRulz 2022-07-19 03:19:15 +10:00
parent 4956bae3a2
commit dcc8957be3
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
3 changed files with 63 additions and 72 deletions

View File

@ -33,79 +33,70 @@ public sealed class FeatureNotImplementedAnalyzer : DiagnosticAnalyzer
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
var skipThisProject = false;
INamedTypeSymbol? featureNotImplementedAttrSym = null;
INamedTypeSymbol? notImplementedExceptionSym = null;
context.RegisterSyntaxNodeAction(
snac =>
{
if (skipThisProject) return;
if (featureNotImplementedAttrSym is null)
context.RegisterCompilationStartAction(initContext =>
{
var featureNotImplementedAttrSym = initContext.Compilation.GetTypeByMetadataName("BizHawk.Emulation.Common.FeatureNotImplementedAttribute");
if (featureNotImplementedAttrSym is null) return; // project does not have BizHawk.Emulation.Common dependency
var notImplementedExceptionSym = initContext.Compilation.GetTypeByMetadataName("System.NotImplementedException")!;
initContext.RegisterSyntaxNodeAction(
snac =>
{
featureNotImplementedAttrSym = snac.Compilation.GetTypeByMetadataName("BizHawk.Emulation.Common.FeatureNotImplementedAttribute");
if (featureNotImplementedAttrSym is null)
void Wat(Location location)
=> snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_UNEXPECTED_INCANTATION));
void MaybeReportFor(ITypeSymbol? thrownExceptionType, Location location)
{
// project does not have BizHawk.Emulation.Common dependency
skipThisProject = true;
return;
if (thrownExceptionType is null) snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_METHOD_THROWS_UNKNOWN));
else if (!notImplementedExceptionSym.Matches(thrownExceptionType)) snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_THROWS_WRONG_TYPE));
// else correct usage, do not flag
}
notImplementedExceptionSym = snac.Compilation.GetTypeByMetadataName("System.NotImplementedException")!;
}
void Wat(Location location)
=> snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_UNEXPECTED_INCANTATION));
void MaybeReportFor(ITypeSymbol? thrownExceptionType, Location location)
{
if (thrownExceptionType is null) snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_METHOD_THROWS_UNKNOWN));
else if (!notImplementedExceptionSym!.Matches(thrownExceptionType)) snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_THROWS_WRONG_TYPE));
// else correct usage, do not flag
}
bool IncludesFNIAttribute(SyntaxList<AttributeListSyntax> mds)
=> mds.SelectMany(static als => als.Attributes).Any(aSyn => featureNotImplementedAttrSym.Matches(snac.SemanticModel.GetTypeInfo(aSyn).Type));
void CheckBlockBody(BlockSyntax bs, Location location)
{
if (bs.Statements.Count is not 1) snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_DOES_NOT_THROW));
else if (bs.Statements[0] is not ThrowStatementSyntax tss) snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_DOES_NOT_THROW));
else MaybeReportFor(snac.SemanticModel.GetThrownExceptionType(tss), tss.GetLocation());
}
void CheckExprBody(ArrowExpressionClauseSyntax aecs, Location location)
{
if (aecs.Expression is not ThrowExpressionSyntax tes) snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_DOES_NOT_THROW));
else MaybeReportFor(snac.SemanticModel.GetThrownExceptionType(tes), tes.GetLocation());
}
void CheckAccessor(AccessorDeclarationSyntax ads)
{
if (!IncludesFNIAttribute(ads.AttributeLists)) return;
if (ads.ExpressionBody is not null) CheckExprBody(ads.ExpressionBody, ads.GetLocation());
else if (ads.Body is not null) CheckBlockBody(ads.Body, ads.GetLocation());
else Wat(ads.GetLocation());
}
switch (snac.Node)
{
case AccessorDeclarationSyntax ads:
CheckAccessor(ads);
break;
case MethodDeclarationSyntax mds:
if (!IncludesFNIAttribute(mds.AttributeLists)) return;
if (mds.ExpressionBody is not null) CheckExprBody(mds.ExpressionBody, mds.GetLocation());
else if (mds.Body is not null) CheckBlockBody(mds.Body, mds.GetLocation());
else Wat(mds.GetLocation());
break;
case PropertyDeclarationSyntax pds:
if (pds.ExpressionBody is not null)
{
if (IncludesFNIAttribute(pds.AttributeLists)) CheckExprBody(pds.ExpressionBody, pds.GetLocation());
}
else
{
if (IncludesFNIAttribute(pds.AttributeLists)) Wat(pds.GetLocation());
else foreach (var accessor in pds.AccessorList!.Accessors) CheckAccessor(accessor);
}
break;
}
},
SyntaxKind.GetAccessorDeclaration,
SyntaxKind.MethodDeclaration,
SyntaxKind.PropertyDeclaration,
SyntaxKind.SetAccessorDeclaration);
bool IncludesFNIAttribute(SyntaxList<AttributeListSyntax> mds)
=> mds.SelectMany(static als => als.Attributes).Any(aSyn => featureNotImplementedAttrSym.Matches(snac.SemanticModel.GetTypeInfo(aSyn).Type));
void CheckBlockBody(BlockSyntax bs, Location location)
{
if (bs.Statements.Count is not 1) snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_DOES_NOT_THROW));
else if (bs.Statements[0] is not ThrowStatementSyntax tss) snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_DOES_NOT_THROW));
else MaybeReportFor(snac.SemanticModel.GetThrownExceptionType(tss), tss.GetLocation());
}
void CheckExprBody(ArrowExpressionClauseSyntax aecs, Location location)
{
if (aecs.Expression is not ThrowExpressionSyntax tes) snac.ReportDiagnostic(Diagnostic.Create(DiagShouldThrowNIE, location, ERR_MSG_DOES_NOT_THROW));
else MaybeReportFor(snac.SemanticModel.GetThrownExceptionType(tes), tes.GetLocation());
}
void CheckAccessor(AccessorDeclarationSyntax ads)
{
if (!IncludesFNIAttribute(ads.AttributeLists)) return;
if (ads.ExpressionBody is not null) CheckExprBody(ads.ExpressionBody, ads.GetLocation());
else if (ads.Body is not null) CheckBlockBody(ads.Body, ads.GetLocation());
else Wat(ads.GetLocation());
}
switch (snac.Node)
{
case AccessorDeclarationSyntax ads:
CheckAccessor(ads);
break;
case MethodDeclarationSyntax mds:
if (!IncludesFNIAttribute(mds.AttributeLists)) return;
if (mds.ExpressionBody is not null) CheckExprBody(mds.ExpressionBody, mds.GetLocation());
else if (mds.Body is not null) CheckBlockBody(mds.Body, mds.GetLocation());
else Wat(mds.GetLocation());
break;
case PropertyDeclarationSyntax pds:
if (pds.ExpressionBody is not null)
{
if (IncludesFNIAttribute(pds.AttributeLists)) CheckExprBody(pds.ExpressionBody, pds.GetLocation());
}
else
{
if (IncludesFNIAttribute(pds.AttributeLists)) Wat(pds.GetLocation());
else foreach (var accessor in pds.AccessorList!.Accessors) CheckAccessor(accessor);
}
break;
}
},
SyntaxKind.GetAccessorDeclaration,
SyntaxKind.MethodDeclaration,
SyntaxKind.PropertyDeclaration,
SyntaxKind.SetAccessorDeclaration);
});
}
}

View File

@ -6,7 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
internal static class RoslynUtils
{
private static ITypeSymbol? GetThrownExceptionType(this SemanticModel model, ExpressionSyntax exprSyn)
=> exprSyn is ObjectCreationExpressionSyntax oces
=> exprSyn is ObjectCreationExpressionSyntax
? model.GetTypeInfo(exprSyn).Type
: null; // code reads `throw <something weird>`

Binary file not shown.