File: Structure\StructureTaggerTests.cs
Web Access
Project: src\src\EditorFeatures\Test\Microsoft.CodeAnalysis.EditorFeatures.UnitTests.csproj (Microsoft.CodeAnalysis.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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Implementation.Structure;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.Structure;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Structure;
 
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.Outlining)]
public class StructureTaggerTests
{
    [WpfTheory]
    [CombinatorialData]
    public async Task CSharpOutliningTagger(
        bool collapseRegionsWhenCollapsingToDefinitions,
        bool showBlockStructureGuidesForDeclarationLevelConstructs,
        bool showBlockStructureGuidesForCodeLevelConstructs,
        bool showBlockStructureGuidesForCommentsAndPreprocessorRegions)
    {
        var code =
@"using System;
namespace MyNamespace
{
#region MyRegion
    public class MyClass
    {
        static void Main(string[] args)
        {
            if (false)
            {
                return;
            }
 
            int x = 5;
        }
    }
#endregion
}";
 
        using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf);
        var globalOptions = workspace.GlobalOptions;
 
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForDeclarationLevelConstructs);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForCodeLevelConstructs);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, LanguageNames.CSharp, showBlockStructureGuidesForCommentsAndPreprocessorRegions);
 
        var tags = await GetTagsFromWorkspaceAsync(workspace);
 
        Assert.Collection(tags,
            namespaceTag =>
            {
                Assert.False(namespaceTag.IsImplementation);
                Assert.Equal(17, GetCollapsedHintLineCount(namespaceTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Namespace : PredefinedStructureTagTypes.Nonstructural, namespaceTag.Type);
                Assert.Equal("namespace MyNamespace", GetHeaderText(namespaceTag));
            },
            regionTag =>
            {
                Assert.Equal(collapseRegionsWhenCollapsingToDefinitions, regionTag.IsImplementation);
                Assert.Equal(14, GetCollapsedHintLineCount(regionTag));
                Assert.Equal(showBlockStructureGuidesForCommentsAndPreprocessorRegions ? PredefinedStructureTagTypes.PreprocessorRegion : PredefinedStructureTagTypes.Nonstructural, regionTag.Type);
                Assert.Equal("#region MyRegion", GetHeaderText(regionTag));
            },
            classTag =>
            {
                Assert.False(classTag.IsImplementation);
                Assert.Equal(12, GetCollapsedHintLineCount(classTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Type : PredefinedStructureTagTypes.Nonstructural, classTag.Type);
                Assert.Equal("public class MyClass", GetHeaderText(classTag));
            },
            methodTag =>
            {
                Assert.True(methodTag.IsImplementation);
                Assert.Equal(9, GetCollapsedHintLineCount(methodTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Member : PredefinedStructureTagTypes.Nonstructural, methodTag.Type);
                Assert.Equal("static void Main(string[] args)", GetHeaderText(methodTag));
            },
            ifTag =>
            {
                Assert.False(ifTag.IsImplementation);
                Assert.Equal(4, GetCollapsedHintLineCount(ifTag));
                Assert.Equal(showBlockStructureGuidesForCodeLevelConstructs ? PredefinedStructureTagTypes.Conditional : PredefinedStructureTagTypes.Nonstructural, ifTag.Type);
                Assert.Equal("if (false)", GetHeaderText(ifTag));
            });
    }
 
    [WpfTheory]
    [CombinatorialData]
    public async Task CSharpImportsFileScopedNamespaceTest(
        bool collapseRegionsWhenCollapsingToDefinitions,
        bool showBlockStructureGuidesForDeclarationLevelConstructs,
        bool showBlockStructureGuidesForCodeLevelConstructs)
    {
        var code =
@"
namespace Foo;
 
using System;
using System.Linq;
public class Bar
{
 
}
";
 
        using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf);
        var globalOptions = workspace.GlobalOptions;
 
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForDeclarationLevelConstructs);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForCodeLevelConstructs);
 
        var tags = await GetTagsFromWorkspaceAsync(workspace);
 
        Assert.Collection(tags,
            importsTag =>
            {
                Assert.Equal(2, GetCollapsedHintLineCount(importsTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Imports : PredefinedStructureTagTypes.Nonstructural, importsTag.Type);
                Assert.Equal("using ", GetHeaderText(importsTag));
            },
            classTag =>
            {
                Assert.Equal(4, GetCollapsedHintLineCount(classTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Type : PredefinedStructureTagTypes.Nonstructural, classTag.Type);
                Assert.Equal("public class Bar", GetHeaderText(classTag));
            });
    }
 
    [WpfTheory]
    [CombinatorialData]
    public async Task CSharpCommentsFileScopedNamespace(
        bool collapseRegionsWhenCollapsingToDefinitions,
        bool showBlockStructureGuidesForDeclarationLevelConstructs,
        bool showBlockStructureGuidesForCodeLevelConstructs,
        bool showBlockStructureGuidesForCommentsAndPreprocessorRegions)
    {
        var code =
@"
namespace Foo;
/// <summary>
/// 
/// </summary>
 
public class Bar
{
 
}
";
 
        using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf);
        var globalOptions = workspace.GlobalOptions;
 
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForDeclarationLevelConstructs);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForCodeLevelConstructs);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, LanguageNames.CSharp, showBlockStructureGuidesForCommentsAndPreprocessorRegions);
 
        var tags = await GetTagsFromWorkspaceAsync(workspace);
 
        Assert.Collection(tags,
            commentsTag =>
            {
                Assert.Equal(3, GetCollapsedHintLineCount(commentsTag));
                Assert.Equal(showBlockStructureGuidesForCommentsAndPreprocessorRegions ? PredefinedStructureTagTypes.Comment : PredefinedStructureTagTypes.Nonstructural, commentsTag.Type);
                Assert.Equal("/// <summary>", GetHeaderText(commentsTag));
            },
            classTag =>
            {
                Assert.Equal(4, GetCollapsedHintLineCount(classTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Type : PredefinedStructureTagTypes.Nonstructural, classTag.Type);
                Assert.Equal("public class Bar", GetHeaderText(classTag));
            });
    }
 
    [WpfTheory]
    [CombinatorialData]
    public async Task CSharpImportsNormalNamespaceTest(
        bool collapseRegionsWhenCollapsingToDefinitions,
        bool showBlockStructureGuidesForDeclarationLevelConstructs,
        bool showBlockStructureGuidesForCodeLevelConstructs)
    {
        var code =
@"
namespace Foo
{
    using System;
    using System.Linq;
    public class Bar
    {
 
    }
}
";
 
        using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf);
        var globalOptions = workspace.GlobalOptions;
 
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForDeclarationLevelConstructs);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForCodeLevelConstructs);
 
        var tags = await GetTagsFromWorkspaceAsync(workspace);
 
        Assert.Collection(tags,
            namespaceTag =>
            {
                Assert.Equal(9, GetCollapsedHintLineCount(namespaceTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Namespace : PredefinedStructureTagTypes.Nonstructural, namespaceTag.Type);
                Assert.Equal("namespace Foo", GetHeaderText(namespaceTag));
            },
            importsTag =>
            {
                Assert.Equal(2, GetCollapsedHintLineCount(importsTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Imports : PredefinedStructureTagTypes.Nonstructural, importsTag.Type);
                Assert.Equal("using ", GetHeaderText(importsTag));
            },
            classTag =>
            {
                Assert.Equal(4, GetCollapsedHintLineCount(classTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Type : PredefinedStructureTagTypes.Nonstructural, classTag.Type);
                Assert.Equal("public class Bar", GetHeaderText(classTag));
            });
    }
 
    [WpfTheory]
    [CombinatorialData]
    public async Task VisualBasicOutliningTagger(
        bool collapseRegionsWhenCollapsingToDefinitions,
        bool showBlockStructureGuidesForDeclarationLevelConstructs,
        bool showBlockStructureGuidesForCodeLevelConstructs)
    {
        var code = @"Imports System
Namespace MyNamespace
#Region ""MyRegion""
    Module M
        Sub Main(args As String())
            If False Then
                Return
            End If
 
            Dim x As Integer = 5
        End Sub
    End Module
#End Region
End Namespace";
 
        using var workspace = EditorTestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeaturesWpf);
        var globalOptions = workspace.GlobalOptions;
 
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.VisualBasic, collapseRegionsWhenCollapsingToDefinitions);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.VisualBasic, showBlockStructureGuidesForDeclarationLevelConstructs);
        globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.VisualBasic, showBlockStructureGuidesForCodeLevelConstructs);
 
        var tags = await GetTagsFromWorkspaceAsync(workspace);
 
        Assert.Collection(tags,
            namespaceTag =>
            {
                Assert.False(namespaceTag.IsImplementation);
                Assert.Equal(13, GetCollapsedHintLineCount(namespaceTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Namespace : PredefinedStructureTagTypes.Nonstructural, namespaceTag.Type);
                Assert.Equal("Namespace MyNamespace", GetHeaderText(namespaceTag));
            },
            regionTag =>
            {
                Assert.Equal(collapseRegionsWhenCollapsingToDefinitions, regionTag.IsImplementation);
                Assert.Equal(11, GetCollapsedHintLineCount(regionTag));
                Assert.Equal(PredefinedStructureTagTypes.Nonstructural, regionTag.Type);
                Assert.Equal(@"#Region ""MyRegion""", GetHeaderText(regionTag));
            },
            moduleTag =>
            {
                Assert.False(moduleTag.IsImplementation);
                Assert.Equal(9, GetCollapsedHintLineCount(moduleTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Type : PredefinedStructureTagTypes.Nonstructural, moduleTag.Type);
                Assert.Equal("Module M", GetHeaderText(moduleTag));
            },
            methodTag =>
            {
                Assert.True(methodTag.IsImplementation);
                Assert.Equal(7, GetCollapsedHintLineCount(methodTag));
                Assert.Equal(showBlockStructureGuidesForDeclarationLevelConstructs ? PredefinedStructureTagTypes.Member : PredefinedStructureTagTypes.Nonstructural, methodTag.Type);
                Assert.Equal("Sub Main(args As String())", GetHeaderText(methodTag));
            },
            ifTag =>
            {
                Assert.False(ifTag.IsImplementation);
                Assert.Equal(3, GetCollapsedHintLineCount(ifTag));
                Assert.Equal(showBlockStructureGuidesForCodeLevelConstructs ? PredefinedStructureTagTypes.Conditional : PredefinedStructureTagTypes.Nonstructural, ifTag.Type);
                Assert.Equal("If False Then", GetHeaderText(ifTag));
            });
 
    }
 
    [WpfFact]
    public async Task OutliningTaggerTooltipText()
    {
        var code = @"Module Module1
    Sub Main(args As String())
    End Sub
End Module";
 
        using var workspace = EditorTestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeaturesWpf);
        var tags = await GetTagsFromWorkspaceAsync(workspace);
 
        var hints = tags.Select(x => x.GetCollapsedHintForm()).Cast<ViewHostingControl>().ToArray();
        Assert.Equal("Sub Main(args As String())\r\nEnd Sub", hints[1].GetText_TestOnly()); // method
        hints.Do(v => v.TextView_TestOnly.Close());
    }
 
    [WpfFact]
    [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2094051")]
    public async Task IfShouldBeCollapsed()
    {
        var code = @"
Module Program
    Sub Main(args As String())
        Dim str = """"
        If str.Contains(""foo"") Then
 
        End If
    End Sub
End Module";
 
        using var workspace = EditorTestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeaturesWpf);
        var tags = await GetTagsFromWorkspaceAsync(workspace);
        Assert.Collection(tags, programTag =>
        {
            Assert.Equal("Module Program", GetHeaderText(programTag));
            Assert.Equal(8, GetCollapsedHintLineCount(programTag));
        },
        mainTag =>
        {
            Assert.Equal("Sub Main(args As String())", GetHeaderText(mainTag));
            Assert.Equal(6, GetCollapsedHintLineCount(mainTag));
        },
        IfTag =>
        {
            Assert.Equal("If str.Contains(\"foo\") Then", GetHeaderText(IfTag));
            Assert.Equal(3, GetCollapsedHintLineCount(IfTag));
        });
    }
 
    [WpfFact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2112145")]
    public async Task LambdaShouldBeCollapsed()
    {
        var code = """
            public class MyClass
            {
                public void MyMethod() => System.Linq.Enumerable.Range(10, 100).Any(x =>
                {
                    return x == 10;
                });
            }
            """;
 
        using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf);
        var tags = await GetTagsFromWorkspaceAsync(workspace);
 
        Assert.Collection(tags, programTag =>
        {
            Assert.Equal("public class MyClass", GetHeaderText(programTag));
            Assert.Equal(7, GetCollapsedHintLineCount(programTag));
        },
        mainTag =>
        {
            Assert.Equal("public void MyMethod()", GetHeaderText(mainTag));
            Assert.Equal(4, GetCollapsedHintLineCount(mainTag));
        },
        IfTag =>
        {
            Assert.Equal("x =>", GetHeaderText(IfTag));
            Assert.Equal(4, GetCollapsedHintLineCount(IfTag));
        });
    }
 
#pragma warning disable CS0618 // Type or member is obsolete
    private static async Task<List<IContainerStructureTag>> GetTagsFromWorkspaceAsync(EditorTestWorkspace workspace)
    {
        var hostdoc = workspace.Documents.First();
        var view = hostdoc.GetTextView();
 
        var provider = workspace.ExportProvider.GetExportedValue<AbstractStructureTaggerProvider>();
 
        var document = workspace.CurrentSolution.GetDocument(hostdoc.Id);
        var context = new TaggerContext<IContainerStructureTag>(document, view.TextSnapshot, frozenPartialSemantics: false);
        await provider.GetTestAccessor().ProduceTagsAsync(context);
 
        return [.. context.TagSpans.Select(x => x.Tag).OrderBy(t => t.OutliningSpan.Value.Start)];
    }
#pragma warning restore CS0618 // Type or member is obsolete
 
    private static string GetHeaderText(IStructureTag namespaceTag)
    {
        return namespaceTag.Snapshot.GetText(namespaceTag.HeaderSpan.Value);
    }
 
    private static int GetCollapsedHintLineCount(IStructureTag tag)
    {
        var control = Assert.IsType<ViewHostingControl>(tag.GetCollapsedHintForm());
        var view = control.TextView_TestOnly;
        try
        {
            return view.TextSnapshot.LineCount;
        }
        finally
        {
            view.Close();
        }
    }
}