File: Debugging\DataTipInfoGetterTests.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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Debugging;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Debugging;
 
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.DebuggingDataTips)]
public class DataTipInfoGetterTests
{
    private static async Task TestAsync(string markup, string expectedText = null)
    {
        await TestSpanGetterAsync(markup, async (document, position, expectedSpan) =>
        {
            var result = await DataTipInfoGetter.GetInfoAsync(document, position, CancellationToken.None);
 
            Assert.Equal(expectedSpan, result.Span);
            Assert.Equal(expectedText, result.Text);
        });
    }
 
    private static async Task TestNoDataTipAsync(string markup)
    {
        await TestSpanGetterAsync(markup, async (document, position, expectedSpan) =>
        {
            var result = await DataTipInfoGetter.GetInfoAsync(document, position, CancellationToken.None);
            Assert.True(result.IsDefault);
        });
    }
 
    private static async Task TestSpanGetterAsync(string markup, Func<Document, int, TextSpan?, Task> continuation)
    {
        using var workspace = EditorTestWorkspace.CreateCSharp(markup);
        var testHostDocument = workspace.Documents.Single();
        var position = testHostDocument.CursorPosition.Value;
        var expectedSpan = testHostDocument.SelectedSpans.Any()
            ? testHostDocument.SelectedSpans.Single()
            : (TextSpan?)null;
 
        await continuation(
            workspace.CurrentSolution.Projects.First().Documents.First(),
            position,
            expectedSpan);
    }
 
    [Fact]
    public async Task TestCSharpLanguageDebugInfoGetDataTipSpanAndText()
    {
        await TestAsync("class [|C$$|] { }");
        await TestAsync("struct [|C$$|] { }");
        await TestAsync("interface [|C$$|] { }");
        await TestAsync("enum [|C$$|] { }");
        await TestAsync("delegate void [|C$$|] ();"); // Without the space, that position is actually on the open paren.
    }
 
    [Fact]
    public async Task Test1()
    {
        await TestAsync(
            """
            class C
            {
              void Goo()
              {
                [|Sys$$tem|].Console.WriteLine(args);
              }
            }
            """);
    }
 
    [Fact]
    public async Task Test2()
    {
        await TestAsync(
            """
            class C
            {
              void Goo()
              {
                [|System$$.Console|].WriteLine(args);
              }
            }
            """);
    }
 
    [Fact]
    public async Task Test3()
    {
        await TestAsync(
            """
            class C
            {
              void Goo()
              {
                [|System.$$Console|].WriteLine(args);
              }
            }
            """);
    }
 
    [Fact]
    public async Task Test4()
    {
        await TestAsync(
            """
            class C
            {
              void Goo()
              {
                [|System.Con$$sole|].WriteLine(args);
              }
            }
            """);
    }
 
    [Fact]
    public async Task Test5()
    {
        await TestAsync(
            """
            class C
            {
              void Goo()
              {
                [|System.Console.Wri$$teLine|](args);
              }
            }
            """);
    }
 
    [Fact]
    public async Task Test6()
    {
        await TestNoDataTipAsync(
            """
            class C
            {
              void Goo()
              {
                [|System.Console.WriteLine|]$$(args);
              }
            }
            """);
    }
 
    [Fact]
    public async Task Test7()
    {
        await TestAsync(
            """
            class C
            {
              void Goo()
              {
                System.Console.WriteLine($$[|args|]);
              }
            }
            """);
    }
 
    [Fact]
    public async Task Test8()
    {
        await TestNoDataTipAsync(
            """
            class C
            {
              void Goo()
              {
                [|System.Console.WriteLine|](args$$);
              }
            }
            """);
    }
 
    [Fact]
    public async Task TestVar()
    {
        await TestAsync(
            """
            class C
            {
              void Goo()
              {
                [|va$$r|] v = 0;
              }
            }
            """, "int");
    }
 
    [Fact]
    public async Task TestVariableType()
    {
        await TestAsync(
            """
            class C
            {
              void Goo()
              {
                [|in$$t|] i = 0;
              }
            }
            """);
    }
 
