Skip to content

Commit 3777d99

Browse files
Update 'sort usings' to always preserve the new-lines that were present in the document (#76332)
2 parents ad54ee4 + 89ebff9 commit 3777d99

File tree

5 files changed

+54
-28
lines changed

5 files changed

+54
-28
lines changed

src/EditorFeatures/Core/Organizing/OrganizeDocumentCommandHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Organizing;
3535
[Name(PredefinedCommandHandlerNames.OrganizeDocument)]
3636
[method: ImportingConstructor]
3737
[SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
38-
internal class OrganizeDocumentCommandHandler(
38+
internal sealed class OrganizeDocumentCommandHandler(
3939
IThreadingContext threadingContext,
4040
IAsynchronousOperationListenerProvider listenerProvider) :
4141
ICommandHandler<OrganizeDocumentCommandArgs>,

src/Workspaces/CSharp/Portable/OrganizeImports/CSharpOrganizeImportsService.Rewriter.cs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,21 @@ namespace Microsoft.CodeAnalysis.CSharp.OrganizeImports;
1515

1616
internal partial class CSharpOrganizeImportsService
1717
{
18-
private sealed class Rewriter : CSharpSyntaxRewriter
18+
private sealed class Rewriter(OrganizeImportsOptions options) : CSharpSyntaxRewriter
1919
{
20-
private readonly bool _placeSystemNamespaceFirst;
21-
private readonly bool _separateGroups;
22-
private readonly SyntaxTrivia _newLineTrivia;
20+
private readonly bool _placeSystemNamespaceFirst = options.PlaceSystemNamespaceFirst;
21+
private readonly bool _separateGroups = options.SeparateImportDirectiveGroups;
22+
private readonly SyntaxTrivia _fallbackTrivia = CSharpSyntaxGeneratorInternal.Instance.EndOfLine(options.NewLine);
2323

2424
public readonly IList<TextChange> TextChanges = [];
2525

26-
public Rewriter(OrganizeImportsOptions options)
27-
{
28-
_placeSystemNamespaceFirst = options.PlaceSystemNamespaceFirst;
29-
_separateGroups = options.SeparateImportDirectiveGroups;
30-
_newLineTrivia = CSharpSyntaxGeneratorInternal.Instance.EndOfLine(options.NewLine);
31-
}
32-
3326
public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node)
3427
{
3528
node = (CompilationUnitSyntax)base.VisitCompilationUnit(node)!;
3629
UsingsAndExternAliasesOrganizer.Organize(
3730
node.Externs, node.Usings,
3831
_placeSystemNamespaceFirst, _separateGroups,
39-
_newLineTrivia,
32+
_fallbackTrivia,
4033
out var organizedExternAliasList, out var organizedUsingList);
4134

4235
var result = node.WithExterns(organizedExternAliasList).WithUsings(organizedUsingList);
@@ -61,7 +54,7 @@ private BaseNamespaceDeclarationSyntax VisitBaseNamespaceDeclaration(BaseNamespa
6154
UsingsAndExternAliasesOrganizer.Organize(
6255
node.Externs, node.Usings,
6356
_placeSystemNamespaceFirst, _separateGroups,
64-
_newLineTrivia,
57+
_fallbackTrivia,
6558
out var organizedExternAliasList, out var organizedUsingList);
6659

6760
var result = node.WithExterns(organizedExternAliasList).WithUsings(organizedUsingList);

src/Workspaces/CSharp/Portable/OrganizeImports/CSharpOrganizeImportsService.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,28 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
#nullable disable
6-
75
using System;
86
using System.Composition;
97
using System.Threading;
108
using System.Threading.Tasks;
119
using Microsoft.CodeAnalysis.Host.Mef;
1210
using Microsoft.CodeAnalysis.OrganizeImports;
11+
using Roslyn.Utilities;
1312

1413
namespace Microsoft.CodeAnalysis.CSharp.OrganizeImports;
1514

1615
[ExportLanguageService(typeof(IOrganizeImportsService), LanguageNames.CSharp), Shared]
17-
internal partial class CSharpOrganizeImportsService : IOrganizeImportsService
16+
[method: ImportingConstructor]
17+
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
18+
internal sealed partial class CSharpOrganizeImportsService() : IOrganizeImportsService
1819
{
19-
[ImportingConstructor]
20-
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
21-
public CSharpOrganizeImportsService()
22-
{
23-
}
24-
2520
public async Task<Document> OrganizeImportsAsync(Document document, OrganizeImportsOptions options, CancellationToken cancellationToken)
2621
{
2722
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
2823

2924
var rewriter = new Rewriter(options);
3025
var newRoot = rewriter.Visit(root);
26+
Contract.ThrowIfNull(newRoot);
3127

3228
return document.WithSyntaxRoot(newRoot);
3329
}

src/Workspaces/CSharpTest/OrganizeImports/OrganizeUsingsTests.cs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Threading;
78
using System.Threading.Tasks;
89
using Microsoft.CodeAnalysis.OrganizeImports;
@@ -16,10 +17,11 @@ namespace Microsoft.CodeAnalysis.CSharp.Workspaces.UnitTests.OrganizeImports;
1617

1718
[UseExportProvider]
1819
[Trait(Traits.Feature, Traits.Features.Organizing)]
19-
public class OrganizeUsingsTests
20+
public sealed class OrganizeUsingsTests
2021
{
21-
protected static async Task CheckAsync(
22-
string initial, string final,
22+
private static async Task CheckAsync(
23+
string initial,
24+
string final,
2325
bool placeSystemNamespaceFirst = false,
2426
bool separateImportGroups = false,
2527
string? endOfLine = null)
@@ -76,6 +78,32 @@ public async Task AliasesAtBottom()
7678
await CheckAsync(initial, final);
7779
}
7880

81+
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/44136")]
82+
[InlineData("\n")]
83+
[InlineData("\r\n")]
84+
public async Task PreserveExistingEndOfLine(string fallbackEndOfLine)
85+
{
86+
var initial = "using A = B;\nusing C;\nusing D = E;\nusing F;\n";
87+
88+
var final = "using C;\nusing F;\nusing A = B;\nusing D = E;\n";
89+
90+
using var workspace = new AdhocWorkspace();
91+
var project = workspace.CurrentSolution.AddProject("Project", "Project.dll", LanguageNames.CSharp);
92+
var document = project.AddDocument("Document", initial);
93+
94+
var options = new OrganizeImportsOptions()
95+
{
96+
PlaceSystemNamespaceFirst = false,
97+
SeparateImportDirectiveGroups = false,
98+
NewLine = fallbackEndOfLine,
99+
};
100+
101+
var organizeImportsService = document.GetRequiredLanguageService<IOrganizeImportsService>();
102+
var newDocument = await organizeImportsService.OrganizeImportsAsync(document, options, CancellationToken.None);
103+
var newRoot = await newDocument.GetRequiredSyntaxRootAsync(default);
104+
Assert.Equal(final, newRoot.ToFullString());
105+
}
106+
79107
[Fact]
80108
public async Task UsingStaticsBetweenUsingsAndAliases()
81109
{

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/UsingsAndExternAliasesOrganizer.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,20 @@ internal static partial class UsingsAndExternAliasesOrganizer
1616
public static void Organize(
1717
SyntaxList<ExternAliasDirectiveSyntax> externAliasList,
1818
SyntaxList<UsingDirectiveSyntax> usingList,
19-
bool placeSystemNamespaceFirst, bool separateGroups,
20-
SyntaxTrivia newLineTrivia,
19+
bool placeSystemNamespaceFirst,
20+
bool separateGroups,
21+
SyntaxTrivia fallbackTrivia,
2122
out SyntaxList<ExternAliasDirectiveSyntax> organizedExternAliasList,
2223
out SyntaxList<UsingDirectiveSyntax> organizedUsingList)
2324
{
25+
// Attempt to use an existing newline trivia from the existing usings/externs. If we can't find any use what
26+
// the caller passed in.
27+
var newLineTrivia = ((IEnumerable<SyntaxNode>)externAliasList)
28+
.Concat(usingList)
29+
.Select(n => n.GetTrailingTrivia().FirstOrNull(t => t.Kind() == SyntaxKind.EndOfLineTrivia))
30+
.Where(t => t != null)
31+
.FirstOrDefault() ?? fallbackTrivia;
32+
2433
OrganizeWorker(
2534
externAliasList, usingList, placeSystemNamespaceFirst,
2635
newLineTrivia,

0 commit comments

Comments
 (0)