File: StringIndentation\StringIndentationTests.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.
 
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.StringIndentation;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.StringIndentation;
 
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.StringIndentation)]
public class StringIndentationTests
{
    private static async Task TestAsync(string contents)
    {
        using var workspace = TestWorkspace.CreateWorkspace(
            TestWorkspace.CreateWorkspaceElement(LanguageNames.CSharp,
                files: [contents.Replace("|", " ")],
                isMarkup: false));
        var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id);
        var root = await document.GetRequiredSyntaxRootAsync(default);
 
        var service = document.GetRequiredLanguageService<IStringIndentationService>();
        var regions = await service.GetStringIndentationRegionsAsync(document, root.FullSpan, CancellationToken.None).ConfigureAwait(false);
 
        var actual = ApplyRegions(contents.Replace("|", " "), regions);
        Assert.Equal(contents, actual);
    }
 
    private static string ApplyRegions(string val, ImmutableArray<StringIndentationRegion> regions)
    {
        var text = SourceText.From(val);
        using var _ = ArrayBuilder<TextChange>.GetInstance(out var changes);
 
        foreach (var region in regions)
        {
            var firstLine = text.Lines.GetLineFromPosition(region.IndentSpan.Start);
            var lastLine = text.Lines.GetLineFromPosition(region.IndentSpan.End);
            var offset = region.IndentSpan.End - lastLine.Start;
 
            for (var i = firstLine.LineNumber + 1; i < lastLine.LineNumber; i++)
            {
                var lineStart = text.Lines[i].Start;
                if (region.OrderedHoleSpans.Any(s => s.Contains(lineStart)))
                    continue;
 
                changes.Add(new TextChange(new TextSpan(lineStart + offset - 1, 1), "|"));
            }
        }
 
        var changedText = text.WithChanges(changes);
        return changedText.ToString();
    }
 
    [Fact]
    public async Task TestEmptyFile()
        => await TestAsync(string.Empty);
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestLiteralError1(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        // not enough lines in literal
        var v = """"""
                """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestLiteralError2(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        // invalid literal
        var v = """"""
            text too early
                """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestZeroColumn1(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
goo
""""""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestZeroColumn2(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
    goo
""""""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestOneColumn1(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
|goo
 """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestOneColumn2(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
|   goo
 """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestCase1(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
               |goo
                """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestCase2(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
               |goo
               |bar
                """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestCase3(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
               |goo
               |bar
               |baz
                """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestCase4(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
               |goo
               |
               |baz
                """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestCase5(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
           |    goo
           |
           |    baz
            """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestCase6(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v =
            $""""""
           |goo
            """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestCase7(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v =
            $""""""
            |goo
             """"""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestCase8(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v =
            $""""""""
            |goo
             """"""""{suffix};
    }}
}}");
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("u8")]
    public async Task TestCase9(string suffix)
    {
        await TestAsync($@"class C
{{
    void M()
    {{
        var v =
             """"""""
            |goo
             """"""""{suffix};
    }}
}}");
    }
 
    [Fact]
    public async Task TestCase10()
    {
        await TestAsync("""""
            class C
            {
                void M()
                {
                    var v =
                         $$""""
                        |goo
                         """";
                }
            }
            """"");
    }
 
    [Fact]
    public async Task TestCase11()
    {
        await TestAsync("""""
            class C
            {
                void M()
                {
                    var v =
                        $$""""
                        |goo
                         """";
                }
            }
            """"");
    }
 
    [Fact]
    public async Task TestCase12()
    {
        await TestAsync("""""
            class C
            {
                void M()
                {
                    var v =
                       $$""""
                        |goo
                         """";
                }
            }
            """"");
    }
 
    [Fact]
    public async Task TestWithHoles1()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var v = $"""
                       |    goo
                       |    { 1 + 1 }
                       |    baz
                        """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithHoles2()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var v = $"""
                       |    goo{
                       |    1 + 1
                       |    }baz
                        """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithHoles3()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var v = $"""
                       |    goo{
                       |1 + 1
                       |    }baz
                        """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithHoles4()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var v = $"""
                       |    goo{
                       1 + 1
                            }baz
                        """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithHoles5()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var v = $"""
                       |    goo{
                       |1 + 1
                       |    }baz
                       |    quux
                        """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithHoles6()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var v = $"""
                       |    goo{
                     1 + 1
                            }baz
                       |    quux
                        """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithHoles7()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var v = $"""
                       |goo{
                     1 + 1
                     }baz
                       |quux
                        """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithHoles8()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var v = $"""
                       |    { 1 + 1 }
                       |    baz
                        """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithHoles9()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var v = $"""
                       |    {
                       |        1 + 1
                       |    }
                       |    baz
                        """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithHoles10()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var v = $"""
                       |    {
                    1 + 1
                            }
                       |    baz
                        """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithNestedHoles1()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var x =
                        $"""
                        |goo
                        |{
                        |   $"""
                        |   |bar
                        |    """
                        |}
                        |baz
                         """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithNestedHoles2()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var x =
                        $"""
                        |goo
                        |{
                        |   $"""
                        |   |bar
                        |   |{
                        |   |   1 + 1
                        |   |}
                        |    """
                        |}
                        |baz
                         """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithNestedHoles3()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var x =
                        $"""
                        |goo
                        |{
                        |   $"""
                        |   |bar
                        |   |{
                        |   1 + 1
                        |    }
                        |    """
                        |}
                        |baz
                         """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithNestedHoles4()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var x =
                        $"""
                        |goo
                        |{
                            $"""
                            |bar
                            |{
                        1 + 1
                             }
                             """
                         }
                        |baz
                         """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithNestedHoles5()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var x =
                        $"""
                        |goo
                        |{
                    $"""
                    |bar
                     """
                         }
                        |baz
                         """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithNestedHoles6()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var x =
                        $"""
                        |goo
                        |{
                    $"""
                    |bar
                    |{
                    |   1 + 1
                    |}
                     """
                         }
                        |baz
                         """;
                }
            }
            """");
    }
 
    [Fact]
    public async Task TestWithNestedHoles7()
    {
        await TestAsync(""""
            class C
            {
                void M()
                {
                    var x =
                        $"""
                        |goo
                        |{
                    $"""
                    |bar
                    |{
                    1 + 1
                     }
                     """
                         }
                        |baz
                         """;
                }
            }
            """");
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1542623")]
    public async Task TestWithManyConcatenatedStrings()
    {
        var input = new StringBuilder(
            """
            class C
            {
                void M()
                {
                    _ =
            """);
 
        for (var i = 0; i < 2000; i++)
        {
            input.AppendLine(
                """
                        @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"" +
                """);
        }
 
        input.AppendLine(
            """
                    @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"";
                }
            }
            """);
 
        await TestAsync(input.ToString());
    }
}