diff --git a/.global.editorconfig.ini b/.global.editorconfig.ini index 321b5b618e..9f7e7ba2eb 100644 --- a/.global.editorconfig.ini +++ b/.global.editorconfig.ini @@ -14,6 +14,8 @@ dotnet_diagnostic.BHI1004.severity = error dotnet_diagnostic.BHI1005.severity = error # Do not discard local variables dotnet_diagnostic.BHI1006.severity = error +# Don't use target-typed new for throw expressions +dotnet_diagnostic.BHI1007.severity = suggestion # Don't call this.GetType() in sealed type, use typeof operator dotnet_diagnostic.BHI1100.severity = error # Don't call this.GetType(), use typeof operator (or replace subtype check with better encapsulation) diff --git a/ExternalProjects/BizHawk.Analyzer/NoTargetTypedThrowAnalyzer.cs b/ExternalProjects/BizHawk.Analyzer/NoTargetTypedThrowAnalyzer.cs new file mode 100644 index 0000000000..504059dc04 --- /dev/null +++ b/ExternalProjects/BizHawk.Analyzer/NoTargetTypedThrowAnalyzer.cs @@ -0,0 +1,58 @@ +namespace BizHawk.Analyzers; + +using System.Collections.Immutable; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class NoTargetTypedThrowAnalyzer : DiagnosticAnalyzer +{ + private const string ERR_MSG_IMPLICIT = "Specify `Exception` (or a more precise type) explicitly"; + + private static readonly DiagnosticDescriptor DiagNoTargetTypedThrow = new( + id: "BHI1007", + title: "Don't use target-typed new for throw expressions", + messageFormat: "{0}", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagNoTargetTypedThrow); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction( + oac => + { + var exceptionOp = ((IThrowOperation) oac.Operation).Exception; + if (exceptionOp is null) return; // re-`throw;` + void Fail(string message) + => oac.ReportDiagnostic(Diagnostic.Create(DiagNoTargetTypedThrow, exceptionOp.Syntax.GetLocation(), message)); + switch (exceptionOp.Kind) + { + case OperationKind.ObjectCreation: + case OperationKind.Invocation: + case OperationKind.PropertyReference: + case OperationKind.LocalReference: + return; + case OperationKind.Conversion: + if (((IConversionOperation) exceptionOp).Operand.Syntax + .IsKind(SyntaxKind.ImplicitObjectCreationExpression)) + { + break; + } + return; + default: + Fail($"Argument to throw expression was of an unexpected kind: {exceptionOp.GetType().FullName}"); + return; + } + Fail(ERR_MSG_IMPLICIT); + }, + OperationKind.Throw); + } +} diff --git a/ExternalProjects/BizHawk.AnalyzersTests/BizHawk.Analyzer/NoTargetTypedThrowAnalyzerTests.cs b/ExternalProjects/BizHawk.AnalyzersTests/BizHawk.Analyzer/NoTargetTypedThrowAnalyzerTests.cs new file mode 100644 index 0000000000..8d97e33410 --- /dev/null +++ b/ExternalProjects/BizHawk.AnalyzersTests/BizHawk.Analyzer/NoTargetTypedThrowAnalyzerTests.cs @@ -0,0 +1,43 @@ +namespace BizHawk.Tests.Analyzers; + +using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier< + BizHawk.Analyzers.NoTargetTypedThrowAnalyzer, + Microsoft.CodeAnalysis.Testing.DefaultVerifier>; + +[TestClass] +public sealed class NoTargetTypedThrowAnalyzerTests +{ + [TestMethod] + public Task CheckMisuseOfXORAssignment() + => Verify.VerifyAnalyzerAsync(""" + using System; + public static class Cases { + // not present: throwing a local, which is apparently necessary in Win32 interop + private static void V() + => throw ExceptionHelperProp; + private static void W() + => throw ExceptionHelperMethod(); + private static void X() { + try { + Z(); + } catch (Exception) { + throw; + } + } + private static void Y() + => throw new Exception(); + private static void Z() + => throw new NotImplementedException(); + private static void A() + => throw {|BHI1007:new()|}; + private static Exception ExceptionHelperMethod() + => new(); + private static Exception ExceptionHelperProp + => new(); + } + """); +} diff --git a/References/BizHawk.Analyzer.dll b/References/BizHawk.Analyzer.dll index c72f054f26..e6a8d9e73c 100644 Binary files a/References/BizHawk.Analyzer.dll and b/References/BizHawk.Analyzer.dll differ