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:
parent
4956bae3a2
commit
dcc8957be3
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
Loading…
Reference in New Issue