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 sealed class LineSeparatorTests
{
    [Fact]
    public async Task TestEmptyFile()
        => await AssertTagsOnBracesOrSemicolonsAsync(contents: string.Empty);
 
    [Fact]
    public Task TestEmptyClass()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
            }
            """, 0);
 
    [Fact]
    public Task TestClassWithOneMethod()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                void M()
                {
                }
            }
            """, 1);
 
    [Fact]
    public Task TestClassWithTwoMethods()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                void M()
                {
                }
 
                void N()
                {
                }
            }
            """, 0, 2);
 
    [Fact]
    public Task TestClassWithTwoNonEmptyMethods()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                void M()
                {
                    N();
                }
 
                void N()
                {
                    M();
                }
            }
            """, 1, 4);
 
    [Fact]
    public Task TestClassWithMethodAndField()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                void M()
                {
                }
 
                int field;
            }
            """, 0, 2);
 
    [Fact]
    public Task TestEmptyNamespace()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            namespace N
            {
            }
            """, 0);
 
    [Fact]
    public Task TestNamespaceAndClass()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            namespace N
            {
                class C
                {
                }
            }
            """, 1);
 
    [Fact]
    public Task TestNamespaceAndTwoClasses()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            namespace N
            {
                class C
                {
                }
 
                class D
                {
                }
            }
            """, 0, 2);
 
    [Fact]
    public Task TestNamespaceAndTwoClassesAndDelegate()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            namespace N
            {
                class C
                {
                }
 
                class D
                {
                }
 
                delegate void Del();
            }
            """, 0, 1, 3);
 
    [Fact]
    public Task TestNestedClass()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                class N
                {
                }
            }
            """, 1);
 
    [Fact]
    public Task TestTwoNestedClasses()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                class N
                {
                }
 
                class N2
                {
                }
            }
            """, 0, 2);
 
    [Fact]
    public Task TestStruct()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            struct S
            {
            }
            """, 0);
 
    [Fact]
    public Task TestInterface()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            interface I
            {
            }
            """, 0);
 
    [Fact]
    public Task TestEnum()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            enum E
            {
            }
            """, 0);
 
    [Fact]
    public Task TestProperty()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                int Prop
                {
                    get
                    {
                        return 0;
                    }
                    set
                    {
                    }
                }
            }
            """, 4);
 
    [Fact]
    public Task TestPropertyAndField()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                int Prop
                {
                    get
                    {
                        return 0;
                    }
                    set
                    {
                    }
                }
 
                int field;
            }
            """, 3, 5);
 
    [Fact]
    public Task TestClassWithFieldAndMethod()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                int field;
 
                void M()
                {
                }
            }
            """, 0, 2);
 
    [Fact]
    public Task UsingDirective()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            using System;
 
            class C
            {
            }
            """, 0, 1);
 
    [Fact]
    public Task UsingDirectiveInNamespace()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            namespace N
            {
                using System;
 
                class C
                {
                }
            }
            """, 0, 2);
 
    [Fact]
    public Task UsingDirectiveInFileScopedNamespace()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            namespace N;
 
            using System;
 
            class C
            {
            }
            """, 1);
 
    [Fact]
    public Task PropertyStyleEventDeclaration()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                event EventHandler E
                {
                    add { }
                    remove { }
                }
 
                int i;
            }
            """, 2, 4);
 
    [Fact]
    public Task IndexerDeclaration()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                int this[int i]
                {
                    get { return i; }
                    set { }
                }
 
                int i;
            }
            """, 3, 5);
 
    [Fact]
    public Task Constructor()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                C()
                {
                }
 
                int i;
            }
            """, 0, 2);
 
    [Fact]
    public Task Destructor()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                ~C()
                {
                }
 
                int i;
            }
            """, 0, 2);
 
    [Fact]
    public Task Operator()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                static C operator +(C lhs, C rhs)
                {
                }
 
                int i;
            }
            """, 0, 2);
 
    [Fact]
    public Task ConversionOperator()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                static implicit operator C(int i)
                {
                }
 
                int i;
            }
            """, 0, 2);
 
    [Fact]
    public Task Bug930292()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class Program
            {
            void A() { }
            void B() { }
            void C() { }
            void D() { }
            }
            """, 4);
 
    [Fact]
    public Task Bug930289()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            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); }
            }
            }
            """, 6);
 
    [Fact]
    public Task TestConsoleApp()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            using System;
            using System.Collections.Generic;
            using System.Linq;
 
            class Program
            {
                static void Main(string[] args)
                {
                }
            }
            """, 2, 4);
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/1297")]
    public Task ExpressionBodiedProperty()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                int Prop => 3;
 
                void M()
                {
                }
            }
            """, 0, 2);
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/1297")]
    public Task ExpressionBodiedIndexer()
        => AssertTagsOnBracesOrSemicolonsAsync("""
            class C
            {
                int this[int i] => 3;
 
                void M()
                {
                }
            }
            """, 0, 2);
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/1297")]
    public Task ExpressionBodiedEvent()
        => 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 Task IncompleteOperator()
        => 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.Length);
 
        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;
        }
    }
}