File: Completion\CompletionProviders\ExtensionMethodImportCompletionProviderTests.cs
Web Access
Project: src\src\EditorFeatures\CSharpTest\Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Completion.Providers;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders;
 
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.Completion)]
public class ExtensionMethodImportCompletionProviderTests : AbstractCSharpCompletionProviderTests
{
    public ExtensionMethodImportCompletionProviderTests()
    {
        ShowImportCompletionItemsOptionValue = true;
        ForceExpandedCompletionIndexCreation = true;
    }
 
    internal override Type GetCompletionProviderType()
        => typeof(ExtensionMethodImportCompletionProvider);
 
    public enum ReferenceType
    {
        None,
        Project,
        Metadata
    }
 
    private static IEnumerable<object[]> CombineWithReferenceTypeData(IEnumerable<List<object>> data)
    {
        foreach (var refKind in Enum.GetValues(typeof(ReferenceType)))
        {
            foreach (var d in data)
            {
                d.Add(refKind);
                yield return d.ToArray();
            }
        }
    }
 
    public static IEnumerable<object[]> ReferenceTypeData
        => (new[] { ReferenceType.None, ReferenceType.Project, ReferenceType.Metadata }).Select(refType => new[] { (object)refType });
 
    public static IEnumerable<object[]> AllTypeKindsWithReferenceTypeData
        => CombineWithReferenceTypeData((new[] { "class", "struct", "interface", "enum", "abstract class" }).Select(kind => new List<object>() { kind }));
 
    private static IEnumerable<List<object>> BuiltInTypes
    {
        get
        {
            var predefinedTypes = new List<string>() { "string", "String", "System.String" };
            var arraySuffixes = new[] { "", "[]", "[,]" };
 
            foreach (var type1 in predefinedTypes)
            {
                foreach (var type2 in predefinedTypes)
                {
                    foreach (var suffix in arraySuffixes)
                    {
                        yield return new List<object>() { type1 + suffix, type2 + suffix };
                    }
                }
            }
        }
    }
 
    private static string GetMarkup(string current, string referenced, ReferenceType refType,
                                    string currentLanguage = LanguageNames.CSharp,
                                    string referencedLanguage = LanguageNames.CSharp)
        => refType switch
        {
            ReferenceType.None => CreateMarkupForSingleProject(current, referenced, currentLanguage),
            ReferenceType.Project => GetMarkupWithReference(current, referenced, currentLanguage, referencedLanguage, true),
            ReferenceType.Metadata => GetMarkupWithReference(current, referenced, currentLanguage, referencedLanguage, false),
            _ => null,
        };
 
    public static IEnumerable<object[]> BuiltInTypesWithReferenceTypeData
        => CombineWithReferenceTypeData(BuiltInTypes);
 
    [MemberData(nameof(BuiltInTypesWithReferenceTypeData))]
    [Theory]
    public async Task TestPredefinedType(string type1, string type2, ReferenceType refType)
    {
        var file1 = $@"
using System;
 
namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod(this {type1} x)
            => true;
    }}
}}";
        var file2 = $@"
using System;
 
