From 84edf98592e7ab469a3834d9431c63a734a74597 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 09:46:04 +0000
Subject: [PATCH 1/9] Initial plan
From 6386294b155a42347339a66a3ac6510fd0e4fd3a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 09:56:44 +0000
Subject: [PATCH 2/9] Add AvoidAssertsInCatchBlocksAnalyzer (MSTEST0058)
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
---
.../AvoidAssertsInCatchBlocksAnalyzer.cs | 100 ++++++
.../MSTest.Analyzers/Helpers/DiagnosticIds.cs | 1 +
src/Analyzers/MSTest.Analyzers/Resources.resx | 9 +
.../MSTest.Analyzers/xlf/Resources.cs.xlf | 15 +
.../MSTest.Analyzers/xlf/Resources.de.xlf | 15 +
.../MSTest.Analyzers/xlf/Resources.es.xlf | 15 +
.../MSTest.Analyzers/xlf/Resources.fr.xlf | 15 +
.../MSTest.Analyzers/xlf/Resources.it.xlf | 15 +
.../MSTest.Analyzers/xlf/Resources.ja.xlf | 15 +
.../MSTest.Analyzers/xlf/Resources.ko.xlf | 15 +
.../MSTest.Analyzers/xlf/Resources.pl.xlf | 15 +
.../MSTest.Analyzers/xlf/Resources.pt-BR.xlf | 15 +
.../MSTest.Analyzers/xlf/Resources.ru.xlf | 15 +
.../MSTest.Analyzers/xlf/Resources.tr.xlf | 15 +
.../xlf/Resources.zh-Hans.xlf | 15 +
.../xlf/Resources.zh-Hant.xlf | 15 +
.../AvoidAssertsInCatchBlocksAnalyzerTests.cs | 297 ++++++++++++++++++
17 files changed, 602 insertions(+)
create mode 100644 src/Analyzers/MSTest.Analyzers/AvoidAssertsInCatchBlocksAnalyzer.cs
create mode 100644 test/UnitTests/MSTest.Analyzers.UnitTests/AvoidAssertsInCatchBlocksAnalyzerTests.cs
diff --git a/src/Analyzers/MSTest.Analyzers/AvoidAssertsInCatchBlocksAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/AvoidAssertsInCatchBlocksAnalyzer.cs
new file mode 100644
index 0000000000..e0a86014d7
--- /dev/null
+++ b/src/Analyzers/MSTest.Analyzers/AvoidAssertsInCatchBlocksAnalyzer.cs
@@ -0,0 +1,100 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Immutable;
+
+using Analyzer.Utilities.Extensions;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+using MSTest.Analyzers.Helpers;
+
+namespace MSTest.Analyzers;
+
+///
+/// MSTEST0058: .
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
+public sealed class AvoidAssertsInCatchBlocksAnalyzer : DiagnosticAnalyzer
+{
+ private static readonly LocalizableResourceString Title = new(nameof(Resources.AvoidAssertsInCatchBlocksTitle), Resources.ResourceManager, typeof(Resources));
+ private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.AvoidAssertsInCatchBlocksMessageFormat), Resources.ResourceManager, typeof(Resources));
+ private static readonly LocalizableResourceString Description = new(nameof(Resources.AvoidAssertsInCatchBlocksDescription), Resources.ResourceManager, typeof(Resources));
+
+ internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
+ DiagnosticIds.AvoidAssertsInCatchBlocksRuleId,
+ Title,
+ MessageFormat,
+ Description,
+ Category.Usage,
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; }
+ = ImmutableArray.Create(Rule);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+
+ context.RegisterCompilationStartAction(context =>
+ {
+ Compilation compilation = context.Compilation;
+ INamedTypeSymbol? assertSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingAssert);
+ INamedTypeSymbol? stringAssertSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingStringAssert);
+ INamedTypeSymbol? collectionAssertSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingCollectionAssert);
+
+ if (assertSymbol is not null || stringAssertSymbol is not null || collectionAssertSymbol is not null)
+ {
+ context.RegisterOperationAction(context => AnalyzeOperation(context, assertSymbol, stringAssertSymbol, collectionAssertSymbol), OperationKind.Invocation);
+ }
+ });
+ }
+
+ private static void AnalyzeOperation(
+ OperationAnalysisContext context,
+ INamedTypeSymbol? assertSymbol,
+ INamedTypeSymbol? stringAssertSymbol,
+ INamedTypeSymbol? collectionAssertSymbol)
+ {
+ var operation = (IInvocationOperation)context.Operation;
+
+ INamedTypeSymbol targetType = operation.TargetMethod.ContainingType;
+ bool isAssertType =
+ targetType.Equals(assertSymbol, SymbolEqualityComparer.Default) ||
+ targetType.Equals(stringAssertSymbol, SymbolEqualityComparer.Default) ||
+ targetType.Equals(collectionAssertSymbol, SymbolEqualityComparer.Default);
+
+ if (!isAssertType)
+ {
+ return;
+ }
+
+ // Walk up the operation tree to check if we're inside a catch clause
+ if (IsInsideCatchClause(operation))
+ {
+ context.ReportDiagnostic(operation.CreateDiagnostic(Rule));
+ }
+ }
+
+ private static bool IsInsideCatchClause(IOperation operation)
+ {
+ IOperation? current = operation;
+ while (current is not null)
+ {
+ if (current is ICatchClauseOperation)
+ {
+ return true;
+ }
+
+ current = current.Parent;
+ }
+
+ return false;
+ }
+}
diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
index d7f58d9e67..15b809366e 100644
--- a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
+++ b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
@@ -62,4 +62,5 @@ internal static class DiagnosticIds
public const string IgnoreStringMethodReturnValueRuleId = "MSTEST0055";
public const string TestMethodAttributeShouldSetDisplayNameCorrectlyRuleId = "MSTEST0056";
public const string TestMethodAttributeShouldPropagateSourceInformationRuleId = "MSTEST0057";
+ public const string AvoidAssertsInCatchBlocksRuleId = "MSTEST0058";
}
diff --git a/src/Analyzers/MSTest.Analyzers/Resources.resx b/src/Analyzers/MSTest.Analyzers/Resources.resx
index 0c8a17d969..40e48ac0d5 100644
--- a/src/Analyzers/MSTest.Analyzers/Resources.resx
+++ b/src/Analyzers/MSTest.Analyzers/Resources.resx
@@ -684,4 +684,13 @@ The type declaring these methods should also respect the following rules:
Methods like Contains, StartsWith, and EndsWith return boolean values that indicate whether the condition was met. Ignoring these return values is likely a mistake.
+
+ Do not use asserts in catch blocks
+
+
+ Do not use asserts in catch blocks because they may not fail the test if no exception is thrown
+
+
+ Using asserts in catch blocks is problematic because the test will pass even if no exception is thrown and the catch block is never executed. Use Assert.ThrowsException or Assert.ThrowsExceptionAsync to verify that an exception is thrown, and then make additional assertions on the caught exception outside the try-catch block.
+
\ No newline at end of file
diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
index ba62836f66..a2a6cff806 100644
--- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
+++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
@@ -989,6 +989,21 @@ Typ deklarující tyto metody by měl také respektovat následující pravidla:
Parametr Assert.Throws by měl obsahovat jenom jeden příkaz nebo výraz
+
+ Do not use asserts in catch blocks
+ Do not use asserts in catch blocks
+
+
+
+ Do not use asserts in catch blocks because they may not fail the test if no exception is thrown
+ Do not use asserts in catch blocks because they may not fail the test if no exception is thrown
+
+
+
+ Using asserts in catch blocks is problematic because the test will pass even if no exception is thrown and the catch block is never executed. Use Assert.ThrowsException or Assert.ThrowsExceptionAsync to verify that an exception is thrown, and then make additional assertions on the caught exception outside the try-catch block.
+ Using asserts in catch blocks is problematic because the test will pass even if no exception is thrown and the catch block is never executed. Use Assert.ThrowsException or Assert.ThrowsExceptionAsync to verify that an exception is thrown, and then make additional assertions on the caught exception outside the try-catch block.
+
+