File: CallAnalysis\LegacyLoggingTests.cs
Web Access
Project: src\test\Analyzers\Microsoft.Analyzers.Extra.Tests\Microsoft.Analyzers.Extra.Tests.csproj (Microsoft.Analyzers.Extra.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.ExtraAnalyzers.Test;
using Microsoft.Extensions.Logging;
using Xunit;
 
namespace Microsoft.Extensions.ExtraAnalyzers.CallAnalysis.Test;
 
public static partial class LegacyLoggingTests
{
    [Fact]
    public static async Task TargetClassInNamespace()
    {
        const string OriginalTarget = @"
                namespace Example
                {
                    public static partial class Log
                    {
                    }
                }
                ";
 
        const string OriginalSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.LogTrace(""Hello"");
                        }
                    }
                }
                ";
 
        const string ExpectedSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.Hello();
                        }
                    }
                }
                ";
        const string ExpectedTarget = @"
                namespace Example
                {
                    public static partial class Log
                    {
 
        [Microsoft.Extensions.Logging.LoggerMessage(0, Microsoft.Extensions.Logging.LogLevel.Trace, ""Hello"")]
        internal static partial void Hello(this Microsoft.Extensions.Logging.ILogger logger);
    }
                }
                ";
 
        var l = await RoslynTestUtils.RunAnalyzerAndFixer(
            new CallAnalyzer(),
            new LegacyLoggingFixer(),
            new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! },
            new[] { OriginalSource, OriginalTarget },
            defaultNamespace: "Example");
 
        var actualSource = l[0];
        var actualTarget = l[1];
 
        Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource);
        Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget);
    }
 
    [Fact]
    public static async Task UsesExistingParameterNamesAsArgs()
    {
        const string OriginalTarget = @"
                namespace Example
                {
                    public static partial class Log
                    {
                    }
                }
                ";
 
        const string OriginalSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class MyObject
                    {
                        public string MyProperty { get; set; } = ""This is a test"";
                    }
 
                    public class TestClass
                    {
                        private ILogger logger;
                        public TestClass(ILogger logger)
                        {
                            this.logger = logger;
                        }
 
                        public void Test()
                        {
                            var myObject = new MyObject();
                            var logger = new Logger<TestClass>(this.logger);
                            this.logger.LogTrace($""Hello {myObject.MyProperty}"");
                        }
                    }
                }
                ";
 
        const string ExpectedSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class MyObject
                    {
                        public string MyProperty { get; set; } = ""This is a test"";
                    }
 
                    public class TestClass
                    {
                        private ILogger logger;
                        public TestClass(ILogger logger)
                        {
                            this.logger = logger;
                        }
 
                        public void Test()
                        {
                            var myObject = new MyObject();
                            var logger = new Logger<TestClass>(this.logger);
                            this.logger.HelloMyProperty(myObject.MyProperty);
                        }
                    }
                }
                ";
        const string ExpectedTarget = @"
                namespace Example
                {
                    public static partial class Log
                    {
 
        [Microsoft.Extensions.Logging.LoggerMessage(0, Microsoft.Extensions.Logging.LogLevel.Trace, ""Hello {myProperty}"")]
        internal static partial void HelloMyProperty(this Microsoft.Extensions.Logging.ILogger logger, string myProperty);
    }
                }
                ";
 
        var l = await RoslynTestUtils.RunAnalyzerAndFixer(
            new CallAnalyzer(),
            new LegacyLoggingFixer(),
            new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! },
            new[] { OriginalSource, OriginalTarget },
            defaultNamespace: "Example");
 
        var actualSource = l[0];
        var actualTarget = l[1];
 
        Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource);
        Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget);
    }
 
    [Fact]
    public static async Task TargetClassDoesntExistWithNamespace()
    {
        const string OriginalSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.LogTrace(""Hello"");
                        }
                    }
                }
                ";
 
        const string ExpectedSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.Hello();
                        }
                    }
                }
                ";
        const string ExpectedTarget = @"
namespace Example
{
#pragma warning disable CS8019
    using Microsoft.Extensions.Logging;
    using System;
#pragma warning restore CS8019
 
    static partial class Log
    {
 
