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 @@
-
+
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;
}
}
}