namespace Baz
{{
    public class Bat
    {{
        public void M({type2} x)
        {{
            x.$$
        }}
    }}
}}";
 
        var markup = GetMarkup(file2, file1, refType);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [MemberData(nameof(ReferenceTypeData))]
    [Theory]
    public async Task UsingAliasInDeclaration(ReferenceType refType)
    {
        var file1 = """
            using System;
            using MyInt = System.Int32;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod(this MyInt x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [MemberData(nameof(ReferenceTypeData))]
    [Theory]
    public async Task UsingAliasInDeclaration_PrimitiveType(ReferenceType refType)
    {
        var file1 = """
            using System;
            using MyInt = System.Int32;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod(this MyInt x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [MemberData(nameof(ReferenceTypeData))]
    [Theory]
    public async Task UsingAliasInDeclaration_RegularType(ReferenceType refType)
    {
        var file1 = """
            using System;
            using MyAlias = System.Exception;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod(this MyAlias x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(Exception x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [MemberData(nameof(ReferenceTypeData))]
    [Theory]
    public async Task UsingAliasInDeclaration_GenericType(ReferenceType refType)
    {
        var file1 = """
            using System;
            using MyAlias = System.Collections.Generic.List<int>;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod(this MyAlias x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(System.Collections.Generic.List<int> x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [MemberData(nameof(ReferenceTypeData))]
    [Theory]
    public async Task UsingAliasInDeclaration_RegularTypeWithSameSimpleName(ReferenceType refType)
    {
        var file1 = """
            using DataTime = System.Exception;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod(this System.DateTime x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(DateTime x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [MemberData(nameof(ReferenceTypeData))]
    [Theory]
    public async Task UsingAliasInDeclaration_Namespace(ReferenceType refType)
    {
        var file1 = """
            using System;
            using GenericCollection = System.Collections.Generic;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod<T>(this GenericCollection.List<T> x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(System.Collections.Generic.List<int> x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             displayTextSuffix: "<>",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [MemberData(nameof(ReferenceTypeData))]
    [Theory]
    public async Task UsingAliasInUsage(ReferenceType refType)
    {
        var file1 = """
            using System;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod(this int x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
            using MyInt = System.Int32;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(MyInt x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [MemberData(nameof(AllTypeKindsWithReferenceTypeData))]
    [Theory]
    public async Task RegularType(string typeKind, ReferenceType refType)
    {
        var file1 = $@"
using System;
 
public {typeKind} MyType {{ }}
 
namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod(this MyType t)
            => true;
    }}
}}";
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(MyType x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [MemberData(nameof(AllTypeKindsWithReferenceTypeData))]
    [Theory]
    public async Task ObjectType(string typeKind, ReferenceType refType)
    {
        var file1 = $@"
using System;
 
public {typeKind} MyType {{ }}
 
namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod(this object t)
            => true;
    }}
}}";
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(MyType x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    public static IEnumerable<object[]> TupleWithRefTypeData => CombineWithReferenceTypeData(
        (new[]
        {
            "(int, int)",
            "(int, (int, int))",
            "(string a, string b)"
        }).Select(tuple => new List<object>() { tuple }));
 
    [MemberData(nameof(TupleWithRefTypeData))]
    [Theory]
    public async Task ValueTupleType(string tupleType, ReferenceType refType)
    {
        var file1 = $@"
using System;
 
namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod(this {tupleType} t)
            => true;
    }}
}}";
        var file2 = $@"
using System;
 
namespace Baz
{{
    public class Bat
    {{
        public void M({tupleType} x)
        {{
            x.$$
        }}
    }}
}}";
        var markup = GetMarkup(file2, file1, refType);
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    public static IEnumerable<object[]> DerivableTypeKindsWithReferenceTypeData
        => CombineWithReferenceTypeData((new[] { "class", "interface", "abstract class" }).Select(kind => new List<object>() { kind }));
 
    [MemberData(nameof(DerivableTypeKindsWithReferenceTypeData))]
    [Theory]
    public async Task RegularTypeAsBase(string baseType, ReferenceType refType)
    {
        var file1 = $@"
using System;
 
public {baseType} MyBase {{ }}
 
public class MyType : MyBase {{ }}
 
namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod(this MyBase t)
            => true;
    }}
}}";
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(MyType x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    public static IEnumerable<object[]> BounedGenericTypeWithRefTypeData => CombineWithReferenceTypeData(
        (new[]
        {
            "IEnumerable<string>",
            "List<string>",
            "string[]"
        }).Select(tuple => new List<object>() { tuple }));
 
    [MemberData(nameof(BounedGenericTypeWithRefTypeData))]
    [Theory]
    public async Task BoundedGenericType(string type, ReferenceType refType)
    {
        var file1 = """
            using System;
            using System.Collections.Generic;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod(this IEnumerable<string> t)
                        => true;
                }
            }
            """;
        var file2 = $@"
using System;
using System.Collections.Generic;
 
namespace Baz
{{
    public class Bat
    {{
        public void M({type} x)
        {{
            x.$$
        }}
    }}
}}";
        var markup = GetMarkup(file2, file1, refType);
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
    public static IEnumerable<object[]> TypeParameterWithRefTypeData => CombineWithReferenceTypeData(
        (new[]
        {
            "IEnumerable<string>",
            "int",
            "Bat",
            "Bat"
        }).Select(tuple => new List<object>() { tuple }));
 
    [MemberData(nameof(TypeParameterWithRefTypeData))]
    [Theory]
    public async Task MatchingTypeParameter(string type, ReferenceType refType)
    {
        var file1 = """
            using System;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod<T>(this T t)
                        => true;
                }
            }
            """;
        var file2 = $@"
using System;
using System.Collections.Generic;
 
namespace Baz
{{
    public interface Bar {{}}
 
    public class Bat
    {{
        public void M({type} x)
        {{
            x.$$
        }}
    }}
}}";
        var markup = GetMarkup(file2, file1, refType);
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             displayTextSuffix: "<>",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [InlineData(ReferenceType.Project)]
    [InlineData(ReferenceType.Metadata)]
    [Theory]
    public async Task TestInternalExtensionMethods_NoIVT_InReference(ReferenceType refType)
    {
        var file1 = """
            using System;
 
            namespace Foo
            {
                internal static class ExtensionClass
                {
                    public static bool ExtentionMethod(this int x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
 
        var markup = GetMarkup(file2, file1, refType);
        await VerifyImportItemIsAbsentAsync(
             markup,
             "ExtentionMethod",
             inlineDescription: "Foo");
    }
 
    [Fact]
    public async Task TestInternalExtensionMethods_NoIVT_InSameProject()
    {
        var file1 = """
            using System;
 
            namespace Foo
            {
                internal static class ExtensionClass
                {
                    internal static bool ExtentionMethod(this int x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
 
        var markup = GetMarkup(file2, file1, ReferenceType.None);
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodInternal,     // This is based on declared accessibility
             inlineDescription: "Foo");
    }
 
    // SymbolTreeInfo explicitly ignores non-public types from metadata(likely for perf reasons). So we don't need to test internals in PE reference
    [InlineData(ReferenceType.None)]
    [InlineData(ReferenceType.Project)]
    [Theory]
    public async Task TestInternalExtensionMethods_WithIVT(ReferenceType refType)
    {
        var file1 = """
            [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Project1")]
 
            namespace Foo
            {
                internal static class ExtensionClass
                {
                    internal static bool ExtentionMethod(this int x)
                        => true;
                }
            }
            """;
        var file2 = """
            namespace Baz
            {
                public class Bat
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
 
        var markup = GetMarkup(file2, file1, refType);
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodInternal,
             inlineDescription: "Foo");
    }
 
    [MemberData(nameof(ReferenceTypeData))]
    [Theory]
    public async Task UserDefinedGenericType(ReferenceType refType)
    {
        var file1 = """
            using System;
 
            public class MyGeneric<T>
            {
            }
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod(this MyGeneric<int> x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(MyGeneric<int> x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, refType);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [InlineData("(1 + 1)")]
    [InlineData("(new int())")]
    [Theory]
    public async Task MethodSymbolReceiver(string expression)
    {
        var file1 = """
            using System;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtentionMethod(this int x)
                        => true;
                }
            }
            """;
        var file2 = $@"
using System;
 
namespace Baz
{{
    public class Bat
    {{
        public void M()
        {{
            {expression}.$$
        }}
    }}
}}";
        var markup = GetMarkup(file2, file1, ReferenceType.None);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    public static IEnumerable<object[]> VBBuiltInTypes
    {
        get
        {
            var predefinedTypes = new List<(string vbType, string csType)>
            {
                ( "Boolean", "bool" ),
                ( "Byte", "byte" ),
                ( "Char", "char" ),
                ( "Date", "DateTime" ),
                ( "Integer", "int" ),
                ( "String", "string" ),
                ( "Object", "object" ),
                ( "Short", "short" ),
 
            };
 
            var arraySuffixes = new (string vbSuffix, string csSuffix)[] { ("", ""), ("()", "[]"), ("(,)", "[,]") };
 
            foreach (var type in predefinedTypes)
            {
                foreach (var suffix in arraySuffixes)
                {
                    yield return new object[] { type.vbType + suffix.vbSuffix, type.csType + suffix.csSuffix };
                }
            }
        }
    }
 
    [MemberData(nameof(VBBuiltInTypes))]
    [Theory]
    public async Task ExtensionMethodDelcaredInVBSource(string vbType, string csType)
    {
        var file1 = $@"
Imports System
Imports System.Runtime.CompilerServices
 
Namespace NS
    Public Module Foo
        <Extension>
        public Function ExtentionMethod(x As {vbType}) As Boolean
            Return True
        End Function
    End Module
End Namespace";
        var file2 = $@"
using System;
 
namespace Baz
{{
    public class Bat
    {{
        public void M({csType} x)
        {{
            x.$$
        }}
    }}
}}";
        var markup = GetMarkup(file2, file1, ReferenceType.Project, currentLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.VisualBasic);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "NS");
    }
 
    [Fact]
    public async Task ExtensionMethodDelcaredInRootNamespaceVBSource()
    {
        var file1 = """
            Imports System
            Imports System.Runtime.CompilerServices
 
            Public Module Foo
                <Extension>
                public Function ExtentionMethod(x As Integer) As Boolean
                    Return True
                End Function
            End Module
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootNamespace: "Root");
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Root");
    }
 
    [Fact]
    public async Task ExtensionMethodDelcaredInGlobalNamespaceVBSource()
    {
        var file1 = """
            Imports System
            Imports System.Runtime.CompilerServices
 
            Public Module Foo
                <Extension>
                public Function ExtentionMethod(x As Integer) As Boolean
                    Return True
                End Function
            End Module
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
        var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp);
 
        await VerifyImportItemIsAbsentAsync(
             markup,
             "ExtentionMethod",
             inlineDescription: "");
    }
 
    [Fact]
    public async Task TestTriggerLocation()
    {
        var file1 = """
            using System;
 
            namespace Foo
            {
                internal static class ExtensionClass
                {
                    internal static bool ExtentionMethod(this int x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(int x)
                    {
                        x.$$
                        var z = 10;
                    }
                }
            }
            """;
 
        var markup = GetMarkup(file2, file1, ReferenceType.None);
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod",
             glyph: (int)Glyph.ExtensionMethodInternal,     // This is based on declared accessibility
             inlineDescription: "Foo");
    }
 
    [InlineData("int", "Int32Method", "Foo")]
    [InlineData("string", "StringMethod", "Bar")]
    [Theory]
    public async Task TestIdenticalAliases(string type, string expectedMethodname, string expectedNamespace)
    {
        var file1 = """
            using X = System.String;
 
            namespace Foo
            {
                using X = System.Int32;
 
                internal static class ExtensionClass
                {
                    internal static bool Int32Method(this X x)
                        => true;
                }
            }
 
            namespace Bar
            {
                internal static class ExtensionClass
                {
                    internal static bool StringMethod(this X x)
                        => true;
                }
            }
            """;
        var file2 = $@"
using System;
 
namespace Baz
{{
    public class Bat
    {{
        public void M({type} x)
        {{
            x.$$
        }}
    }}
}}";
 
        var markup = GetMarkup(file2, file1, ReferenceType.None);
        await VerifyImportItemExistsAsync(
             markup,
             expectedMethodname,
             glyph: (int)Glyph.ExtensionMethodInternal,
             inlineDescription: expectedNamespace);
    }
 
    [InlineData("int")]
    [InlineData("Exception")]
    [Theory]
    public async Task TestIdenticalMethodName(string type)
    {
        var file1 = """
            using System;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtMethod(this int x)
                        => true;
 
                    public static bool ExtMethod(this Exception x)
                        => true;
                }
            }
            """;
        var file2 = $@"
using System;
 
namespace Baz
{{
    public class Bat
    {{
        public void M({type} x)
        {{
            x.$$
        }}
    }}
}}";
 
        var markup = GetMarkup(file2, file1, ReferenceType.None);
        await VerifyImportItemExistsAsync(
             markup,
             "ExtMethod",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [Fact]
    public async Task DoNotTriggerOnType()
    {
        var file1 = """
            using System;
 
            namespace Foo
            {
                public static class ExtensionClass
                {
                    public static bool ExtMethod(this string x)
                        => true;
                }
            }
            """;
        var file2 = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M()
                    {
                        string.$$
                    }
                }
            }
            """;
        var markup = GetMarkup(file2, file1, ReferenceType.None);
        await VerifyImportItemIsAbsentAsync(
             markup,
             "ExtMethod",
             inlineDescription: "Foo");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42325")]
    public async Task TestExtensionMethodInPartialClass()
    {
        var file1 = """
            using System;
 
            namespace Foo
            {
                public static partial class ExtensionClass
                {
                    public static bool ExtentionMethod1(this string x)
                        => true;
                }
            }
            """;
        var currentFile = """
            using System;
 
            namespace Foo
            {
                public static partial class ExtensionClass
                {
                    public static bool ExtentionMethod2(this string x)
                        => true;
                }
            }
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(string x)
                    {
                        x.$$
                    }
                }
            }
            """;
 
        var markup = CreateMarkupForSingleProject(currentFile, file1, LanguageNames.CSharp);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod1",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod2",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [InlineData(ReferenceType.Project, "public")]
    [InlineData(ReferenceType.Project, "internal")]
    [InlineData(ReferenceType.Metadata, "public")]  // We don't support internal extension method from non-source references.
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/42325")]
    public async Task TestExtensionMethodsInConflictingTypes(ReferenceType refType, string accessibility)
    {
        var refDoc = $@"
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")]
 
namespace Foo
{{
    {accessibility} static class ExtensionClass
    {{
        public static bool ExtentionMethod1(this int x)
            => true;
    }}
}}";
        var srcDoc = """
            using System;
 
            namespace Foo
            {
                internal static class ExtensionClass
                {
                    public static bool ExtentionMethod2(this int x)
                        => true;
                }
            }
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
 
        var markup = refType switch
        {
            ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            _ => null,
        };
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod1",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod2",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42325")]
    public async Task TestExtensionMethodsInConflictingTypesFromReferencedProjects()
    {
        var refDoc1 = """
            [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Project1")]
 
            namespace Foo
            {
                internal static class ExtensionClass
                {
                    public static bool ExtentionMethod1(this int x)
                        => true;
                }
            }
            """;
        var refDoc2 = """
            [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Project1")]
 
            namespace Foo
            {
                internal static class ExtensionClass
                {
                    public static bool ExtentionMethod2(this int x)
                        => true;
                }
            }
            """;
        var srcDoc = """
            using System;
 
            namespace Baz
            {
                public class Bat
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
 
        var markup = CreateMarkupForProjectWithMultupleProjectReferences(srcDoc, LanguageNames.CSharp, LanguageNames.CSharp, [refDoc1, refDoc2]);
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod1",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
 
        await VerifyImportItemExistsAsync(
             markup,
             "ExtentionMethod2",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [InlineData("", "", false)]
    [InlineData("", "public", true)]
    [InlineData("public", "", false)]
    [Theory]
    public async Task TestCSharpDefaultAccessibility(string containerAccessibility, string methodAccessibility, bool isAvailable)
    {
        var file1 = $@"
using System;
 
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")]
 
namespace Foo
{{
    {containerAccessibility} static class ExtensionClass
    {{
        {methodAccessibility} static bool ExtentionMethod(this int x)
            => true;
    }}
}}";
        var file2 = $@"
using System;
 
namespace Baz
{{
    public class Bat
    {{
        public void M(int x)
        {{
            x.$$
        }}
    }}
}}";
 
        var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference: true);
 
        if (isAvailable)
        {
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }
        else
        {
            await VerifyImportItemIsAbsentAsync(
                 markup,
                 "ExtentionMethod",
                 inlineDescription: "Foo");
        }
    }
 
    [InlineData(ReferenceType.Project, "[]", "ExtentionMethod2")]
    [InlineData(ReferenceType.Project, "[][]", "ExtentionMethod3")]
    [InlineData(ReferenceType.Project, "[,]", "ExtentionMethod4")]
    [InlineData(ReferenceType.Project, "[][,]", "ExtentionMethod5")]
    [InlineData(ReferenceType.Metadata, "[]", "ExtentionMethod2")]
    [InlineData(ReferenceType.Metadata, "[][]", "ExtentionMethod3")]
    [InlineData(ReferenceType.Metadata, "[,]", "ExtentionMethod4")]
    [InlineData(ReferenceType.Metadata, "[][,]", "ExtentionMethod5")]
    [Theory]
    public async Task TestExtensionMethodsForSimpleArrayType(ReferenceType refType, string rank, string expectedName)
    {
        var refDoc = $@"
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")]
 
namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod1(this int x)
            => true;
 
        public static bool ExtentionMethod2(this int[] x)
            => true;
 
        public static bool ExtentionMethod3(this int[][] x)
            => true;
 
        public static bool ExtentionMethod4(this int[,] x)
            => true;
 
        public static bool ExtentionMethod5(this int[][,] x)
            => true;
    }}
}}";
        var srcDoc = $@"
namespace Baz
{{
    public class Bat
    {{
        public void M(int{rank} x)
        {{
            x.$$
        }}
    }}
}}";
 
        var markup = refType switch
        {
            ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            _ => null,
        };
 
        await VerifyImportItemExistsAsync(
             markup,
             expectedName,
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [InlineData(ReferenceType.Project, "[]", "ExtentionMethod2")]
    [InlineData(ReferenceType.Project, "[][]", "ExtentionMethod3")]
    [InlineData(ReferenceType.Project, "[,]", "ExtentionMethod4")]
    [InlineData(ReferenceType.Project, "[][,]", "ExtentionMethod5")]
    [InlineData(ReferenceType.Metadata, "[]", "ExtentionMethod2")]
    [InlineData(ReferenceType.Metadata, "[][]", "ExtentionMethod3")]
    [InlineData(ReferenceType.Metadata, "[,]", "ExtentionMethod4")]
    [InlineData(ReferenceType.Metadata, "[][,]", "ExtentionMethod5")]
    [Theory]
    public async Task TestExtensionMethodsForGenericArrayType(ReferenceType refType, string rank, string expectedName)
    {
        var refDoc = $@"
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")]
 
namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod1<T>(this T x)
            => true;
 
        public static bool ExtentionMethod2<T>(this T[] x)
            => true;
 
        public static bool ExtentionMethod3<T>(this T[][] x)
            => true;
 
        public static bool ExtentionMethod4<T>(this T[,] x)
            => true;
 
        public static bool ExtentionMethod5<T>(this T[][,] x)
            => true;
    }}
}}";
        var srcDoc = $@"
namespace Baz
{{
    public class Bat
    {{
        public void M(int{rank} x)
        {{
            x.$$
        }}
    }}
}}";
 
        var markup = refType switch
        {
            ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            _ => null,
        };
 
        await VerifyImportItemExistsAsync(
             markup,
             expectedName,
             displayTextSuffix: "<>",
             glyph: (int)Glyph.ExtensionMethodPublic,
             inlineDescription: "Foo");
    }
 
    [InlineData(ReferenceType.Project)]
    [InlineData(ReferenceType.Metadata)]
    [Theory]
    public async Task TestGenericReceiverTypeWithConstraint(ReferenceType refType)
    {
        var refDoc = """
            using System;
 
            namespace NS1
            {
                public class C1 {}
            }
 
            namespace NS2
            {
                public static class Extensions
                {
                    public static bool ExtentionMethod(this NS1.C1 c) => false;
                }
            }
            """;
        var srcDoc = """
            namespace NS1
            {
                public class C2
                {
                    public void M<T>(T x) where T : C1
                    {
                        x.$$
                    }
                }
            }
            """;
 
        var markup = refType switch
        {
            ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            _ => null,
        };
 
        await VerifyImportItemExistsAsync(
            markup,
            "ExtentionMethod",
            glyph: (int)Glyph.ExtensionMethodPublic,
            inlineDescription: "NS2");
    }
 
    [InlineData(ReferenceType.Project, "(int,int)")]
    [InlineData(ReferenceType.Project, "(int,int,int,int,int,int,int,int,int,int)")]    // more than 8 tuple elements
    [InlineData(ReferenceType.Metadata, "(int,int)")]
    [InlineData(ReferenceType.Metadata, "(int,int,int,int,int,int,int,int,int,int)")]   // more than 8 tuple elements
    [Theory]
    public async Task TestTupleArray(ReferenceType refType, string tupleType)
    {
        var refDoc = $@"
using System;
 
namespace NS2
{{
    public static class Extensions
    {{
        public static bool ExtentionMethod(this {tupleType}[] x) => false;
    }}
}}";
        var srcDoc = $@"
namespace NS1
{{
    public class C
    {{
        public void M({tupleType}[] x)
        {{
            x.$$
        }}
    }}
}}";
 
        var markup = refType switch
        {
            ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            _ => null,
        };
 
        await VerifyImportItemExistsAsync(
            markup,
            "ExtentionMethod",
            glyph: (int)Glyph.ExtensionMethodPublic,
            inlineDescription: "NS2");
    }
 
    [InlineData(ReferenceType.Project, "(int[],int[])")]
    [InlineData(ReferenceType.Project, "(int[],int[],int[],int[],int[],int[],int[],int[],int[],int[])")] // more than 8 tuple elements
    [InlineData(ReferenceType.Metadata, "(int[],int[])")]
    [InlineData(ReferenceType.Metadata, "(int[],int[],int[],int[],int[],int[],int[],int[],int[],int[])")] // more than 8 tuple elements
    [Theory]
    public async Task TestArrayTuple(ReferenceType refType, string tupleType)
    {
        var refDoc = $@"
using System;
 
namespace NS2
{{
    public static class Extensions
    {{
        public static bool ExtentionMethod(this {tupleType} x) => false;
    }}
}}";
        var srcDoc = $@"
namespace NS1
{{
    public class C
    {{
        public void M({tupleType} x)
        {{
            x.$$
        }}
    }}
}}";
 
        var markup = refType switch
        {
            ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            _ => null,
        };
 
        await VerifyImportItemExistsAsync(
            markup,
            "ExtentionMethod",
            glyph: (int)Glyph.ExtensionMethodPublic,
            inlineDescription: "NS2");
    }
 
    [InlineData(ReferenceType.Project)]
    [InlineData(ReferenceType.Metadata)]
    [Theory]
    public async Task TestDescriptionOfGenericReceiverType(ReferenceType refType)
    {
        var refDoc = """
            using System;
 
            namespace NS2
            {
                public static class Extensions
                {
                    public static bool ExtentionMethod<T>(this T t) => false;
                }
            }
            """;
        var srcDoc = """
            namespace NS1
            {
                public class C
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
 
        var markup = refType switch
        {
            ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            _ => null,
        };
 
        await VerifyImportItemExistsAsync(
            markup,
            "ExtentionMethod",
            displayTextSuffix: "<>",
            glyph: (int)Glyph.ExtensionMethodPublic,
            inlineDescription: "NS2",
            expectedDescriptionOrNull: $"({CSharpFeaturesResources.extension}) bool int.ExtentionMethod<int>()");
    }
 
    [InlineData(ReferenceType.Project)]
    [InlineData(ReferenceType.Metadata)]
    [Theory]
    public async Task TestDescriptionOfOverloads(ReferenceType refType)
    {
        var refDoc = """
            using System;
 
            namespace NS2
            {
                public static class Extensions
                {
                    public static bool ExtentionMethod(this int t) => false;
                    public static bool ExtentionMethod(this int t, int a) => false;
                    public static bool ExtentionMethod(this int t, int a, int b) => false;
                    public static bool ExtentionMethod<T>(this int t, T a) => false;
                    public static bool ExtentionMethod<T>(this int t, T a, T b) => false;
                    public static bool ExtentionMethod<T1, T2>(this int t, T1 a, T2 b) => false;
                }
            }
            """;
        var srcDoc = """
            namespace NS1
            {
                public class C
                {
                    public void M(int x)
                    {
                        x.$$
                    }
                }
            }
            """;
 
        var markup = refType switch
        {
            ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            _ => null,
        };
 
        await VerifyImportItemExistsAsync(
            markup,
            "ExtentionMethod",
            glyph: (int)Glyph.ExtensionMethodPublic,
            inlineDescription: "NS2",
            expectedDescriptionOrNull: $"({CSharpFeaturesResources.extension}) bool int.ExtentionMethod() (+{NonBreakingSpaceString}2{NonBreakingSpaceString}{FeaturesResources.overloads_})");
 
        await VerifyImportItemExistsAsync(
            markup,
            "ExtentionMethod",
            displayTextSuffix: "<>",
            glyph: (int)Glyph.ExtensionMethodPublic,
            inlineDescription: "NS2",
            expectedDescriptionOrNull: $"({CSharpFeaturesResources.extension}) bool int.ExtentionMethod<T>(T a) (+{NonBreakingSpaceString}2{NonBreakingSpaceString}{FeaturesResources.generic_overloads})");
    }
 
    [InlineData(ReferenceType.Project)]
    [InlineData(ReferenceType.Metadata)]
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/47551")]
    public async Task TestBrowsableAlways(ReferenceType refType)
    {
        var srcDoc = """
            class Program
            {
                void M()
                {
                    new Goo().$$
                }
            }
            """;
 
        var refDoc = """
            public class Goo
            {
            }
 
            namespace Foo
            {
                public static class GooExtensions
                {
                    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Always)]
                    public static void Bar(this Goo goo, int x)
                    {
                    }
                }
            }
            """;
 
        var markup = refType switch
        {
            ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
            _ => null,
        };
 
        await VerifyImportItemExistsAsync(
                markup,
                "Bar",
                glyph: (int)Glyph.ExtensionMethodPublic,
                inlineDescription: "Foo");
    }
 
    [InlineData(ReferenceType.Project)]
    [InlineData(ReferenceType.Metadata)]
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/47551")]
    public async Task TestBrowsableNever(ReferenceType refType)
    {
        var srcDoc = """
            class Program
            {
                void M()
                {
                    new Goo().$$
                }
            }
            """;
 
        var refDoc = """
            public class Goo
            {
            }
 
            namespace Foo
            {
                public static class GooExtensions
                {
                    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
                    public static void Bar(this Goo goo, int x)
                    {
                    }
                }
            }
            """;
 
        var (markup, shouldContainItem) = refType switch
        {
            ReferenceType.Project => (CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), true),
            ReferenceType.Metadata => (CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), false),
            _ => throw ExceptionUtilities.Unreachable(),
        };
 
        if (shouldContainItem)
        {
            await VerifyImportItemExistsAsync(
                    markup,
                    "Bar",
                    glyph: (int)Glyph.ExtensionMethodPublic,
                    inlineDescription: "Foo");
        }
        else
        {
            await VerifyImportItemIsAbsentAsync(
                    markup,
                    "Bar",
                    inlineDescription: "Foo");
        }
    }
 
    [InlineData(ReferenceType.Project, true)]
    [InlineData(ReferenceType.Project, false)]
    [InlineData(ReferenceType.Metadata, true)]
    [InlineData(ReferenceType.Metadata, false)]
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/47551")]
    public async Task TestBrowsableAdvanced(ReferenceType refType, bool hideAdvanced)
    {
        HideAdvancedMembers = hideAdvanced;
 
        var srcDoc = """
            class Program
            {
                void M()
                {
                    new Goo().$$
                }
            }
            """;
 
        var refDoc = """
            public class Goo
            {
            }
 
            namespace Foo
            {
                public static class GooExtensions
                {
                    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
                    public static void Bar(this Goo goo, int x)
                    {
                    }
                }
            }
            """;
 
        var (markup, shouldContainItem) = (refType, hideAdvanced) switch
        {
            (ReferenceType.Project, _) => (CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), true),
            (ReferenceType.Metadata, true) => (CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), false),
            (ReferenceType.Metadata, false) => (CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), true),
            _ => throw ExceptionUtilities.Unreachable(),
        };
 
        if (shouldContainItem)
        {
            await VerifyImportItemExistsAsync(
                    markup,
                    "Bar",
                    glyph: (int)Glyph.ExtensionMethodPublic,
                    inlineDescription: "Foo");
        }
        else
        {
            await VerifyImportItemIsAbsentAsync(
                    markup,
                    "Bar",
                    inlineDescription: "Foo");
        }
    }
 
    [Theory]
    [InlineData('.')]
    [InlineData(';')]
    public async Task TestCommitWithCustomizedCharForMethod(char commitChar)
    {
        var markup = """
            public class C
            {
            }
            namespace AA
            {
                public static class Ext
                {
                    public static int ToInt(this C c)
                        => 1;
                }
            }
 
            namespace BB
            {
                public class B
                {
                    public void M()
                    {
                        var c = new C();
                        c.$$
                    }
                }
            }
            """;
 
        var expected = $$"""
        using AA;
 
        public class C
        {
        }
        namespace AA
        {
            public static class Ext
            {
                public static int ToInt(this C c)
                    => 1;
            }
        }
 
        namespace BB
        {
            public class B
            {
                public void M()
                {
                    var c = new C();
                    c.ToInt(){{commitChar}}
                }
            }
        }
        """;
        await VerifyProviderCommitAsync(markup, "ToInt", expected, commitChar: commitChar, sourceCodeKind: SourceCodeKind.Regular);
    }
 
    [InlineData("int", true, "int a")]
    [InlineData("int[]", true, "int a, int b")]
    [InlineData("bool", false, null)]
    [Theory]
    public async Task TestTargetTypedCompletion(string targetType, bool matchTargetType, string expectedParameterList)
    {
        var refDoc = """
            using System;
 
            namespace NS2
            {
                public static class Extensions
                {
                    public static int ExtentionMethod(this int t, int a) => 0;
                    public static int[] ExtentionMethod(this int t, int a, int b) => null;
                    public static string ExtentionMethod(this int t, int a, int b, int c) => false;
                }
            }
            """;
        var srcDoc = $@"
namespace NS1
{{
    public class C
    {{
        public void M(int x)
        {{
            {targetType} y = x.$$
        }}
    }}
}}";
 
        ShowTargetTypedCompletionFilter = true;
        var markup = CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp);
 
        string expectedDescription = null;
        var expectedFilters = new List<CompletionFilter>()
        {
            FilterSet.ExtensionMethodFilter
        };
 
        if (matchTargetType)
        {
            expectedFilters.Add(FilterSet.TargetTypedFilter);
            expectedDescription = $"({CSharpFeaturesResources.extension}) {targetType} int.ExtentionMethod({expectedParameterList}) (+{NonBreakingSpaceString}2{NonBreakingSpaceString}{FeaturesResources.overloads_})";
        }
 
        await VerifyImportItemExistsAsync(
            markup,
            "ExtentionMethod",
            expectedFilters: expectedFilters,
            inlineDescription: "NS2",
            expectedDescriptionOrNull: expectedDescription);
    }
 
    private Task VerifyImportItemExistsAsync(string markup, string expectedItem, string inlineDescription, int? glyph = null, string displayTextSuffix = null, string expectedDescriptionOrNull = null, List<CompletionFilter> expectedFilters = null)
        => VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull, isComplexTextEdit: true, matchingFilters: expectedFilters);
 
    private Task VerifyImportItemIsAbsentAsync(string markup, string expectedItem, string inlineDescription, string displayTextSuffix = null)
        => VerifyItemIsAbsentAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription);
}