        [LoggerMessage(0, LogLevel.Trace, ""Hello"")]
        internal static partial void Hello(this ILogger logger);
    }
}
";
 
        var l = await RoslynTestUtils.RunAnalyzerAndFixer(
            new CallAnalyzer(),
            new LegacyLoggingFixer(),
            new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! },
            new[] { OriginalSource },
            extraFile: "Log.cs",
            defaultNamespace: "Example");
 
        var actualSource = l[0];
        var actualTarget = l[1];
 
        Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource);
        Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget);
    }
 
    [Fact]
    public static async Task TargetClassDoesntExistWithFileScopedNamespace()
    {
        const string OriginalTarget = @"
            namespace Example;
 
            public static partial class Log
            {
            }";
 
        const string OriginalSource = @"
            using Microsoft.Extensions.Logging;
 
            namespace Example;
 
            public class TestClass
            {
                public void Test(ILogger logger)
                {
                    logger.LogTrace(""Hello"");
                }
            }";
 
        const string ExpectedSource = @"
            using Microsoft.Extensions.Logging;
 
            namespace Example;
 
            public class TestClass
            {
                public void Test(ILogger logger)
                {
                    logger.Hello();
                }
            }";
 
        const string ExpectedTarget = @"
            namespace Example;
 
            public static partial class Log
            {
 
    [Microsoft.Extensions.Logging.LoggerMessage(0, Microsoft.Extensions.Logging.LogLevel.Trace, ""Hello"")]
    internal static partial void Hello(this Microsoft.Extensions.Logging.ILogger logger);
}";
 
        var l = await RoslynTestUtils.RunAnalyzerAndFixer(
            new CallAnalyzer(),
            new LegacyLoggingFixer(),
            new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! },
            new[] { OriginalSource, OriginalTarget },
            defaultNamespace: "Example");
 
        var actualSource = l[0];
        var actualTarget = l[1];
 
        Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource);
        Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget);
    }
 
    [Fact]
    public static async Task TargetClassExistWithDeepNamespace()
    {
        const string OriginalTarget = @"
                namespace Example
                {
                    namespace Example2
                    {
                        static partial class Log
                        {
                        }
                    }
                }
                ";
 
        const string OriginalSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.LogTrace(""Hello"");
                        }
                    }
                }
                ";
 
        const string ExpectedSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.Hello();
                        }
                    }
                }
                ";
 
        const string ExpectedTarget = @"
                namespace Example
                {
                    namespace Example2
                    {
                        static partial class Log
                        {
 
            [Microsoft.Extensions.Logging.LoggerMessage(0, Microsoft.Extensions.Logging.LogLevel.Trace, ""Hello"")]
            internal static partial void Hello(this Microsoft.Extensions.Logging.ILogger logger);
        }
                    }
                }
                ";
 
        var l = await RoslynTestUtils.RunAnalyzerAndFixer(
            new CallAnalyzer(),
            new LegacyLoggingFixer(),
            new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! },
            new[] { OriginalSource, OriginalTarget },
            defaultNamespace: "Example.Example2");
 
        var actualSource = l[0];
        var actualTarget = l[1];
 
        Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource);
        Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget);
    }
 
    [Fact]
    public static async Task TargetClassDoesntExistInNestedType()
    {
        const string OriginalTarget = @"
                namespace Example
                {
                    class Container
                    {
                        static partial class Log
                        {
                        }
                    }
                }
                ";
 
        const string OriginalSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.LogTrace(""Hello"");
                        }
                    }
                }
                ";
 
        const string ExpectedSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.Hello();
                        }
                    }
                }
                ";
 
        const string ExpectedTarget = @"
namespace Example.Example2
{
#pragma warning disable CS8019
    using Microsoft.Extensions.Logging;
    using System;
#pragma warning restore CS8019
 
    static partial class Log
    {
 
