From dcc8957be31c8ff6da9a18cfb3509ca563cb1d54 Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Tue, 19 Jul 2022 03:19:15 +1000 Subject: [PATCH] Fast-fail Analyzer properly, fix typo thanks to https://www.meziantou.net/working-with-types-in-a-roslyn-analyzer.htm --- .../FeatureNotImplementedAnalyzer.cs | 133 ++++++++---------- .../BizHawk.Analyzer/RoslynUtils.cs | 2 +- References/BizHawk.Analyzer.dll | Bin 18432 -> 18432 bytes 3 files changed, 63 insertions(+), 72 deletions(-) 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 aa5311910e93e1ab8488ed5f14c90fda36f7f33f..4fe37a090993acbe0e3c0d2e85570e172f23679b 100644 GIT binary patch delta 6434 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 delta 6336 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