From 4a27e078aed35497fad65e791c439c047a186a47 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 10 May 2026 21:22:12 +0000 Subject: [PATCH 1/2] chore(deps): update dependency nunit to 4.6.0 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 2e189f40f7..8f7316ef11 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -71,7 +71,7 @@ - + From 59b1597963c8bb8fb43bbac44c4d9fecba739da3 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 10 May 2026 22:49:23 +0100 Subject: [PATCH 2/2] fix(analyzers): bind framework calls even when overload resolution fails NUnit 4.6 removed `Assert.Throws`, `Assert.ThrowsAsync`, and `Assert.DoesNotThrow(Action)`. Roslyn returns null `Symbol` for those invocations, so `BaseMigrationAnalyzer.HasFrameworkTypes` fell through to the syntactic fallback which intentionally excludes the shared `Assert` name. The migration diagnostic then dropped from the class header to the next `[Test]` attribute. Try `CandidateSymbols` and the receiver `INamedTypeSymbol` before the syntax-only fallback. Two MSTest tests had explicit workaround comments for the same root cause; their markers now flag the class declaration. --- .../MSTestMigrationAnalyzerTests.cs | 13 +++----- .../Migrators/Base/BaseMigrationAnalyzer.cs | 33 +++++++++++-------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs b/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs index 6080c9986d..6d527aa3b1 100644 --- a/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs @@ -719,16 +719,13 @@ public async Task TestWithMessages() [Test] public async Task MSTest_Assertions_With_FormatStrings_Converted() { - // Note: The diagnostic is on [TestMethod] because Assert.AreEqual with format strings - // isn't a valid MSTest overload, so semantic model doesn't resolve it. - // The analyzer detects the method attribute instead of the Assert call. await CodeFixer.VerifyCodeFixAsync( """ using Microsoft.VisualStudio.TestTools.UnitTesting; - public class MyClass + {|#0:public class MyClass|} { - {|#0:[TestMethod]|} + [TestMethod] public void TestWithFormatStrings() { int x = 5; @@ -761,16 +758,14 @@ public async Task MSTest_Assertions_With_Comparer_AddsTodoComment() { // When a comparer is detected (via semantic or syntax-based detection), // a TODO comment is added explaining that TUnit uses different comparison semantics. - // Note: The diagnostic is on [TestMethod] because Assert.AreEqual with comparer - // isn't a valid MSTest overload, so semantic model doesn't resolve it. await CodeFixer.VerifyCodeFixAsync( """ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; - public class MyClass + {|#0:public class MyClass|} { - {|#0:[TestMethod]|} + [TestMethod] public void TestWithComparer() { var comparer = StringComparer.OrdinalIgnoreCase; diff --git a/TUnit.Analyzers/Migrators/Base/BaseMigrationAnalyzer.cs b/TUnit.Analyzers/Migrators/Base/BaseMigrationAnalyzer.cs index ff0ede550d..cf833aecbf 100644 --- a/TUnit.Analyzers/Migrators/Base/BaseMigrationAnalyzer.cs +++ b/TUnit.Analyzers/Migrators/Base/BaseMigrationAnalyzer.cs @@ -208,7 +208,10 @@ protected virtual bool HasFrameworkTypes(SyntaxNodeAnalysisContext context, INam foreach (var invocation in invocationExpressions) { var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation); - if (symbolInfo.Symbol is IMethodSymbol methodSymbol) + var methodSymbol = symbolInfo.Symbol as IMethodSymbol + ?? symbolInfo.CandidateSymbols.OfType().FirstOrDefault(); + + if (methodSymbol != null) { var namespaceName = methodSymbol.ContainingNamespace?.ToDisplayString(); @@ -228,22 +231,24 @@ protected virtual bool HasFrameworkTypes(SyntaxNodeAnalysisContext context, INam return true; } } - else if (symbolInfo.Symbol == null) + else if (invocation.Expression is MemberAccessExpressionSyntax memberAccess) { - // Fallback: if symbol resolution fails completely, check the syntax directly - // This handles cases where the semantic model hasn't fully resolved types - // Note: If TUnit is available, we already returned false above, so this only - // runs when TUnit is not present (pure source framework project). - if (invocation.Expression is MemberAccessExpressionSyntax memberAccess) + // Method symbol couldn't be resolved (e.g. overload removed across framework + // versions). Try the receiver: if it resolves to a framework type, treat the + // call as framework usage. + var receiverSymbol = context.SemanticModel.GetSymbolInfo(memberAccess.Expression).Symbol; + if (receiverSymbol is INamedTypeSymbol receiverType && IsFrameworkType(receiverType)) { - var typeExpression = memberAccess.Expression.ToString(); + return true; + } - // For framework-specific types, only flag if the framework is still available - // This prevents flagging after migration when the framework assembly has been removed - if (IsFrameworkTypeName(typeExpression) && IsFrameworkAvailable(context.SemanticModel.Compilation)) - { - return true; - } + // Final fallback: pure syntactic match for framework-specific type names. + // Only flag when the framework assembly is still available, to avoid false + // positives after migration has removed it. + var typeExpression = memberAccess.Expression.ToString(); + if (IsFrameworkTypeName(typeExpression) && IsFrameworkAvailable(context.SemanticModel.Compilation)) + { + return true; } } }