        [LoggerMessage(0, LogLevel.Trace, ""Hello"")]
        internal static partial void Hello(this ILogger logger);
    }
}
";
 
        var l = await RoslynTestUtils.RunAnalyzerAndFixer(
            new CallAnalyzer(),
            new LegacyLoggingFixer(),
            new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! },
            new[] { OriginalSource, OriginalTarget },
            extraFile: "Log.cs",
            defaultNamespace: "Example.Example2");
 
        var actualSource = l[0];
        var actualTarget = l[2];
 
        Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource);
        Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget);
    }
 
    [Fact]
    public static async Task TargetClassHasMethodsWithoutEventId()
    {
        const string OriginalTarget = @"
                namespace Example
                {
                    public static partial class Log
                    {
                        [Microsoft.Extensions.Logging.LoggerMessage(Microsoft.Extensions.Logging.LogLevel.Debug, message: ""Test"")]
                        internal static partial void WithoutEvent(Microsoft.Extensions.Logging.ILogger logger);
                    }
                }";
 
        const string OriginalSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        private ILogger _logger;
 
                        public TestClass(ILogger logger)
                        {
                            _logger = logger;
                        }
 
                        public void Test()
                        {
                            _logger.LogTrace(""Hello!"");
                        }
                    }
                }";
 
        const string ExpectedSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        private ILogger _logger;
 
                        public TestClass(ILogger logger)
                        {
                            _logger = logger;
                        }
 
                        public void Test()
                        {
                            _logger.Hello();
                        }
                    }
                }";
 
        const string ExpectedTarget = @"
                namespace Example
                {
                    public static partial class Log
                    {
                        [Microsoft.Extensions.Logging.LoggerMessage(Microsoft.Extensions.Logging.LogLevel.Debug, message: ""Test"")]
                        internal static partial void WithoutEvent(Microsoft.Extensions.Logging.ILogger logger);
 
        [Microsoft.Extensions.Logging.LoggerMessage(0, Microsoft.Extensions.Logging.LogLevel.Trace, ""Hello!"")]
        internal static partial void Hello(this Microsoft.Extensions.Logging.ILogger logger);
    }
                }";
 
        var l = await RoslynTestUtils.RunAnalyzerAndFixer(
            new CallAnalyzer(),
            new LegacyLoggingFixer(),
            new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! },
            new[] { OriginalSource, OriginalTarget },
            defaultNamespace: "Example");
 
        var actualSource = l[0];
        var actualTarget = l[1];
 
        Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource);
        Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget);
    }
 
    [Fact]
    public static async Task TargetClassHasMethodsWithAndWithoutEventId()
    {
        const string OriginalTarget = @"
                namespace Example
                {
                    public static partial class Log
                    {
                        [Microsoft.Extensions.Logging.LoggerMessage(message: ""Test"")]
                        internal static partial void WithoutEvent(Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.LogLevel level);
 
                        [Microsoft.Extensions.Logging.LoggerMessage(1, Microsoft.Extensions.Logging.LogLevel.Debug, ""Test"")]
                        internal static partial void WithEvent(Microsoft.Extensions.Logging.ILogger logger, string param1);
                    }
                }";
 
        const string OriginalSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        private ILogger _logger;
 
                        public TestClass(ILogger logger)
                        {
                            _logger = logger;
                        }
 
                        public void Test()
                        {
                            _logger.LogTrace(""Hello!"");
                        }
                    }
                }";
 
        const string ExpectedSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        private ILogger _logger;
 
                        public TestClass(ILogger logger)
                        {
                            _logger = logger;
                        }
 
                        public void Test()
                        {
                            _logger.Hello();
                        }
                    }
                }";
 
        const string ExpectedTarget = @"
                namespace Example
                {
                    public static partial class Log
                    {
                        [Microsoft.Extensions.Logging.LoggerMessage(message: ""Test"")]
                        internal static partial void WithoutEvent(Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.LogLevel level);
 
                        [Microsoft.Extensions.Logging.LoggerMessage(1, Microsoft.Extensions.Logging.LogLevel.Debug, ""Test"")]
                        internal static partial void WithEvent(Microsoft.Extensions.Logging.ILogger logger, string param1);
 
        [Microsoft.Extensions.Logging.LoggerMessage(2, Microsoft.Extensions.Logging.LogLevel.Trace, ""Hello!"")]
        internal static partial void Hello(this Microsoft.Extensions.Logging.ILogger logger);
    }
                }";
 
        var l = await RoslynTestUtils.RunAnalyzerAndFixer(
            new CallAnalyzer(),
            new LegacyLoggingFixer(),
            new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! },
            new[] { OriginalSource, OriginalTarget },
            defaultNamespace: "Example");
 
        var actualSource = l[0];
        var actualTarget = l[1];
 
        Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource);
        Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget);
    }
 
    [Fact]
    public static async Task DuplicateFilename()
    {
        const string OriginalTarget = @"
                namespace Example
                {
                }
                ";
 
        const string OriginalSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.LogTrace(""Hello"");
                        }
                    }
                }
                ";
 
        const string ExpectedSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.Hello();
                        }
                    }
                }
                ";
 
        const string ExpectedTarget = @"