    [Fact]
    public async Task TestVariableIdentifier()
    {
        await TestAsync(
            """
            class C
            {
              void Goo()
              {
                int [|$$i|] = 0;
              }
            }
            """);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/539910")]
    public async Task TestLiterals()
    {
        await TestAsync(
            """
            class C
            {
              void Goo()
              {
                int i = [|4$$2|];
              }
            }
            """, "int");
    }
 
    [Fact]
    public async Task TestNonExpressions()
    {
        await TestNoDataTipAsync(
            """
            class C
            {
              void Goo()
              {
                int i = 42;
              }$$
            }
            """);
    }
 
    [Fact]
    public async Task TestParameterIdentifier()
    {
        await TestAsync(
            """
            class C
            {
              void Goo(int [|$$i|])
              {
              }
            }
            """);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/942699")]
    public async Task TestCatchIdentifier()
    {
        await TestAsync(
            """
            class C
            {
                void Goo()
                {
                    try
                    {
                    }
                    catch (System.Exception [|$$e|])
                    {
                    }
                }
            }
            """);
    }
 
    [Fact]
    public async Task TestEvent()
    {
        await TestAsync(
            """
            class C
            {
                event System.Action [|$$E|];
            }
            """);
 
        await TestAsync(
            """
            class C
            {
                event System.Action [|$$E|]
                {
                    add { }
                    remove { }
                }
            }
            """);
    }
 
    [Fact]
    public async Task TestMethod()
    {
        await TestAsync(
            """
            class C
            {
                int [|$$M|]() { }
            }
            """);
    }
 
    [Fact]
    public async Task TestTypeParameter()
    {
        await TestAsync("class C<T, [|$$U|], V> { }");
        await TestAsync(
            """
            class C
            {
                void M<T, [|$$U|]>() { }
            }
            """);
    }
 
    [Fact]
    public async Task UsingAlias()
    {
        await TestAsync(
            """
            using [|$$S|] = Static;
 
            static class Static
            {
            }
            """);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540921")]
    public async Task TestForEachIdentifier()
    {
        await TestAsync(
            """
            class C
            {
              void Goo(string[] args)
              {
                foreach (string [|$$s|] in args)
                {
                }
              }
            }
            """);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546328")]
    public async Task TestProperty()
    {
        await TestAsync(
            """
            namespace ConsoleApplication16
            {
                class C
                {
                    public int [|$$goo|] { get; private set; } // hover over me
                    public C()
                    {
                        this.goo = 1;
                    }
                    public int Goo()
                    {
                        return 2; // breakpoint here
                    }
                }
                class Program
                {
                    static void Main(string[] args)
                    {
                        new C().Goo();
                    }
                }
            }
            """);
    }
 
    [Fact]
    public async Task TestQueryIdentifier()
    {
        await TestAsync( // From
            """
            class C
            {
                object Goo(string[] args)
                {
                    return from [|$$a|] in args select a;
                }
            }
            """);
        await TestAsync( // Let
            """
            class C
            {
                object Goo(string[] args)
                {
                    return from a in args let [|$$b|] = "END" select a + b;
                }
            }
            """);
        await TestAsync( // Join
            """
            class C
            {
                object Goo(string[] args)
                {
                    return from a in args join [|$$b|] in args on a equals b;
                }
            }
            """);
        await TestAsync( // Join Into
            """
            class C
            {
                object Goo(string[] args)
                {
                    return from a in args join b in args on a equals b into [|$$c|];
                }
            }
            """);
        await TestAsync( // Continuation
            """
            class C
            {
                object Goo(string[] args)
                {
                    return from a in args select a into [|$$b|] from c in b select c;
                }
            }
            """);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1077843")]
    public async Task TestConditionalAccessExpression()
    {
        var sourceTemplate = """
            class A
            {{
                B B;
 
                object M()
                {{
                    return {0};
                }}
            }}
 
            class B
            {{
                C C;
            }}
 
            class C
            {{
                D D;
            }}
 
            class D
            {{
            }}
            """;
 
        // One level.
        await TestAsync(string.Format(sourceTemplate, "[|Me?.$$B|]"));
 
        // Two levels.
        await TestAsync(string.Format(sourceTemplate, "[|Me?.$$B|].C"));
        await TestAsync(string.Format(sourceTemplate, "[|Me?.B.$$C|]"));
 
        await TestAsync(string.Format(sourceTemplate, "[|Me.$$B|]?.C"));
        await TestAsync(string.Format(sourceTemplate, "[|Me.B?.$$C|]"));
 
        await TestAsync(string.Format(sourceTemplate, "[|Me?.$$B|]?.C"));
        await TestAsync(string.Format(sourceTemplate, "[|Me?.B?.$$C|]"));
 
        // Three levels.
        await TestAsync(string.Format(sourceTemplate, "[|Me?.$$B|].C.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me?.B.$$C|].D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me?.B.C.$$D|]"));
 
        await TestAsync(string.Format(sourceTemplate, "[|Me.$$B|]?.C.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me.B?.$$C|].D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me.B?.C.$$D|]"));
 
        await TestAsync(string.Format(sourceTemplate, "[|Me.$$B|].C?.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me.B.$$C|]?.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me.B.C?.$$D|]"));
 
        await TestAsync(string.Format(sourceTemplate, "[|Me?.$$B|]?.C.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me?.B?.$$C|].D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me?.B?.C.$$D|]"));
 
        await TestAsync(string.Format(sourceTemplate, "[|Me?.$$B|].C?.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me?.B.$$C|]?.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me?.B.C?.$$D|]"));
 
        await TestAsync(string.Format(sourceTemplate, "[|Me.$$B|]?.C?.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me.B?.$$C|]?.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me.B?.C?.$$D|]"));
 
        await TestAsync(string.Format(sourceTemplate, "[|Me?.$$B|]?.C?.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me?.B?.$$C|]?.D"));
        await TestAsync(string.Format(sourceTemplate, "[|Me?.B?.C?.$$D|]"));
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1077843")]
    public async Task TestConditionalAccessExpression_Trivia()
    {
        var sourceTemplate = """
            class A
            {{
                B B;
 
                object M()
                {{
                    return {0};
                }}
            }}
 
            class B
            {{
                C C;
            }}
 
            class C
            {{
            }}
            """;
 
        await TestAsync(string.Format(sourceTemplate, "/*1*/[|$$Me|]/*2*/?./*3*/B/*4*/?./*5*/C/*6*/"));
        await TestAsync(string.Format(sourceTemplate, "/*1*/[|Me/*2*/?./*3*/$$B|]/*4*/?./*5*/C/*6*/"));
        await TestAsync(string.Format(sourceTemplate, "/*1*/[|Me/*2*/?./*3*/B/*4*/?./*5*/$$C|]/*6*/"));
    }
}