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