namespace Example
{
#pragma warning disable CS8019
    using Microsoft.Extensions.Logging;
    using System;
#pragma warning restore CS8019
 
    static partial class Log
    {
 
        [LoggerMessage(0, LogLevel.Trace, ""Hello"")]
        internal static partial void Hello(this ILogger logger);
    }
}
";
 
        var l = await RoslynTestUtils.RunAnalyzerAndFixer(
            new CallAnalyzer(),
            new LegacyLoggingFixer(),
            new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! },
            new[] { OriginalSource, OriginalTarget },
            sourceNames: new[] { "primary.cs", "Log.cs" },
            extraFile: "Log2.cs",
            defaultNamespace: "Example");
 
        var actualSource = l[0];
        var actualTarget = l[2];
 
        Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource);
        Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget);
    }
 
    [Fact]
    public static async Task MissingMetadata()
    {
        const string OriginalTarget = @"
                namespace Example
                {
                    public static partial class Log
                    {
                    }
                }
                ";
 
        const string OriginalSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.LogTrace(""Hello"");
                        }
                    }
                }
                ";
 
        const string ExpectedSource = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public void Test(ILogger logger)
                        {
                            logger.Hello();
                        }
                    }
                }
                ";
 
        const string ExpectedTarget = @"
                namespace Example
                {
                    public static partial class Log
                    {
 
        [Microsoft.Extensions.Logging.LoggerMessage(1, Microsoft.Extensions.Logging.LogLevel.Trace, ""Hello"")]
        internal static partial void Hello(this Microsoft.Extensions.Logging.ILogger logger);
    }
                }
                ";
 
        var l = await RoslynTestUtils.RunAnalyzerAndFixer(
            new CallAnalyzer(),
            new LegacyLoggingFixer
            {
                GetTypeByMetadataName3 = (_, _) => null,
            },
            new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! },
            new[] { OriginalSource, OriginalTarget },
            defaultNamespace: "Example");
 
        var actualSource = l[0];
        var actualTarget = l[1];
 
        Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource);
        Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget);
    }
 
    [Fact]
    public static void UtilityMethods()
    {
        var f = new LegacyLoggingFixer();
        Assert.Single(f.FixableDiagnosticIds);
        Assert.Equal(DiagDescriptors.LegacyLogging.Id, f.FixableDiagnosticIds[0]);
        Assert.Null(f.GetFixAllProvider());
    }
 
    [Fact]
    public static async Task FailureModes()
    {
        const string TargetSourceCode = @"
                using Microsoft.Extensions.Logging;
                using System;
                using System.Runtime.CompilerServices;
 
                namespace Example
                {
                    namespace Example2
                    {
                        /*0+*/static partial class Log/*-0*/
                        {
                            [Microsoft.Extensions.Telemetry.LoggerMessage(0, LogLevel.Trace, ""Hello"")]
                            internal static void Hello(ILogger logger) {}
 
                            [Obsolete]
                            [MethodImpl(MethodImplOptions.AggressiveInlining)]
                            internal static void World() {}
                        }
                    }
                }
                ";
 
        const string InvocationSourceCode = @"
                using Microsoft.Extensions.Logging;
 
                namespace Example
                {
                    public class TestClass
                    {
                        public static void TestMethod(ILogger logger)
                        {
                            /*0+*/logger.LogTrace(""TestA"")/*-0*/;
                        }
                    }
                }
                ";
 
        var proj = RoslynTestUtils
            .CreateTestProject(new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! })
                .WithDocument("target.cs", TargetSourceCode)
                .WithDocument("invocation.cs", InvocationSourceCode);
 
        proj.CommitChanges();
        var targetDoc = proj.FindDocument("target.cs");
        var targetRoot = await targetDoc.GetSyntaxRootAsync(CancellationToken.None);
        var targetClass = targetRoot!.FindNode(RoslynTestUtils.MakeTextSpan(TargetSourceCode, 0)) as ClassDeclarationSyntax;
        var invocationDoc = proj.FindDocument("invocation.cs");
 
        // make sure this works normally
        var f = new LegacyLoggingFixer();
        var (invocationExpression, details) = await f.CheckIfCanFixAsync(
            invocationDoc,
            InvocationSourceCode.MakeTextSpan(0),
            CancellationToken.None);
 
        Assert.NotNull(invocationExpression);
        Assert.NotNull(details);
 
        var (methodName, existing) = await f.GetFinalTargetMethodNameAsync(
            targetDoc,
            targetClass!,
            invocationDoc,
            invocationExpression!,
            details!,
            CancellationToken.None);
 
        Assert.Equal("TestA", methodName);
        Assert.False(existing);
 
        f = new LegacyLoggingFixer
        {
            GetSyntaxRootAsync = (_, _) => Task.FromResult<SyntaxNode?>(null)
        };
 
        (invocationExpression, details) = await f.CheckIfCanFixAsync(
            invocationDoc,
            RoslynTestUtils.MakeTextSpan(InvocationSourceCode, 0),
            CancellationToken.None);
 
        Assert.Null(invocationExpression);
        Assert.Null(details);
 
        f = new LegacyLoggingFixer
        {
            GetSemanticModelAsync = (_, _) => Task.FromResult<SemanticModel?>(null)
        };
 
        (invocationExpression, details) = await f.CheckIfCanFixAsync(
            invocationDoc,
            RoslynTestUtils.MakeTextSpan(InvocationSourceCode, 0),
            CancellationToken.None);
 
        Assert.Null(invocationExpression);
        Assert.Null(details);
 
        f = new LegacyLoggingFixer
        {
            GetOperation = (_, _, _) => null
        };
 
        (invocationExpression, details) = await f.CheckIfCanFixAsync(
            invocationDoc,
            RoslynTestUtils.MakeTextSpan(InvocationSourceCode, 0),
            CancellationToken.None);
 
        Assert.Null(invocationExpression);
        Assert.Null(details);
 
        f = new LegacyLoggingFixer
        {
            GetDeclaredSymbol = (_, _, _) => null
        };
 
        (invocationExpression, details) = await f.CheckIfCanFixAsync(
            invocationDoc,
            RoslynTestUtils.MakeTextSpan(InvocationSourceCode, 0),
            CancellationToken.None);
 
        Assert.NotNull(invocationExpression);
        Assert.NotNull(details);
 
        (methodName, existing) = await f.GetFinalTargetMethodNameAsync(
            targetDoc,
            targetClass!,
            invocationDoc,
            invocationExpression!,
            details!,
            CancellationToken.None);
 
        Assert.Equal("TestA", methodName);
        Assert.False(existing);
 
        f = new LegacyLoggingFixer
        {
            GetTypeByMetadataName1 = (_, _) => null
        };
 
        (invocationExpression, details) = await f.CheckIfCanFixAsync(
            invocationDoc,
            RoslynTestUtils.MakeTextSpan(InvocationSourceCode, 0),
            CancellationToken.None);
 
        Assert.Null(invocationExpression);
        Assert.Null(details);
 
        f = new LegacyLoggingFixer
        {
            GetTypeByMetadataName2 = (_, _) => null
        };
 
        (invocationExpression, details) = await f.CheckIfCanFixAsync(
            invocationDoc,
            RoslynTestUtils.MakeTextSpan(InvocationSourceCode, 0),
            CancellationToken.None);
 
        Assert.NotNull(invocationExpression);
        Assert.NotNull(details);
 
        (methodName, existing) = await f.GetFinalTargetMethodNameAsync(
            targetDoc,
            targetClass!,
            invocationDoc,
            invocationExpression!,
            details!,
            CancellationToken.None);
 
        Assert.Equal("TestA", methodName);
        Assert.False(existing);
    }
}