diff --git a/ExternalProjects/BizHawk.Analyzer/FeatureNotImplementedAnalyzer.cs b/ExternalProjects/BizHawk.Analyzer/FeatureNotImplementedAnalyzer.cs index bc7c6fb9c9..ffaedb03cb 100644 --- a/ExternalProjects/BizHawk.Analyzer/FeatureNotImplementedAnalyzer.cs +++ b/ExternalProjects/BizHawk.Analyzer/FeatureNotImplementedAnalyzer.cs @@ -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 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 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); + }); } } diff --git a/ExternalProjects/BizHawk.Analyzer/RoslynUtils.cs b/ExternalProjects/BizHawk.Analyzer/RoslynUtils.cs index 051658fecf..2a7ffbf43e 100644 --- a/ExternalProjects/BizHawk.Analyzer/RoslynUtils.cs +++ b/ExternalProjects/BizHawk.Analyzer/RoslynUtils.cs @@ -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 ` diff --git a/References/BizHawk.Analyzer.dll b/References/BizHawk.Analyzer.dll index aa5311910e..4fe37a0909 100644 Binary files a/References/BizHawk.Analyzer.dll and b/References/BizHawk.Analyzer.dll differ