File: QuickInfo\SyntacticQuickInfoSourceTests.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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.UnitTests.QuickInfo;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Utilities;
using Moq;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.QuickInfo;
 
[Trait(Traits.Feature, Traits.Features.QuickInfo)]
public class SyntacticQuickInfoSourceTests : AbstractQuickInfoSourceTests
{
    [WpfFact]
    public async Task Brackets_0()
    {
        await TestInMethodAndScriptAsync(
@"
switch (true)
{
}$$
",
@"switch (true)
{");
    }
 
    [WpfFact]
    public async Task Brackets_1()
        => await TestInClassAsync("int Property { get; }$$ ", "int Property {");
 
    [WpfFact]
    public async Task Brackets_2()
        => await TestInClassAsync("void M()\r\n{ }$$ ", "void M()\r\n{");
 
    [WpfFact]
    public async Task Brackets_3()
        => await TestInMethodAndScriptAsync("var a = new int[] { }$$ ", "new int[] {");
 
    [WpfFact]
    public async Task Brackets_4()
    {
        await TestInMethodAndScriptAsync(
@"
if (true)
{
}$$
",
@"if (true)
{");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_0()
    {
        await TestInMethodAndScriptAsync(
@"if (true)
            {
                {
                }$$
            }",
        "{");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_1()
    {
        await TestInMethodAndScriptAsync(
@"while (true)
            {
                // some
                // comment
                {
                }$$
            }",
@"// some
// comment
{");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_2()
    {
        await TestInMethodAndScriptAsync(
@"do
            {
                /* comment */
                {
                }$$
            }
            while (true);",
@"/* comment */
{");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_3()
    {
        await TestInMethodAndScriptAsync(
@"if (true)
            {
            }
            else
            {
                {
                    // some
                    // comment
                }$$
            }",
@"{
    // some
    // comment");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_4()
    {
        await TestInMethodAndScriptAsync(
@"using (var x = new X())
            {
                {
                    /* comment */
                }$$
            }",
@"{
    /* comment */");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_5()
    {
        await TestInMethodAndScriptAsync(
@"foreach (var x in xs)
            {
                // above
                {
                    /* below */
                }$$
            }",
@"// above
{");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_6()
    {
        await TestInMethodAndScriptAsync(
@"for (;;)
            {
                /*************/
 
                // part 1
 
                // part 2
                {
                }$$
            }",
@"/*************/
 
// part 1
 
// part 2
{");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_7()
    {
        await TestInMethodAndScriptAsync(
@"try
            {
                /*************/
 
                // part 1
 
                // part 2
                {
                }$$
            }
            catch { throw; }",
@"/*************/
 
// part 1
 
// part 2
{");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_8()
    {
        await TestInMethodAndScriptAsync(
@"
{
    /*************/
 
    // part 1
 
    // part 2
}$$
",
@"{
    /*************/
 
    // part 1
 
    // part 2");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_9()
    {
        await TestInClassAsync(
@"int Property
{
    set
    {
        {
        }$$
    }
}",
        "{");
    }
 
    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/325")]
    public async Task ScopeBrackets_10()
    {
        await TestInMethodAndScriptAsync(
@"switch (true)
            {
                default:
                    // comment
                    {
                    }$$
                    break;
            }",
@"// comment
{");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task RegionEndShowsStartRegionMessage()
    {
        await TestAsync(
@"
#region Start
#end$$region", "#region Start");
    }
 
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    [InlineData("$$#endregion")]
    [InlineData("#$$endregion")]
    [InlineData("#endregion$$ ")]
    [InlineData("#endregion$$\r\n")]
    [InlineData("#endregion$$ End")]
    public async Task RegionEndShowsStartRegionMessageAtDifferentPositions(string endRegion)
    {
        await TestAsync(
@$"
#region Start
{endRegion}", "#region Start");
    }
 
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    [InlineData("#endregion$$")]
    [InlineData("# $$ endregion")]
    [InlineData("#endregion $$End")]
    [InlineData("#endregion En$$d")]
    [InlineData("#endregion $$")]
    [InlineData("#endregion\r\n$$")]
    public async Task RegionEndQuickInfoIsNotOfferedAtDifferentPositions(string endRegion)
    {
        await TestAsync(
@$"
#region Start
{endRegion}", "");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task RegionEndHasNoQuickinfo_MissingRegionStart_1()
    {
        await TestAsync(
@$"#end$$region", "");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task RegionEndHasNoQuickinfo_MissingRegionStart_2()
    {
        await TestAsync(
@$"
#region Start
#endregion
#end$$region", "");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task RegionEndShowsRegionStart_Nesting_1()
    {
        await TestAsync(
@$"
#region Start1
#region Start2
#endregion
#end$$region", "#region Start1");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task RegionEndShowsRegionStart_Nesting_2()
    {
        await TestAsync(
@$"
#region Start1
#region Start2
#end$$region
#endregion", "#region Start2");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task RegionEndShowsRegionStart_Blocks_1()
    {
        await TestAsync(
@$"
#region Start1
#end$$region
#region Start2
#endregion", "#region Start1");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task RegionEndShowsRegionStart_Blocks_2()
    {
        await TestAsync(
@$"
#region Start1
#endregion
#region Start2
#end$$region", "#region Start2");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task EndIfShowsIfCondition_1()
    {
        await TestAsync(
@$"
#if DEBUG
#end$$if", "#if DEBUG");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task EndIfShowsIfCondition_2()
    {
        await TestAsync(
@$"
#if DEBUG
#else
#end$$if", "#if DEBUG\r\n#else");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task EndIfShowsElIfCondition()
    {
        await TestAsync(
@$"
#if DEBUG
#elif RELEASE
#end$$if", "#if DEBUG\r\n#elif RELEASE");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task ElseShowsIfCondition()
    {
        await TestAsync(
@$"
#if DEBUG
#el$$se
#endif", "#if DEBUG");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task ElseShowsElIfCondition_1()
    {
        await TestAsync(
@$"
#if DEBUG
#elif RELEASE
#el$$se
#endif", "#if DEBUG\r\n#elif RELEASE");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task ElseShowsElIfCondition_2()
    {
        await TestAsync(
@$"
#if DEBUG
#elif RELEASE
#elif DEMO
#el$$se
#endif", "#if DEBUG\r\n#elif RELEASE\r\n#elif DEMO");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task ElIfShowsIfCondition()
    {
        await TestAsync(
@$"
#if DEBUG
#el$$if RELEASE
#endif", "#if DEBUG");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task EndIfShowsIfNested_1()
    {
        await TestAsync(
@$"
#if DEBUG
#if RELEASE
#end$$if
#endif", "#if RELEASE");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task EndIfShowsIfNested_2()
    {
        await TestAsync(
@$"
#if DEBUG
#if RELEASE
#endif
#end$$if", "#if DEBUG");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task EndIfShowsIfNested_3()
    {
        await TestAsync(
@$"
#if DEBUG
#elif RELEASE
#if DEMO
#end$$if
#endif", "#if DEMO");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task EndIfShowsIfNested_4()
    {
        await TestAsync(
@$"
#if DEBUG
#elif RELEASE
#if DEMO
#endif
#end$$if", "#if DEBUG\r\n#elif RELEASE");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task EndIfHasNoQuickinfo_MissingIf_1()
    {
        await TestAsync(
@$"
#end$$if", "");
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    public async Task EndIfHasNoQuickinfo_MissingIf_2()
    {
        await TestAsync(
@$"
#if DEBUG
#endif
#end$$if", "");
    }
 
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    [InlineData("#$$elif RELEASE")]
    [InlineData("#elif$$ RELEASE")]
    [InlineData("#elif RELEASE$$")]
    public async Task ElifHasQuickinfoAtDifferentPositions(string elif)
    {
        await TestAsync(
@$"
#if DEBUG
{elif}
#endif", "#if DEBUG");
    }
 
    [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/56507")]
    [InlineData("#elif $$RELEASE")]
    [InlineData("#elif RELE$$ASE")]
    [InlineData("#elif (REL$$EASE == true)")]
    [InlineData("#elif (RELEASE =$$= true)")]
    [InlineData("#elif (RELEASE !$$= true)")]
    [InlineData("#elif (RELEASE == $$true)")]
    [InlineData("#elif (RELEASE == $$false)")]
    [InlineData("#elif RELEASE |$$| DEMO")]
    [InlineData("#elif RELEASE &$$& DEMO")]
    [InlineData("#elif ($$ RELEASE && DEMO)")]
    [InlineData("#elif (RELEASE && DEMO $$)")]
    public async Task ElifHasNoQuickinfoAtDifferentPositions(string elif)
    {
        await TestAsync(
@$"
#if DEBUG
{elif}
#endif", "");
    }
 
    private static QuickInfoProvider CreateProvider()
        => new CSharpSyntacticQuickInfoProvider();
 
    protected override async Task AssertNoContentAsync(
        EditorTestWorkspace workspace,
        Document document,
        int position)
    {
        var provider = CreateProvider();
        Assert.Null(await provider.GetQuickInfoAsync(new QuickInfoContext(document, position, SymbolDescriptionOptions.Default, CancellationToken.None)));
    }
 
    protected override async Task AssertContentIsAsync(
        EditorTestWorkspace workspace,
        Document document,
        int position,
        string expectedContent,
        string expectedDocumentationComment = null)
    {
        var provider = CreateProvider();
        var info = await provider.GetQuickInfoAsync(new QuickInfoContext(document, position, SymbolDescriptionOptions.Default, CancellationToken.None));
        Assert.NotNull(info);
        Assert.NotEqual(0, info.RelatedSpans.Length);
 
        var trackingSpan = new Mock<ITrackingSpan>(MockBehavior.Strict);
 
        var navigationActionFactory = new NavigationActionFactory(
            document,
            threadingContext: workspace.ExportProvider.GetExportedValue<IThreadingContext>(),
            operationExecutor: workspace.ExportProvider.GetExportedValue<IUIThreadOperationExecutor>(),
            AsynchronousOperationListenerProvider.NullListener,
            streamingPresenter: workspace.ExportProvider.GetExport<IStreamingFindUsagesPresenter>());
 
        var quickInfoItem = await IntellisenseQuickInfoBuilder.BuildItemAsync(
            trackingSpan.Object, info, document,
            ClassificationOptions.Default, LineFormattingOptions.Default,
            navigationActionFactory, CancellationToken.None);
 
        var containerElement = quickInfoItem.Item as ContainerElement;
 
        var textElements = containerElement.Elements.OfType<ClassifiedTextElement>();
        Assert.NotEmpty(textElements);
 
        var textElement = textElements.First();
        var actualText = string.Concat(textElement.Runs.Select(r => r.Text));
        Assert.Equal(expectedContent, actualText);
    }
 
    protected override Task TestInMethodAsync(string code, string expectedContent, string expectedDocumentationComment = null)
    {
        return TestInClassAsync(
@"void M()
{" + code + "}", expectedContent, expectedDocumentationComment);
    }
 
    protected override Task TestInClassAsync(string code, string expectedContent, string expectedDocumentationComment = null)
    {
        return TestAsync(
@"class C
{" + code + "}", expectedContent, expectedDocumentationComment);
    }
 
    protected override Task TestInScriptAsync(string code, string expectedContent, string expectedDocumentationComment = null)
        => TestAsync(code, expectedContent, expectedContent, Options.Script);
 
    protected override async Task TestAsync(
        string code,
        string expectedContent,
        string expectedDocumentationComment = null,
        CSharpParseOptions parseOptions = null)
    {
        using var workspace = EditorTestWorkspace.CreateCSharp(code, parseOptions);
        var testDocument = workspace.Documents.Single();
        var position = testDocument.CursorPosition.Value;
        var document = workspace.CurrentSolution.Projects.First().Documents.First();
 
        if (string.IsNullOrEmpty(expectedContent))
        {
            await AssertNoContentAsync(workspace, document, position);
        }
        else
        {
            await AssertContentIsAsync(workspace, document, position, expectedContent, expectedDocumentationComment);
        }
    }
}