提交 8285d42f 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #21087 from CyrusNajmabadi/groupingBetweenImports

Add user facing option to place blank lines between using/import groups.
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Editing;
...@@ -14,13 +15,19 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Organizing ...@@ -14,13 +15,19 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Organizing
{ {
public class OrganizeUsingsTests public class OrganizeUsingsTests
{ {
protected async Task CheckAsync(string initial, string final, bool placeSystemNamespaceFirst = false, CSharpParseOptions options = null) protected async Task CheckAsync(
string initial, string final,
bool placeSystemNamespaceFirst = false,
bool separateImportGroups = false,
CSharpParseOptions options = null)
{ {
using (var workspace = TestWorkspace.CreateCSharp(initial)) using (var workspace = TestWorkspace.CreateCSharp(initial))
{ {
var document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id); var document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id);
workspace.Options = workspace.Options.WithChangedOption(new OptionKey(GenerationOptions.PlaceSystemNamespaceFirst, document.Project.Language), placeSystemNamespaceFirst); workspace.Options = workspace.Options.WithChangedOption(new OptionKey(GenerationOptions.PlaceSystemNamespaceFirst, document.Project.Language), placeSystemNamespaceFirst);
var newRoot = await (await OrganizeImportsService.OrganizeImportsAsync(document)).GetSyntaxRootAsync(); workspace.Options = workspace.Options.WithChangedOption(new OptionKey(GenerationOptions.SeparateImportDirectiveGroups, document.Project.Language), separateImportGroups);
var newRoot = await (await OrganizeImportsService.OrganizeImportsAsync(document, CancellationToken.None)).GetSyntaxRootAsync();
Assert.Equal(final.NormalizeLineEndings(), newRoot.ToFullString()); Assert.Equal(final.NormalizeLineEndings(), newRoot.ToFullString());
} }
} }
...@@ -1027,5 +1034,84 @@ public async Task CaseSensitivity2() ...@@ -1027,5 +1034,84 @@ public async Task CaseSensitivity2()
await CheckAsync(initial, final); await CheckAsync(initial, final);
} }
[WorkItem(20988, "https://github.com/dotnet/roslyn/issues/20988")]
[Fact, Trait(Traits.Feature, Traits.Features.Organizing)]
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);
}
[WorkItem(20988, "https://github.com/dotnet/roslyn/issues/20988")]
[Fact, Trait(Traits.Feature, Traits.Features.Organizing)]
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);
}
} }
} }
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Threading
Imports System.Xml.Linq Imports System.Xml.Linq
Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions
...@@ -9,11 +10,15 @@ Imports Microsoft.CodeAnalysis.OrganizeImports ...@@ -9,11 +10,15 @@ Imports Microsoft.CodeAnalysis.OrganizeImports
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Organizing Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Organizing
Public Class OrganizeImportsTests Public Class OrganizeImportsTests
Private Async Function CheckAsync(initial As XElement, final As XElement, Optional placeSystemNamespaceFirst As Boolean = False) As Task Private Async Function CheckAsync(initial As XElement, final As XElement,
Optional placeSystemNamespaceFirst As Boolean = False,
Optional separateImportGroups As Boolean = False) As Task
Using workspace = TestWorkspace.CreateVisualBasic(initial.NormalizedValue) Using workspace = TestWorkspace.CreateVisualBasic(initial.NormalizedValue)
Dim document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id) Dim document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id)
workspace.Options = workspace.Options.WithChangedOption(New OptionKey(GenerationOptions.PlaceSystemNamespaceFirst, document.Project.Language), placeSystemNamespaceFirst) workspace.Options = workspace.Options.WithChangedOption(New OptionKey(GenerationOptions.PlaceSystemNamespaceFirst, document.Project.Language), placeSystemNamespaceFirst)
Dim newRoot = Await (Await OrganizeImportsService.OrganizeImportsAsync(document)).GetSyntaxRootAsync() workspace.Options = workspace.Options.WithChangedOption(New OptionKey(GenerationOptions.SeparateImportDirectiveGroups, document.Project.Language), separateImportGroups)
Dim newRoot = Await (Await OrganizeImportsService.OrganizeImportsAsync(document, CancellationToken.None)).GetSyntaxRootAsync()
Assert.Equal(final.NormalizedValue, newRoot.ToFullString()) Assert.Equal(final.NormalizedValue, newRoot.ToFullString())
End Using End Using
End Function End Function
...@@ -678,5 +683,90 @@ Imports ああ ...@@ -678,5 +683,90 @@ Imports ああ
Await CheckAsync(initial, final) Await CheckAsync(initial, final)
End Function End Function
<WorkItem(20988, "https://github.com/dotnet/roslyn/issues/20988")>
<Fact, Trait(Traits.Feature, Traits.Features.Organizing)>
Public Async Function TestGrouping() As Task
Dim initial =
<content><![CDATA[' Banner
Imports Microsoft.CodeAnalysis.CSharp.Extensions
Imports Microsoft.CodeAnalysis.CSharp.Syntax
Imports System.Collections.Generic
Imports System.Linq
Imports Microsoft.CodeAnalysis.Shared.Extensions
Imports <xmlns:ab="http://NewNamespace">
Imports <xmlns="http://DefaultNamespace">
Imports Roslyn.Utilities
Imports IntList = System.Collections.Generic.List(Of Integer)
Imports <xmlns:zz="http://NextNamespace">
]]></content>
Dim final =
<content><![CDATA[' Banner
Imports System.Collections.Generic
Imports System.Linq
Imports Microsoft.CodeAnalysis.CSharp.Extensions
Imports Microsoft.CodeAnalysis.CSharp.Syntax
Imports Microsoft.CodeAnalysis.Shared.Extensions
Imports Roslyn.Utilities
Imports IntList = System.Collections.Generic.List(Of Integer)
Imports <xmlns:ab="http://NewNamespace">
Imports <xmlns="http://DefaultNamespace">
Imports <xmlns:zz="http://NextNamespace">
]]></content>
Await CheckAsync(initial, final, placeSystemNamespaceFirst:=True, separateImportGroups:=True)
End Function
<WorkItem(20988, "https://github.com/dotnet/roslyn/issues/20988")>
<Fact, Trait(Traits.Feature, Traits.Features.Organizing)>
Public Async Function TestGrouping2() As Task
' Make sure we don't insert extra newlines if they're already there.
Dim initial =
<content><![CDATA[' Banner
Imports System.Collections.Generic
Imports System.Linq
Imports Microsoft.CodeAnalysis.CSharp.Extensions
Imports Microsoft.CodeAnalysis.CSharp.Syntax
Imports Microsoft.CodeAnalysis.Shared.Extensions
Imports Roslyn.Utilities
Imports IntList = System.Collections.Generic.List(Of Integer)
Imports <xmlns:ab="http://NewNamespace">
Imports <xmlns="http://DefaultNamespace">
Imports <xmlns:zz="http://NextNamespace">
]]></content>
Dim final =
<content><![CDATA[' Banner
Imports System.Collections.Generic
Imports System.Linq
Imports Microsoft.CodeAnalysis.CSharp.Extensions
Imports Microsoft.CodeAnalysis.CSharp.Syntax
Imports Microsoft.CodeAnalysis.Shared.Extensions
Imports Roslyn.Utilities
Imports IntList = System.Collections.Generic.List(Of Integer)
Imports <xmlns:ab="http://NewNamespace">
Imports <xmlns="http://DefaultNamespace">
Imports <xmlns:zz="http://NextNamespace">
]]></content>
Await CheckAsync(initial, final, placeSystemNamespaceFirst:=True, separateImportGroups:=True)
End Function
End Class End Class
End Namespace End Namespace
...@@ -13,18 +13,23 @@ internal partial class CSharpOrganizeImportsService ...@@ -13,18 +13,23 @@ internal partial class CSharpOrganizeImportsService
private class Rewriter : CSharpSyntaxRewriter private class Rewriter : CSharpSyntaxRewriter
{ {
private readonly bool _placeSystemNamespaceFirst; private readonly bool _placeSystemNamespaceFirst;
private readonly bool _separateGroups;
public readonly IList<TextChange> TextChanges = new List<TextChange>(); public readonly IList<TextChange> TextChanges = new List<TextChange>();
public Rewriter(bool placeSystemNamespaceFirst) public Rewriter(bool placeSystemNamespaceFirst,
bool separateGroups)
{ {
_placeSystemNamespaceFirst = placeSystemNamespaceFirst; _placeSystemNamespaceFirst = placeSystemNamespaceFirst;
_separateGroups = separateGroups;
} }
public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node)
{ {
node = (CompilationUnitSyntax)base.VisitCompilationUnit(node); node = (CompilationUnitSyntax)base.VisitCompilationUnit(node);
UsingsAndExternAliasesOrganizer.Organize( UsingsAndExternAliasesOrganizer.Organize(
node.Externs, node.Usings, _placeSystemNamespaceFirst, node.Externs, node.Usings,
_placeSystemNamespaceFirst, _separateGroups,
out var organizedExternAliasList, out var organizedUsingList); out var organizedExternAliasList, out var organizedUsingList);
var result = node.WithExterns(organizedExternAliasList).WithUsings(organizedUsingList); var result = node.WithExterns(organizedExternAliasList).WithUsings(organizedUsingList);
...@@ -41,7 +46,8 @@ public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax ...@@ -41,7 +46,8 @@ public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax
{ {
node = (NamespaceDeclarationSyntax)base.VisitNamespaceDeclaration(node); node = (NamespaceDeclarationSyntax)base.VisitNamespaceDeclaration(node);
UsingsAndExternAliasesOrganizer.Organize( UsingsAndExternAliasesOrganizer.Organize(
node.Externs, node.Usings, _placeSystemNamespaceFirst, node.Externs, node.Usings,
_placeSystemNamespaceFirst, _separateGroups,
out var organizedExternAliasList, out var organizedUsingList); out var organizedExternAliasList, out var organizedUsingList);
var result = node.WithExterns(organizedExternAliasList).WithUsings(organizedUsingList); var result = node.WithExterns(organizedExternAliasList).WithUsings(organizedUsingList);
......
...@@ -16,8 +16,11 @@ public async Task<Document> OrganizeImportsAsync(Document document, Cancellation ...@@ -16,8 +16,11 @@ public async Task<Document> OrganizeImportsAsync(Document document, Cancellation
{ {
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var placeSystemNamespaceFirst = options.GetOption(GenerationOptions.PlaceSystemNamespaceFirst); var placeSystemNamespaceFirst = options.GetOption(GenerationOptions.PlaceSystemNamespaceFirst);
var rewriter = new Rewriter(placeSystemNamespaceFirst); var blankLineBetweenGroups = options.GetOption(GenerationOptions.SeparateImportDirectiveGroups);
var rewriter = new Rewriter(placeSystemNamespaceFirst, blankLineBetweenGroups);
var newRoot = rewriter.Visit(root); var newRoot = rewriter.Visit(root);
return document.WithSyntaxRoot(newRoot); return document.WithSyntaxRoot(newRoot);
......
...@@ -8,9 +8,7 @@ namespace Microsoft.CodeAnalysis.OrganizeImports ...@@ -8,9 +8,7 @@ namespace Microsoft.CodeAnalysis.OrganizeImports
{ {
internal static partial class OrganizeImportsService internal static partial class OrganizeImportsService
{ {
public static Task<Document> OrganizeImportsAsync(Document document, CancellationToken cancellationToken = default) public static Task<Document> OrganizeImportsAsync(Document document, CancellationToken cancellationToken)
{ => document.GetLanguageService<IOrganizeImportsService>().OrganizeImportsAsync(document, cancellationToken);
return document.GetLanguageService<IOrganizeImportsService>().OrganizeImportsAsync(document, cancellationToken);
}
} }
} }
...@@ -10,15 +10,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.OrganizeImports ...@@ -10,15 +10,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.OrganizeImports
Inherits VisualBasicSyntaxRewriter Inherits VisualBasicSyntaxRewriter
Private ReadOnly _placeSystemNamespaceFirst As Boolean Private ReadOnly _placeSystemNamespaceFirst As Boolean
Private ReadOnly _separateGroups As Boolean
Public ReadOnly TextChanges As IList(Of TextChange) = New List(Of TextChange)() Public ReadOnly TextChanges As IList(Of TextChange) = New List(Of TextChange)()
Public Sub New(placeSystemNamespaceFirst As Boolean) Public Sub New(placeSystemNamespaceFirst As Boolean, separateGroups As Boolean)
Me._placeSystemNamespaceFirst = placeSystemNamespaceFirst _placeSystemNamespaceFirst = placeSystemNamespaceFirst
_separateGroups = separateGroups
End Sub End Sub
Public Overrides Function VisitCompilationUnit(node As CompilationUnitSyntax) As SyntaxNode Public Overrides Function VisitCompilationUnit(node As CompilationUnitSyntax) As SyntaxNode
node = DirectCast(MyBase.VisitCompilationUnit(node), CompilationUnitSyntax) node = DirectCast(MyBase.VisitCompilationUnit(node), CompilationUnitSyntax)
Dim organizedImports = ImportsOrganizer.Organize(node.Imports, _placeSystemNamespaceFirst) Dim organizedImports = ImportsOrganizer.Organize(
node.Imports, _placeSystemNamespaceFirst, _separateGroups)
Dim result = node.WithImports(organizedImports) Dim result = node.WithImports(organizedImports)
If result IsNot node Then If result IsNot node Then
......
...@@ -15,8 +15,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.OrganizeImports ...@@ -15,8 +15,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.OrganizeImports
cancellationToken As CancellationToken) As Task(Of Document) Implements IOrganizeImportsService.OrganizeImportsAsync cancellationToken As CancellationToken) As Task(Of Document) Implements IOrganizeImportsService.OrganizeImportsAsync
Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
Dim options = Await document.GetOptionsAsync(cancellationToken).ConfigureAwait(False) Dim options = Await document.GetOptionsAsync(cancellationToken).ConfigureAwait(False)
Dim placeSystemNamespaceFirst = options.GetOption(GenerationOptions.PlaceSystemNamespaceFirst) Dim placeSystemNamespaceFirst = options.GetOption(GenerationOptions.PlaceSystemNamespaceFirst)
Dim rewriter = New Rewriter(placeSystemNamespaceFirst) Dim separateGroups = options.GetOption(GenerationOptions.SeparateImportDirectiveGroups)
Dim rewriter = New Rewriter(placeSystemNamespaceFirst, separateGroups)
Dim newRoot = rewriter.Visit(root) Dim newRoot = rewriter.Visit(root)
Return document.WithSyntaxRoot(newRoot) Return document.WithSyntaxRoot(newRoot)
End Function End Function
......
...@@ -1059,6 +1059,15 @@ internal class CSharpVSResources { ...@@ -1059,6 +1059,15 @@ internal class CSharpVSResources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Separate using directive groups.
/// </summary>
internal static string Separate_using_directive_groups {
get {
return ResourceManager.GetString("Separate_using_directive_groups", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Set other spacing options. /// Looks up a localized string similar to Set other spacing options.
/// </summary> /// </summary>
......
...@@ -525,4 +525,7 @@ ...@@ -525,4 +525,7 @@
<data name="Report_invalid_placeholders_in_string_dot_format_calls" xml:space="preserve"> <data name="Report_invalid_placeholders_in_string_dot_format_calls" xml:space="preserve">
<value>Report invalid placeholders in 'string.Format' calls</value> <value>Report invalid placeholders in 'string.Format' calls</value>
</data> </data>
<data name="Separate_using_directive_groups" xml:space="preserve">
<value>Separate using directive groups</value>
</data>
</root> </root>
\ No newline at end of file
...@@ -27,6 +27,9 @@ ...@@ -27,6 +27,9 @@
<CheckBox x:Name="PlaceSystemNamespaceFirst" <CheckBox x:Name="PlaceSystemNamespaceFirst"
x:Uid="SortUsings_PlaceSystemFirst" x:Uid="SortUsings_PlaceSystemFirst"
Content="{x:Static local:AdvancedOptionPageStrings.Option_PlaceSystemNamespaceFirst}" /> Content="{x:Static local:AdvancedOptionPageStrings.Option_PlaceSystemNamespaceFirst}" />
<CheckBox x:Name="SeparateImportGroups"
x:Uid="SeparateImportGroups"
Content="{x:Static local:AdvancedOptionPageStrings.Option_SeparateImportGroups}" />
<CheckBox x:Name="SuggestForTypesInReferenceAssemblies" <CheckBox x:Name="SuggestForTypesInReferenceAssemblies"
x:Uid="AddImport_SuggestForTypesInReferenceAssemblies" x:Uid="AddImport_SuggestForTypesInReferenceAssemblies"
Content="{x:Static local:AdvancedOptionPageStrings.Option_Suggest_usings_for_types_in_reference_assemblies}" /> Content="{x:Static local:AdvancedOptionPageStrings.Option_Suggest_usings_for_types_in_reference_assemblies}" />
......
...@@ -26,6 +26,7 @@ public AdvancedOptionPageControl(IServiceProvider serviceProvider) : base(servic ...@@ -26,6 +26,7 @@ public AdvancedOptionPageControl(IServiceProvider serviceProvider) : base(servic
BindToOption(Perform_editor_feature_analysis_in_external_process, RemoteFeatureOptions.OutOfProcessAllowed); BindToOption(Perform_editor_feature_analysis_in_external_process, RemoteFeatureOptions.OutOfProcessAllowed);
BindToOption(PlaceSystemNamespaceFirst, GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.CSharp); BindToOption(PlaceSystemNamespaceFirst, GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.CSharp);
BindToOption(SeparateImportGroups, GenerationOptions.SeparateImportDirectiveGroups, LanguageNames.CSharp);
BindToOption(SuggestForTypesInReferenceAssemblies, SymbolSearchOptions.SuggestForTypesInReferenceAssemblies, LanguageNames.CSharp); BindToOption(SuggestForTypesInReferenceAssemblies, SymbolSearchOptions.SuggestForTypesInReferenceAssemblies, LanguageNames.CSharp);
BindToOption(SuggestForTypesInNuGetPackages, SymbolSearchOptions.SuggestForTypesInNuGetPackages, LanguageNames.CSharp); BindToOption(SuggestForTypesInNuGetPackages, SymbolSearchOptions.SuggestForTypesInNuGetPackages, LanguageNames.CSharp);
BindToOption(Split_string_literals_on_enter, SplitStringLiteralOptions.Enabled, LanguageNames.CSharp); BindToOption(Split_string_literals_on_enter, SplitStringLiteralOptions.Enabled, LanguageNames.CSharp);
......
...@@ -149,9 +149,10 @@ public static string Option_Performance ...@@ -149,9 +149,10 @@ public static string Option_Performance
} }
public static string Option_PlaceSystemNamespaceFirst public static string Option_PlaceSystemNamespaceFirst
{ => CSharpVSResources.Place_System_directives_first_when_sorting_usings;
get { return CSharpVSResources.Place_System_directives_first_when_sorting_usings; }
} public static string Option_SeparateImportGroups
=> CSharpVSResources.Separate_using_directive_groups;
public static string Option_Using_Directives => public static string Option_Using_Directives =>
CSharpVSResources.Using_Directives; CSharpVSResources.Using_Directives;
......
...@@ -460,6 +460,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic ...@@ -460,6 +460,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic
End Get End Get
End Property End Property
'''<summary>
''' Looks up a localized string similar to Separate import directive groups.
'''</summary>
Friend Shared ReadOnly Property Separate_import_directive_groups() As String
Get
Return ResourceManager.GetString("Separate_import_directive_groups", resourceCulture)
End Get
End Property
'''<summary> '''<summary>
''' Looks up a localized string similar to Show completion item _filters. ''' Looks up a localized string similar to Show completion item _filters.
'''</summary> '''</summary>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
Example: Example:
... ado.net/XML headers & schema ... ... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader <resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader> <resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
...@@ -276,4 +276,7 @@ ...@@ -276,4 +276,7 @@
<data name="Report_invalid_placeholders_in_string_dot_format_calls" xml:space="preserve"> <data name="Report_invalid_placeholders_in_string_dot_format_calls" xml:space="preserve">
<value>Report invalid placeholders in 'String.Format' calls</value> <value>Report invalid placeholders in 'String.Format' calls</value>
</data> </data>
<data name="Separate_import_directive_groups" xml:space="preserve">
<value>Separate import directive groups</value>
</data>
</root> </root>
\ No newline at end of file
...@@ -28,6 +28,9 @@ ...@@ -28,6 +28,9 @@
<CheckBox x:Name="PlaceSystemNamespaceFirst" <CheckBox x:Name="PlaceSystemNamespaceFirst"
x:Uid="SortImports_PlaceSystemFirst" x:Uid="SortImports_PlaceSystemFirst"
Content="{x:Static local:AdvancedOptionPageStrings.Option_PlaceSystemNamespaceFirst}" /> Content="{x:Static local:AdvancedOptionPageStrings.Option_PlaceSystemNamespaceFirst}" />
<CheckBox x:Name="SeparateImportGroups"
x:Uid="SeparateImportGroups"
Content="{x:Static local:AdvancedOptionPageStrings.Option_SeparateImportGroups}" />
<CheckBox x:Name="SuggestForTypesInReferenceAssemblies" <CheckBox x:Name="SuggestForTypesInReferenceAssemblies"
x:Uid="AddImport_SuggestForTypesInReferenceAssemblies" x:Uid="AddImport_SuggestForTypesInReferenceAssemblies"
Content="{x:Static local:AdvancedOptionPageStrings.Option_Suggest_imports_for_types_in_reference_assemblies}" /> Content="{x:Static local:AdvancedOptionPageStrings.Option_Suggest_imports_for_types_in_reference_assemblies}" />
......
...@@ -23,6 +23,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options ...@@ -23,6 +23,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options
BindToOption(Perform_editor_feature_analysis_in_external_process, RemoteFeatureOptions.OutOfProcessAllowed) BindToOption(Perform_editor_feature_analysis_in_external_process, RemoteFeatureOptions.OutOfProcessAllowed)
BindToOption(PlaceSystemNamespaceFirst, GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.VisualBasic) BindToOption(PlaceSystemNamespaceFirst, GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.VisualBasic)
BindToOption(SeparateImportGroups, GenerationOptions.SeparateImportDirectiveGroups, LanguageNames.VisualBasic)
BindToOption(SuggestForTypesInReferenceAssemblies, SymbolSearchOptions.SuggestForTypesInReferenceAssemblies, LanguageNames.VisualBasic) BindToOption(SuggestForTypesInReferenceAssemblies, SymbolSearchOptions.SuggestForTypesInReferenceAssemblies, LanguageNames.VisualBasic)
BindToOption(SuggestForTypesInNuGetPackages, SymbolSearchOptions.SuggestForTypesInNuGetPackages, LanguageNames.VisualBasic) BindToOption(SuggestForTypesInNuGetPackages, SymbolSearchOptions.SuggestForTypesInNuGetPackages, LanguageNames.VisualBasic)
......
...@@ -191,17 +191,14 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options ...@@ -191,17 +191,14 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options
End Get End Get
End Property End Property
Public ReadOnly Property Option_Import_Directives As String Public ReadOnly Property Option_Import_Directives As String =
Get BasicVSResources.Import_Directives
Return BasicVSResources.Import_Directives
End Get
End Property
Public ReadOnly Property Option_PlaceSystemNamespaceFirst As String Public ReadOnly Property Option_PlaceSystemNamespaceFirst As String =
Get BasicVSResources.Place_System_directives_first_when_sorting_imports
Return BasicVSResources.Place_System_directives_first_when_sorting_imports
End Get Public ReadOnly Property Option_SeparateImportGroups As String =
End Property BasicVSResources.Separate_import_directive_groups
Public ReadOnly Property Option_Suggest_imports_for_types_in_reference_assemblies As String Public ReadOnly Property Option_Suggest_imports_for_types_in_reference_assemblies As String
Get Get
......
...@@ -2,19 +2,93 @@ ...@@ -2,19 +2,93 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities; using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Utilities namespace Microsoft.CodeAnalysis.CSharp.Utilities
{ {
internal static partial class UsingsAndExternAliasesOrganizer internal static partial class UsingsAndExternAliasesOrganizer
{ {
private static readonly SyntaxTrivia s_newLine = SyntaxFactory.CarriageReturnLineFeed;
public static void Organize( public static void Organize(
SyntaxList<ExternAliasDirectiveSyntax> externAliasList,
SyntaxList<UsingDirectiveSyntax> usingList,
bool placeSystemNamespaceFirst, bool separateGroups,
out SyntaxList<ExternAliasDirectiveSyntax> organizedExternAliasList,
out SyntaxList<UsingDirectiveSyntax> organizedUsingList)
{
OrganizeWorker(
externAliasList, usingList, placeSystemNamespaceFirst,
out organizedExternAliasList, out organizedUsingList);
if (separateGroups)
{
if (organizedExternAliasList.Count > 0 && organizedUsingList.Count > 0)
{
var firstUsing = organizedUsingList[0];
if (!firstUsing.GetLeadingTrivia().Any(t => t.IsEndOfLine()))
{
var newFirstUsing = firstUsing.WithPrependedLeadingTrivia(s_newLine);
organizedUsingList = organizedUsingList.Replace(firstUsing, newFirstUsing);
}
}
for (var i = 1; i < organizedUsingList.Count; i++)
{
var lastUsing = organizedUsingList[i - 1];
var currentUsing = organizedUsingList[i];
if (NeedsGrouping(lastUsing, currentUsing) &&
!currentUsing.GetLeadingTrivia().Any(t => t.IsEndOfLine()))
{
var newCurrentUsing = currentUsing.WithPrependedLeadingTrivia(s_newLine);
organizedUsingList = organizedUsingList.Replace(currentUsing, newCurrentUsing);
}
}
}
}
private static bool NeedsGrouping(
UsingDirectiveSyntax using1,
UsingDirectiveSyntax using2)
{
var directive1IsUsingStatic = using1.StaticKeyword.IsKind(SyntaxKind.StaticKeyword);
var directive2IsUsingStatic = using2.StaticKeyword.IsKind(SyntaxKind.StaticKeyword);
var directive1IsAlias = using1.Alias != null;
var directive2IsAlias = using2.Alias != null;
var directive1IsNamespace = !directive1IsUsingStatic && !directive1IsAlias;
var directive2IsNamespace = !directive2IsUsingStatic && !directive2IsAlias;
if (directive1IsAlias && directive2IsAlias)
{
return false;
}
if (directive1IsUsingStatic && directive2IsUsingStatic)
{
return false;
}
if (directive1IsNamespace && directive2IsNamespace)
{
// Both normal usings. Place them in groups if their first namespace
// component differs.
var name1 = using1.Name.GetFirstToken().ValueText;
var name2 = using2.Name.GetFirstToken().ValueText;
return name1 != name2;
}
// They have different types, definitely put them into new groups.
return true;
}
private static void OrganizeWorker(
SyntaxList<ExternAliasDirectiveSyntax> externAliasList, SyntaxList<ExternAliasDirectiveSyntax> externAliasList,
SyntaxList<UsingDirectiveSyntax> usingList, SyntaxList<UsingDirectiveSyntax> usingList,
bool placeSystemNamespaceFirst, bool placeSystemNamespaceFirst,
...@@ -79,9 +153,7 @@ private static void EnsureNewLines(IList<SyntaxNode> list) ...@@ -79,9 +153,7 @@ private static void EnsureNewLines(IList<SyntaxNode> list)
if (!trailingTrivia.Any() || trailingTrivia.Last().Kind() != SyntaxKind.EndOfLineTrivia) if (!trailingTrivia.Any() || trailingTrivia.Last().Kind() != SyntaxKind.EndOfLineTrivia)
{ {
// TODO(cyrusn): Don't use CRLF. Use the appropriate list[i] = node.WithTrailingTrivia(trailingTrivia.Concat(s_newLine));
// newline for this file.
list[i] = node.WithTrailingTrivia(trailingTrivia.Concat(SyntaxFactory.CarriageReturnLineFeed));
} }
} }
...@@ -90,10 +162,13 @@ private static void EnsureNewLines(IList<SyntaxNode> list) ...@@ -90,10 +162,13 @@ private static void EnsureNewLines(IList<SyntaxNode> list)
for (int i = 1; i < list.Count; i++) for (int i = 1; i < list.Count; i++)
{ {
var node = list[i]; var node = list[i];
list[i] = node.WithLeadingTrivia(node.GetLeadingTrivia().SkipWhile(t => t.Kind() == SyntaxKind.EndOfLineTrivia)); list[i] = TrimLeadingNewLines(node);
} }
list[0] = list[0].WithLeadingTrivia(list[0].GetLeadingTrivia().SkipWhile(t => t.Kind() == SyntaxKind.EndOfLineTrivia)); list[0] = TrimLeadingNewLines(list[0]);
} }
private static SyntaxNode TrimLeadingNewLines(SyntaxNode node)
=> node.WithLeadingTrivia(node.GetLeadingTrivia().SkipWhile(t => t.Kind() == SyntaxKind.EndOfLineTrivia));
} }
} }
...@@ -6,9 +6,16 @@ namespace Microsoft.CodeAnalysis.Editing ...@@ -6,9 +6,16 @@ namespace Microsoft.CodeAnalysis.Editing
{ {
internal class GenerationOptions internal class GenerationOptions
{ {
public static readonly PerLanguageOption<bool> PlaceSystemNamespaceFirst = new PerLanguageOption<bool>(nameof(GenerationOptions), nameof(PlaceSystemNamespaceFirst), defaultValue: true, public static readonly PerLanguageOption<bool> PlaceSystemNamespaceFirst = new PerLanguageOption<bool>(nameof(GenerationOptions),
nameof(PlaceSystemNamespaceFirst), defaultValue: true,
storageLocations: new OptionStorageLocation[] { storageLocations: new OptionStorageLocation[] {
EditorConfigStorageLocation.ForBoolOption("dotnet_sort_system_directives_first"), EditorConfigStorageLocation.ForBoolOption("dotnet_sort_system_directives_first"),
new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PlaceSystemNamespaceFirst")}); new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PlaceSystemNamespaceFirst")});
public static readonly PerLanguageOption<bool> SeparateImportDirectiveGroups = new PerLanguageOption<bool>(
nameof(GenerationOptions), nameof(SeparateImportDirectiveGroups), defaultValue: false,
storageLocations: new OptionStorageLocation[] {
EditorConfigStorageLocation.ForBoolOption("dotnet_separate_import_directive_groups"),
new RoamingProfileStorageLocation($"TextEditor.%LANGUAGE%.Specific.{nameof(SeparateImportDirectiveGroups)}")});
} }
} }
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System
Imports System.Collections.Generic
Imports System.Collections.Immutable Imports System.Collections.Immutable
Imports System.Globalization
Imports System.Linq
Imports System.Text
Imports System.Threading
Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Shared.Collections
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.Utilities
Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities
Partial Friend Class ImportsOrganizer Partial Friend Class ImportsOrganizer
Private Shared ReadOnly s_newLine As SyntaxTrivia = SyntaxFactory.CarriageReturnLineFeed
Public Shared Function Organize([imports] As SyntaxList(Of ImportsStatementSyntax), Public Shared Function Organize([imports] As SyntaxList(Of ImportsStatementSyntax),
placeSystemNamespaceFirst As Boolean,
separateGroups As Boolean) As SyntaxList(Of ImportsStatementSyntax)
[imports] = OrganizeWorker([imports], placeSystemNamespaceFirst)
If separateGroups Then
For i = 1 To [imports].Count - 1
Dim lastImport = [imports](i - 1)
Dim currentImport = [imports](i)
If NeedsGrouping(lastImport, currentImport) AndAlso
Not currentImport.GetLeadingTrivia().Any(Function(t) t.IsEndOfLine()) Then
[imports] = [imports].Replace(
currentImport, currentImport.WithPrependedLeadingTrivia(s_newLine))
End If
Next
End If
Return [imports]
End Function
Private Shared Function NeedsGrouping(import1 As ImportsStatementSyntax,
import2 As ImportsStatementSyntax) As Boolean
If import1.ImportsClauses.Count = 0 OrElse import2.ImportsClauses.Count = 0 Then
Return False
End If
Dim importClause1 = import1.ImportsClauses(0)
Dim importClause2 = import2.ImportsClauses(0)
Dim simpleClause1 = TryCast(importClause1, SimpleImportsClauseSyntax)
Dim simpleClause2 = TryCast(importClause2, SimpleImportsClauseSyntax)
If simpleClause1 Is Nothing AndAlso simpleClause2 Is Nothing Then
Return False
End If
If simpleClause1 IsNot Nothing AndAlso simpleClause2 IsNot Nothing Then
Dim isAlias1 = simpleClause1.Alias IsNot Nothing
Dim isAlias2 = simpleClause2.Alias IsNot Nothing
If isAlias1 AndAlso isAlias2 Then
Return False
End If
If Not isAlias1 AndAlso Not isAlias2 Then
' named imports
Dim name1 = simpleClause1.Name.GetFirstToken().ValueText
Dim name2 = simpleClause2.Name.GetFirstToken().ValueText
Return Not VisualBasicSyntaxFactsService.Instance.StringComparer.Equals(name1, name2)
End If
End If
' Different kinds of imports. Definitely place into separate groups.
Return True
End Function
Public Shared Function OrganizeWorker([imports] As SyntaxList(Of ImportsStatementSyntax),
placeSystemNamespaceFirst As Boolean) As SyntaxList(Of ImportsStatementSyntax) placeSystemNamespaceFirst As Boolean) As SyntaxList(Of ImportsStatementSyntax)
If [imports].Count > 1 Then If [imports].Count > 1 Then
Dim initialList = New List(Of ImportsStatementSyntax)([imports]) Dim initialList = New List(Of ImportsStatementSyntax)([imports])
...@@ -51,7 +100,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities ...@@ -51,7 +100,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities
Private Shared Sub EnsureNewLines(list As List(Of ImportsStatementSyntax)) Private Shared Sub EnsureNewLines(list As List(Of ImportsStatementSyntax))
Dim endOfLine = GetExistingEndOfLineTrivia(list) Dim endOfLine = GetExistingEndOfLineTrivia(list)
endOfLine = If(endOfLine.Kind = SyntaxKind.None, SyntaxFactory.CarriageReturnLineFeed, endOfLine) endOfLine = If(endOfLine.Kind = SyntaxKind.None, s_newLine, endOfLine)
For i = 0 To list.Count - 1 For i = 0 To list.Count - 1
If Not list(i).GetTrailingTrivia().Any(SyntaxKind.EndOfLineTrivia) Then If Not list(i).GetTrailingTrivia().Any(SyntaxKind.EndOfLineTrivia) Then
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册