|
// 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;
}
}
}
|