|
// 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;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.OrganizeImports;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.Workspaces.UnitTests.OrganizeImports;
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.Organizing)]
public sealed class OrganizeUsingsTests
{
private static async Task CheckAsync(
string initial,
string final,
bool placeSystemNamespaceFirst = false,
bool separateImportGroups = false,
string? endOfLine = null)
{
using var workspace = new AdhocWorkspace();
var project = workspace.CurrentSolution.AddProject("Project", "Project.dll", LanguageNames.CSharp);
var document = project.AddDocument("Document", initial.ReplaceLineEndings(endOfLine ?? Environment.NewLine));
var options = new OrganizeImportsOptions()
{
PlaceSystemNamespaceFirst = placeSystemNamespaceFirst,
SeparateImportDirectiveGroups = separateImportGroups,
NewLine = endOfLine ?? OrganizeImportsOptions.Default.NewLine
};
var organizeImportsService = document.GetRequiredLanguageService<IOrganizeImportsService>();
var newDocument = await organizeImportsService.OrganizeImportsAsync(document, options, CancellationToken.None);
var newRoot = await newDocument.GetRequiredSyntaxRootAsync(default);
Assert.Equal(final.ReplaceLineEndings(endOfLine ?? Environment.NewLine), newRoot.ToFullString());
}
[Fact]
public async Task EmptyFile()
=> await CheckAsync(string.Empty, string.Empty);
[Fact]
public async Task SingleUsingStatement()
{
var initial = @"using A;";
var final = initial;
await CheckAsync(initial, final);
}
[Fact]
public async Task AliasesAtBottom()
{
var initial =
"""
using A = B;
using C;
using D = E;
using F;
""";
var final =
"""
using C;
using F;
using A = B;
using D = E;
""";
await CheckAsync(initial, final);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/44136")]
[InlineData("\n")]
[InlineData("\r\n")]
public async Task PreserveExistingEndOfLine(string fallbackEndOfLine)
{
var initial = "using A = B;\nusing C;\nusing D = E;\nusing F;\n";
var final = "using C;\nusing F;\nusing A = B;\nusing D = E;\n";
using var workspace = new AdhocWorkspace();
var project = workspace.CurrentSolution.AddProject("Project", "Project.dll", LanguageNames.CSharp);
var document = project.AddDocument("Document", initial);
var options = new OrganizeImportsOptions()
{
PlaceSystemNamespaceFirst = false,
SeparateImportDirectiveGroups = false,
NewLine = fallbackEndOfLine,
};
var organizeImportsService = document.GetRequiredLanguageService<IOrganizeImportsService>();
var newDocument = await organizeImportsService.OrganizeImportsAsync(document, options, CancellationToken.None);
var newRoot = await newDocument.GetRequiredSyntaxRootAsync(default);
Assert.Equal(final, newRoot.ToFullString());
}
[Fact]
public async Task UsingStaticsBetweenUsingsAndAliases()
{
var initial =
"""
using static System.Convert;
using A = B;
using C;
using Z;
using D = E;
using static System.Console;
using F;
""";
var final =
"""
using C;
using F;
using Z;
using static System.Console;
using static System.Convert;
using A = B;
using D = E;
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task NestedStatements()
{
var initial =
"""
using B;
using A;
namespace N
{
using D;
using C;
namespace N1
{
using F;
using E;
}
namespace N2
{
using H;
using G;
}
}
namespace N3
{
using J;
using I;
namespace N4
{
using L;
using K;
}
namespace N5
{
using N;
using M;
}
}
""";
var final =
"""
using A;
using B;
namespace N
{
using C;
using D;
namespace N1
{
using E;
using F;
}
namespace N2
{
using G;
using H;
}
}
namespace N3
{
using I;
using J;
namespace N4
{
using K;
using L;
}
namespace N5
{
using M;
using N;
}
}
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task FileScopedNamespace()
{
var initial =
"""
using B;
using A;
namespace N;
using D;
using C;
""";
var final =
"""
using A;
using B;
namespace N;
using C;
using D;
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task SpecialCaseSystem()
{
var initial =
"""
using M2;
using M1;
using System.Linq;
using System;
""";
var final =
"""
using System;
using System.Linq;
using M1;
using M2;
""";
await CheckAsync(initial, final, placeSystemNamespaceFirst: true);
}
[Fact]
public async Task SpecialCaseSystemWithUsingStatic()
{
var initial =
"""
using M2;
using M1;
using System.Linq;
using System;
using static Microsoft.Win32.Registry;
using static System.BitConverter;
""";
var final =
"""
using System;
using System.Linq;
using M1;
using M2;
using static System.BitConverter;
using static Microsoft.Win32.Registry;
""";
await CheckAsync(initial, final, placeSystemNamespaceFirst: true);
}
[Fact]
public async Task DoNotSpecialCaseSystem()
{
var initial =
"""
using M2;
using M1;
using System.Linq;
using System;
""";
var final =
"""
using M1;
using M2;
using System;
using System.Linq;
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task DoNotSpecialCaseSystemWithUsingStatics()
{
var initial =
"""
using M2;
using M1;
using System.Linq;
using System;
using static Microsoft.Win32.Registry;
using static System.BitConverter;
""";
var final =
"""
using M1;
using M2;
using System;
using System.Linq;
using static Microsoft.Win32.Registry;
using static System.BitConverter;
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task IndentationAfterSorting()
{
var initial =
"""
namespace A
{
using V.W;
using U;
using X.Y.Z;
class B { }
}
namespace U { }
namespace V.W { }
namespace X.Y.Z { }
""";
var final =
"""
namespace A
{
using U;
using V.W;
using X.Y.Z;
class B { }
}
namespace U { }
namespace V.W { }
namespace X.Y.Z { }
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task DoNotTouchCommentsAtBeginningOfFile1()
{
var initial =
"""
// Copyright (c) Microsoft Corporation. All rights reserved.
using B;
// I like namespace A
using A;
namespace A { }
namespace B { }
""";
var final =
"""
// Copyright (c) Microsoft Corporation. All rights reserved.
// I like namespace A
using A;
using B;
namespace A { }
namespace B { }
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task DoNotTouchCommentsAtBeginningOfFile2()
{
var initial =
"""
/* Copyright (c) Microsoft Corporation. All rights reserved. */
using B;
/* I like namespace A */
using A;
namespace A { }
namespace B { }
""";
var final =
"""
/* Copyright (c) Microsoft Corporation. All rights reserved. */
/* I like namespace A */
using A;
using B;
namespace A { }
namespace B { }
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task DoNotTouchCommentsAtBeginningOfFile3()
{
var initial =
"""
// Copyright (c) Microsoft Corporation. All rights reserved.
using B;
/// I like namespace A
using A;
namespace A { }
namespace B { }
""";
var final =
"""
// Copyright (c) Microsoft Corporation. All rights reserved.
/// I like namespace A
using A;
using B;
namespace A { }
namespace B { }
""";
await CheckAsync(initial, final);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/33251")]
public async Task DoNotTouchCommentsAtBeginningOfFile4()
{
var initial =
"""
/// Copyright (c) Microsoft Corporation. All rights reserved.
using B;
/// I like namespace A
using A;
namespace A { }
namespace B { }
""";
var final =
"""
/// Copyright (c) Microsoft Corporation. All rights reserved.
/// I like namespace A
using A;
using B;
namespace A { }
namespace B { }
""";
await CheckAsync(initial, final);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/33251")]
public async Task DoNotTouchCommentsAtBeginningOfFile5()
{
var initial =
"""
/** Copyright (c) Microsoft Corporation. All rights reserved.
*/
using B;
/// I like namespace A
using A;
namespace A { }
namespace B { }
""";
var final =
"""
/** Copyright (c) Microsoft Corporation. All rights reserved.
*/
/// I like namespace A
using A;
using B;
namespace A { }
namespace B { }
""";
await CheckAsync(initial, final);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/2480")]
public async Task DoTouchCommentsAtBeginningOfFile1()
{
var initial =
"""
// Copyright (c) Microsoft Corporation. All rights reserved.
using B;
// I like namespace A
using A;
namespace A { }
namespace B { }
""";
var final =
"""
// Copyright (c) Microsoft Corporation. All rights reserved.
// I like namespace A
using A;
using B;
namespace A { }
namespace B { }
""";
await CheckAsync(initial, final);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/2480")]
public async Task DoTouchCommentsAtBeginningOfFile2()
{
var initial =
"""
/* Copyright (c) Microsoft Corporation. All rights reserved. */
using B;
/* I like namespace A */
using A;
namespace A { }
namespace B { }
""";
var final =
"""
/* Copyright (c) Microsoft Corporation. All rights reserved. */
/* I like namespace A */
using A;
using B;
namespace A { }
namespace B { }
""";
await CheckAsync(initial, final);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/2480")]
public async Task DoTouchCommentsAtBeginningOfFile3()
{
var initial =
"""
/// Copyright (c) Microsoft Corporation. All rights reserved.
using B;
/// I like namespace A
using A;
namespace A { }
namespace B { }
""";
var final =
"""
/// I like namespace A
using A;
/// Copyright (c) Microsoft Corporation. All rights reserved.
using B;
namespace A { }
namespace B { }
""";
await CheckAsync(initial, final);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/2480")]
public async Task CommentsNotAtTheStartOfTheFile1()
{
var initial =
"""
namespace N
{
// attached to System.Text
using System.Text;
// attached to System
using System;
}
""";
var final =
"""
namespace N
{
// attached to System
using System;
// attached to System.Text
using System.Text;
}
""";
await CheckAsync(initial, final);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/2480")]
public async Task CommentsNotAtTheStartOfTheFile2()
{
var initial =
"""
namespace N
{
// not attached to System.Text
using System.Text;
// attached to System
using System;
}
""";
var final =
"""
namespace N
{
// not attached to System.Text
// attached to System
using System;
using System.Text;
}
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task DoNotSortIfEndIfBlocks()
{
var initial =
"""
using D;
#if MYCONFIG
using C;
#else
using B;
#endif
using A;
namespace A { }
namespace B { }
namespace C { }
namespace D { }
""";
var final = initial;
await CheckAsync(initial, final);
}
[Fact]
public async Task ExternAliases()
{
var initial =
"""
extern alias Z;
extern alias Y;
extern alias X;
using C;
using U = C.L.T;
using O = A.J;
using A;
using W = A.J.R;
using N = B.K;
using V = B.K.S;
using M = C.L;
using B;
namespace A
{
namespace J
{
class R { }
}
}
namespace B
{
namespace K
{
struct S { }
}
}
namespace C
{
namespace L
{
struct T { }
}
}
""";
var final =
"""
extern alias X;
extern alias Y;
extern alias Z;
using A;
using B;
using C;
using M = C.L;
using N = B.K;
using O = A.J;
using U = C.L.T;
using V = B.K.S;
using W = A.J.R;
namespace A
{
namespace J
{
class R { }
}
}
namespace B
{
namespace K
{
struct S { }
}
}
namespace C
{
namespace L
{
struct T { }
}
}
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task DuplicateUsings()
{
var initial =
"""
using A;
using A;
""";
var final = initial;
await CheckAsync(initial, final);
}
[Fact]
public async Task InlineComments()
{
var initial =
"""
/*00*/using/*01*/D/*02*/;/*03*/
/*04*/using/*05*/C/*06*/;/*07*/
/*08*/using/*09*/A/*10*/;/*11*/
/*12*/using/*13*/B/*14*/;/*15*/
/*16*/
""";
var final =
"""
/*08*/using/*09*/A/*10*/;/*11*/
/*12*/using/*13*/B/*14*/;/*15*/
/*04*/using/*05*/C/*06*/;/*07*/
/*00*/using/*01*/D/*02*/;/*03*/
/*16*/
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task AllOnOneLine()
{
var initial =
@"using C; using B; using A;";
var final =
"""
using A;
using B;
using C;
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task InsideRegionBlock()
{
var initial =
"""
#region Using directives
using C;
using A;
using B;
#endregion
class Class1
{
}
""";
var final =
"""
#region Using directives
using A;
using B;
using C;
#endregion
class Class1
{
}
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task NestedRegionBlock()
{
var initial =
"""
using C;
#region Z
using A;
#endregion
using B;
""";
var final = initial;
await CheckAsync(initial, final);
}
[Fact]
public async Task MultipleRegionBlocks()
{
var initial =
"""
#region Using directives
using C;
#region Z
using A;
#endregion
using B;
#endregion
""";
var final = initial;
await CheckAsync(initial, final);
}
[Fact]
public async Task InterleavedNewlines()
{
var initial =
"""
using B;
using A;
using C;
class D { }
""";
var final =
"""
using A;
using B;
using C;
class D { }
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task InsideIfEndIfBlock()
{
var initial =
"""
#if !X
using B;
using A;
using C;
#endif
""";
var final =
"""
#if !X
using A;
using B;
using C;
#endif
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task IfEndIfBlockAbove()
{
var initial =
"""
#if !X
using C;
using B;
using F;
#endif
using D;
using A;
using E;
""";
var final = initial;
await CheckAsync(initial, final);
}
[Fact]
public async Task IfEndIfBlockMiddle()
{
var initial =
"""
using D;
using A;
using H;
#if !X
using C;
using B;
using I;
#endif
using F;
using E;
using G;
""";
var final = initial;
await CheckAsync(initial, final);
}
[Fact]
public async Task IfEndIfBlockBelow()
{
var initial =
"""
using D;
using A;
using E;
#if !X
using C;
using B;
using F;
#endif
""";
var final = initial;
await CheckAsync(initial, final);
}
[Fact]
public async Task Korean()
{
var initial =
"""
using 하;
using 파;
using 타;
using 카;
using 차;
using 자;
using 아;
using 사;
using 바;
using 마;
using 라;
using 다;
using 나;
using 가;
""";
var final =
"""
using 가;
using 나;
using 다;
using 라;
using 마;
using 바;
using 사;
using 아;
using 자;
using 차;
using 카;
using 타;
using 파;
using 하;
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task DoNotSpecialCaseSystem1()
{
var initial =
"""
using B;
using System.Collections.Generic;
using C;
using _System;
using SystemZ;
using D.System;
using System;
using System.Collections;
using A;
""";
var final =
"""
using _System;
using A;
using B;
using C;
using D.System;
using System;
using System.Collections;
using System.Collections.Generic;
using SystemZ;
""";
await CheckAsync(initial, final, placeSystemNamespaceFirst: false);
}
[Fact]
public async Task DoNotSpecialCaseSystem2()
{
var initial =
"""
extern alias S;
extern alias R;
extern alias T;
using B;
using System.Collections.Generic;
using C;
using _System;
using SystemZ;
using Y = System.UInt32;
using Z = System.Int32;
using D.System;
using System;
using N = System;
using M = System.Collections;
using System.Collections;
using A;
""";
var final =
"""
extern alias R;
extern alias S;
extern alias T;
using _System;
using A;
using B;
using C;
using D.System;
using System;
using System.Collections;
using System.Collections.Generic;
using SystemZ;
using M = System.Collections;
using N = System;
using Y = System.UInt32;
using Z = System.Int32;
""";
await CheckAsync(initial, final, placeSystemNamespaceFirst: false);
}
[Fact]
public async Task CaseSensitivity1()
{
var initial =
"""
using Bb;
using B;
using bB;
using b;
using Aa;
using a;
using A;
using aa;
using aA;
using AA;
using bb;
using BB;
using bBb;
using bbB;
using あ;
using ア;
using ア;
using ああ;
using あア;
using あア;
using アあ;
using cC;
using Cc;
using アア;
using アア;
using アあ;
using アア;
using アア;
using BBb;
using BbB;
using bBB;
using BBB;
using c;
using C;
using bbb;
using Bbb;
using cc;
using cC;
using CC;
""";
string sortedKana;
if (GlobalizationUtilities.ICUMode())
{
sortedKana =
"""
using あ;
using ア;
using ああ;
using あア;
using アあ;
using アア;
using あア;
using アア;
using ア;
using アあ;
using アア;
using アア;
""";
}
else
{
sortedKana =
"""
using ア;
using ア;
using あ;
using アア;
using アア;
using アア;
using アア;
using アあ;
using アあ;
using あア;
using あア;
using ああ;
""";
}
var final =
$"""
using a;
using A;
using aa;
using aA;
using Aa;
using AA;
using b;
using B;
using bb;
using bB;
using Bb;
using BB;
using bbb;
using bbB;
using bBb;
using bBB;
using Bbb;
using BbB;
using BBb;
using BBB;
using c;
using C;
using cc;
using cC;
using cC;
using Cc;
using CC;
{sortedKana}
""";
await CheckAsync(initial, final);
}
[Fact]
public async Task CaseSensitivity2()
{
var initial =
"""
using あ;
using ア;
using ア;
using ああ;
using あア;
using あア;
using アあ;
using アア;
using アア;
using アあ;
using アア;
using アア;
""";
if (GlobalizationUtilities.ICUMode())
{
await CheckAsync(initial,
"""
using あ;
using ア;
using ああ;
using あア;
using アあ;
using アア;
using あア;
using アア;
using ア;
using アあ;
using アア;
using アア;
""");
}
else
{
await CheckAsync(initial,
"""
using ア;
using ア;
using あ;
using アア;
using アア;
using アア;
using アア;
using アあ;
using アあ;
using あア;
using あア;
using ああ;
""");
}
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/20988")]
public async Task TestGrouping()
{
var initial =
"""
// Banner
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using IntList = System.Collections.Generic.List<int>;
using static System.Console;
""";
var final =
"""
// Banner
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using static System.Console;
using IntList = System.Collections.Generic.List<int>;
""";
await CheckAsync(initial, final, placeSystemNamespaceFirst: true, separateImportGroups: true);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/20988")]
public async Task TestGrouping2()
{
// Make sure we don't insert extra newlines if they're already there.
var initial =
"""
// Banner
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using static System.Console;
using IntList = System.Collections.Generic.List<int>;
""";
var final =
"""
// Banner
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using static System.Console;
using IntList = System.Collections.Generic.List<int>;
""";
await CheckAsync(initial, final, placeSystemNamespaceFirst: true, separateImportGroups: true);
}
[Theory, WorkItem(20988, "https://github.com/dotnet/roslyn/issues/19306")]
[InlineData("\n")]
[InlineData("\r\n")]
public async Task TestGrouping3(string endOfLine)
{
var initial =
"""
// Banner
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using IntList = System.Collections.Generic.List<int>;
using static System.Console;
""";
var final =
"""
// Banner
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using static System.Console;
using IntList = System.Collections.Generic.List<int>;
""";
await CheckAsync(initial, final, placeSystemNamespaceFirst: true, separateImportGroups: true, endOfLine: endOfLine);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71502")]
public async Task GlobalsBeforeNonGlobals()
{
var initial = """
using A = B;
using static C;
using X;
global using A = B;
global using static C;
global using X;
""";
var final = """
global using X;
global using static C;
global using A = B;
using X;
using static C;
using A = B;
""";
await CheckAsync(initial, final);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71502")]
public async Task GlobalNonNamedAliases1()
{
var initial = """
global using B = (int, string);
global using A = int;
""";
var final = """
global using A = int;
global using B = (int, string);
""";
await CheckAsync(initial, final);
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71502")]
public async Task GlobalNonNamedAliases2()
{
var initial = """
global using unsafe DataLogWriteFunc = delegate* unmanaged[Cdecl]<void*, byte*, nuint, void>;
global using unsafe DataLogHandle = void*;
""";
var final = """
global using unsafe DataLogHandle = void*;
global using unsafe DataLogWriteFunc = delegate* unmanaged[Cdecl]<void*, byte*, nuint, void>;
""";
await CheckAsync(initial, final);
}
}
|