File: LineSeparators\LineSeparatorTests.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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.LineSeparators;
using Microsoft.CodeAnalysis.LineSeparators;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.LineSeparators;
 
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.LineSeparators)]
public class LineSeparatorTests
{
    [Fact]
    public async Task TestEmptyFile()
        => await AssertTagsOnBracesOrSemicolonsAsync(contents: string.Empty);
 
    [Fact]
    public async Task TestEmptyClass()
    {
        var file = """
            class C
            {
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0);
    }
 
    [Fact]
    public async Task TestClassWithOneMethod()
    {
        var file = """
            class C
            {
                void M()
                {
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 1);
    }
 
    [Fact]
    public async Task TestClassWithTwoMethods()
    {
        var file = """
            class C
            {
                void M()
                {
                }
 
                void N()
                {
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
    }
 
    [Fact]
    public async Task TestClassWithTwoNonEmptyMethods()
    {
        var file = """
            class C
            {
                void M()
                {
                    N();
                }
 
                void N()
                {
                    M();
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 1, 4);
    }
 
    [Fact]
    public async Task TestClassWithMethodAndField()
    {
        var file = """
            class C
            {
                void M()
                {
                }
 
                int field;
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
    }
 
    [Fact]
    public async Task TestEmptyNamespace()
    {
        var file = """
            namespace N
            {
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0);
    }
 
    [Fact]
    public async Task TestNamespaceAndClass()
    {
        var file = """
            namespace N
            {
                class C
                {
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 1);
    }
 
    [Fact]
    public async Task TestNamespaceAndTwoClasses()
    {
        var file = """
            namespace N
            {
                class C
                {
                }
 
                class D
                {
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
    }
 
    [Fact]
    public async Task TestNamespaceAndTwoClassesAndDelegate()
    {
        var file = """
            namespace N
            {
                class C
                {
                }
 
                class D
                {
                }
 
                delegate void Del();
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 1, 3);
    }
 
    [Fact]
    public async Task TestNestedClass()
    {
        var file = """
            class C
            {
                class N
                {
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 1);
    }
 
    [Fact]
    public async Task TestTwoNestedClasses()
    {
        var file = """
            class C
            {
                class N
                {
                }
 
                class N2
                {
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
    }
 
    [Fact]
    public async Task TestStruct()
    {
        var file = """
            struct S
            {
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0);
    }
 
    [Fact]
    public async Task TestInterface()
    {
        var file = """
            interface I
            {
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0);
    }
 
    [Fact]
    public async Task TestEnum()
    {
        var file = """
            enum E
            {
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0);
    }
 
    [Fact]
    public async Task TestProperty()
    {
        var file = """
            class C
            {
                int Prop
                {
                    get
                    {
                        return 0;
                    }
                    set
                    {
                    }
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 4);
    }
 
    [Fact]
    public async Task TestPropertyAndField()
    {
        var file = """
            class C
            {
                int Prop
                {
                    get
                    {
                        return 0;
                    }
                    set
                    {
                    }
                }
 
                int field;
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 3, 5);
    }
 
    [Fact]
    public async Task TestClassWithFieldAndMethod()
    {
        var file = """
            class C
            {
                int field;
 
                void M()
                {
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
    }
 
    [Fact]
    public async Task UsingDirective()
    {
        var file = """
            using System;
 
            class C
            {
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 1);
    }
 
    [Fact]
    public async Task UsingDirectiveInNamespace()
    {
        var file = """
            namespace N
            {
                using System;
 
                class C
                {
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
    }
 
    [Fact]
    public async Task UsingDirectiveInFileScopedNamespace()
    {
        var file = """
            namespace N;
 
            using System;
 
            class C
            {
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 1);
    }
 
    [Fact]
    public async Task PropertyStyleEventDeclaration()
    {
        var file = """
            class C
            {
                event EventHandler E
                {
                    add { }
                    remove { }
                }
 
                int i;
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 2, 4);
    }
 
    [Fact]
    public async Task IndexerDeclaration()
    {
        var file = """
            class C
            {
                int this[int i]
                {
                    get { return i; }
                    set { }
                }
 
                int i;
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 3, 5);
    }
 
    [Fact]
    public async Task Constructor()
    {
        var file = """
            class C
            {
                C()
                {
                }
 
                int i;
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
    }
 
    [Fact]
    public async Task Destructor()
    {
        var file = """
            class C
            {
                ~C()
                {
                }
 
                int i;
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
    }
 
    [Fact]
    public async Task Operator()
    {
        var file = """
            class C
            {
                static C operator +(C lhs, C rhs)
                {
                }
 
                int i;
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
    }
 
    [Fact]
    public async Task ConversionOperator()
    {
        var file = """
            class C
            {
                static implicit operator C(int i)
                {
                }
 
                int i;
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
    }
 
    [Fact]
    public async Task Bug930292()
    {
        var file = """
            class Program
            {
            void A() { }
            void B() { }
            void C() { }
            void D() { }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 4);
    }
 
    [Fact]
    public async Task Bug930289()
    {
        var file = """
            namespace Roslyn.Compilers.CSharp
            {
            internal struct ArrayElement<T>
            {
            internal T Value;
            internal ArrayElement(T value) { this.Value = value; }
            public static implicit operator ArrayElement<T>(T value) { return new ArrayElement<T>(value); }
            }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 6);
    }
 
    [Fact]
    public async Task TestConsoleApp()
    {
        var file = """
            using System;
            using System.Collections.Generic;
            using System.Linq;
 
            class Program
            {
                static void Main(string[] args)
                {
                }
            }
            """;
        await AssertTagsOnBracesOrSemicolonsAsync(file, 2, 4);
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/1297")]
    public async Task ExpressionBodiedProperty()
    {
        await AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                int Prop => 3;
 
                void M()
                {
                }
            }
            """, 0, 2);
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/1297")]
    public async Task ExpressionBodiedIndexer()
    {
        await AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                int this[int i] => 3;
 
                void M()
                {
                }
            }
            """, 0, 2);
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/1297")]
    public async Task ExpressionBodiedEvent()
    {
        // This is not valid code, and parses all wrong, but just in case a user writes it.  Note
        // the 3 is because there is a skipped } in the event declaration.
        await AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                event EventHandler MyEvent => 3;
 
                void M()
                {
                }
            }
            """, 3);
    }
 
    #region Negative (incomplete) tests
 
    [Fact]
    public async Task IncompleteClass()
    {
        await AssertTagsOnBracesOrSemicolonsAsync(@"class C");
        await AssertTagsOnBracesOrSemicolonsAsync(@"class C {");
    }
 
    [Fact]
    public async Task IncompleteEnum()
    {
        await AssertTagsOnBracesOrSemicolonsAsync(@"enum E");
        await AssertTagsOnBracesOrSemicolonsAsync(@"enum E {");
    }
 
    [Fact]
    public async Task IncompleteMethod()
        => await AssertTagsOnBracesOrSemicolonsAsync(@"void goo() {");
 
    [Fact]
    public async Task IncompleteProperty()
        => await AssertTagsOnBracesOrSemicolonsAsync(@"class C { int P { get; set; void");
 
    [Fact]
    public async Task IncompleteEvent()
    {
        await AssertTagsOnBracesOrSemicolonsAsync(@"public event EventHandler");
        await AssertTagsOnBracesOrSemicolonsAsync(@"public event EventHandler {");
    }
 
    [Fact]
    public async Task IncompleteIndexer()
    {
        await AssertTagsOnBracesOrSemicolonsAsync(@"int this[int i]");
        await AssertTagsOnBracesOrSemicolonsAsync(@"int this[int i] {");
    }
 
    [Fact]
    public async Task IncompleteOperator()
    {
        // top level operators not supported in script code
        await AssertTagsOnBracesOrSemicolonsTokensAsync(@"C operator +(C lhs, C rhs) {", [], Options.Regular);
    }
 
    [Fact]
    public async Task IncompleteConversionOperator()
        => await AssertTagsOnBracesOrSemicolonsAsync(@"implicit operator C(int i) {");
 
    [Fact]
    public async Task IncompleteMember()
        => await AssertTagsOnBracesOrSemicolonsAsync(@"class C { private !C(");
 
    #endregion
 
    private static async Task AssertTagsOnBracesOrSemicolonsAsync(string contents, params int[] tokenIndices)
    {
        await AssertTagsOnBracesOrSemicolonsTokensAsync(contents, tokenIndices);
        await AssertTagsOnBracesOrSemicolonsTokensAsync(contents, tokenIndices, Options.Script);
    }
 
    private static async Task AssertTagsOnBracesOrSemicolonsTokensAsync(string contents, int[] tokenIndices, CSharpParseOptions? options = null)
    {
        using var workspace = EditorTestWorkspace.CreateCSharp(contents, options);
        var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id);
        var root = await document.GetRequiredSyntaxRootAsync(default);
 
        var lineSeparatorService = Assert.IsType<CSharpLineSeparatorService>(workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService<ILineSeparatorService>());
        var spans = await lineSeparatorService.GetLineSeparatorsAsync(document, root.FullSpan, CancellationToken.None);
        var tokens = root.DescendantTokens().Where(t => t.Kind() is SyntaxKind.CloseBraceToken or SyntaxKind.SemicolonToken);
 
        Assert.Equal(tokenIndices.Length, spans.Count());
 
        var i = 0;
        foreach (var span in spans.OrderBy(t => t.Start))
        {
            var expectedToken = tokens.ElementAt(tokenIndices[i]);
 
            var expectedSpan = expectedToken.Span;
 
            var message = string.Format("Expected to match curly {0} at span {1}.  Actual span {2}",
                                        tokenIndices[i],
                                        expectedSpan,
                                        span);
            Assert.True(expectedSpan == span, message);
            ++i;
        }
    }
}