From fcb3004d74518138788e1e5ed1d120ddeb283ec6 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Mon, 22 Apr 2019 17:53:56 -0700 Subject: [PATCH] Add import placement codestyle, diagnostic, and fixer (#35009) * Add import placement codestyle option * Add TextEditor CodeStyle options for Using Placement * Add misplaced using directives analyzer and code fix * Use consistent pluralization * Removed Preserve AddImportPlacement option * Removed Preserve from CSharp Style Options * Removed Preserve from editorconfig tests * Coverted to Roslyn style analyzer and fixer tests. * Simplified MisplacedUsings CodeFix based on feedback. * Simplified MisplacedUsings CodeFix based on feedback. * Add warning annotation to moved using directives * Move misplaced usings out of multiple namespaces * Deduplicate usings when moving them * Simplified move usings warning text * Add expected warning markers to misplaced using tests * Fix editor config generator tests * Consolidated diagnostics and tests * Add tests where directives are in both contexts --- .../CSharp/CSharpEditorResources.Designer.cs | 45 + .../CSharp/CSharpEditorResources.resx | 20 + ...MisplacedUsingDirectivesCodeFixProvider.cs | 393 +++++++ ...placedUsingDirectivesDiagnosticAnalyzer.cs | 102 ++ .../CSharp/xlf/CSharpEditorResources.cs.xlf | 25 + .../CSharp/xlf/CSharpEditorResources.de.xlf | 25 + .../CSharp/xlf/CSharpEditorResources.es.xlf | 25 + .../CSharp/xlf/CSharpEditorResources.fr.xlf | 25 + .../CSharp/xlf/CSharpEditorResources.it.xlf | 25 + .../CSharp/xlf/CSharpEditorResources.ja.xlf | 25 + .../CSharp/xlf/CSharpEditorResources.ko.xlf | 25 + .../CSharp/xlf/CSharpEditorResources.pl.xlf | 25 + .../xlf/CSharpEditorResources.pt-BR.xlf | 25 + .../CSharp/xlf/CSharpEditorResources.ru.xlf | 25 + .../CSharp/xlf/CSharpEditorResources.tr.xlf | 25 + .../xlf/CSharpEditorResources.zh-Hans.xlf | 25 + .../xlf/CSharpEditorResources.zh-Hant.xlf | 25 + ...acedUsingDirectivesCodeFixProviderTests.cs | 961 ++++++++++++++++++ .../PredefinedCodeFixProviderNames.cs | 1 + .../Diagnostics/Analyzers/IDEDiagnosticIds.cs | 1 + .../CSharp/Impl/CSharpVSResources.Designer.cs | 36 + .../CSharp/Impl/CSharpVSResources.resx | 16 + .../Impl/Options/Formatting/StyleViewModel.cs | 42 + .../CSharp/Impl/xlf/CSharpVSResources.cs.xlf | 20 + .../CSharp/Impl/xlf/CSharpVSResources.de.xlf | 20 + .../CSharp/Impl/xlf/CSharpVSResources.es.xlf | 20 + .../CSharp/Impl/xlf/CSharpVSResources.fr.xlf | 20 + .../CSharp/Impl/xlf/CSharpVSResources.it.xlf | 20 + .../CSharp/Impl/xlf/CSharpVSResources.ja.xlf | 20 + .../CSharp/Impl/xlf/CSharpVSResources.ko.xlf | 20 + .../CSharp/Impl/xlf/CSharpVSResources.pl.xlf | 20 + .../Impl/xlf/CSharpVSResources.pt-BR.xlf | 20 + .../CSharp/Impl/xlf/CSharpVSResources.ru.xlf | 20 + .../CSharp/Impl/xlf/CSharpVSResources.tr.xlf | 20 + .../Impl/xlf/CSharpVSResources.zh-Hans.xlf | 20 + .../Impl/xlf/CSharpVSResources.zh-Hant.xlf | 20 + .../CSharpEditorConfigGeneratorTests.vb | 6 + .../CSharpWorkspaceResources.Designer.cs | 14 +- .../Portable/CSharpWorkspaceResources.resx | 4 + .../CodeStyle/CSharpCodeStyleOptions.cs | 15 + .../CSharpCodeStyleOptions_Parsing.cs | 40 + .../CSharpOptionsSerializationService.cs | 2 + .../xlf/CSharpWorkspaceResources.cs.xlf | 5 + .../xlf/CSharpWorkspaceResources.de.xlf | 5 + .../xlf/CSharpWorkspaceResources.es.xlf | 5 + .../xlf/CSharpWorkspaceResources.fr.xlf | 5 + .../xlf/CSharpWorkspaceResources.it.xlf | 5 + .../xlf/CSharpWorkspaceResources.ja.xlf | 5 + .../xlf/CSharpWorkspaceResources.ko.xlf | 5 + .../xlf/CSharpWorkspaceResources.pl.xlf | 5 + .../xlf/CSharpWorkspaceResources.pt-BR.xlf | 5 + .../xlf/CSharpWorkspaceResources.ru.xlf | 5 + .../xlf/CSharpWorkspaceResources.tr.xlf | 5 + .../xlf/CSharpWorkspaceResources.zh-Hans.xlf | 5 + .../xlf/CSharpWorkspaceResources.zh-Hant.xlf | 5 + .../Portable/AddImports/AddImportPlacement.cs | 20 + 56 files changed, 2365 insertions(+), 3 deletions(-) create mode 100644 src/EditorFeatures/CSharp/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs create mode 100644 src/EditorFeatures/CSharp/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs create mode 100644 src/EditorFeatures/CSharpTest/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProviderTests.cs create mode 100644 src/Workspaces/Core/Portable/AddImports/AddImportPlacement.cs diff --git a/src/EditorFeatures/CSharp/CSharpEditorResources.Designer.cs b/src/EditorFeatures/CSharp/CSharpEditorResources.Designer.cs index f4647c0558f..6ebb754a6c3 100644 --- a/src/EditorFeatures/CSharp/CSharpEditorResources.Designer.cs +++ b/src/EditorFeatures/CSharp/CSharpEditorResources.Designer.cs @@ -78,6 +78,24 @@ internal class CSharpEditorResources { } } + /// + /// Looks up a localized string similar to Misplaced using directive. + /// + internal static string Misplaced_using_directive { + get { + return ResourceManager.GetString("Misplaced_using_directive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move misplaced using directives. + /// + internal static string Move_misplaced_using_directives { + get { + return ResourceManager.GetString("Move_misplaced_using_directives", resourceCulture); + } + } + /// /// Looks up a localized string similar to (Press TAB to insert). /// @@ -104,5 +122,32 @@ internal class CSharpEditorResources { return ResourceManager.GetString("Split_string", resourceCulture); } } + + /// + /// Looks up a localized string similar to Using directives must be placed inside of a namespace declaration. + /// + internal static string Using_directives_must_be_placed_inside_of_a_namespace_declaration { + get { + return ResourceManager.GetString("Using_directives_must_be_placed_inside_of_a_namespace_declaration", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using directives must be placed outside of a namespace declaration. + /// + internal static string Using_directives_must_be_placed_outside_of_a_namespace_declaration { + get { + return ResourceManager.GetString("Using_directives_must_be_placed_outside_of_a_namespace_declaration", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Warning: Moving using directives may change code meaning.. + /// + internal static string Warning_colon_Moving_using_directives_may_change_code_meaning { + get { + return ResourceManager.GetString("Warning_colon_Moving_using_directives_may_change_code_meaning", resourceCulture); + } + } } } diff --git a/src/EditorFeatures/CSharp/CSharpEditorResources.resx b/src/EditorFeatures/CSharp/CSharpEditorResources.resx index d48f857299f..8ad864d753c 100644 --- a/src/EditorFeatures/CSharp/CSharpEditorResources.resx +++ b/src/EditorFeatures/CSharp/CSharpEditorResources.resx @@ -132,4 +132,24 @@ Split string + + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs b/src/EditorFeatures/CSharp/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs new file mode 100644 index 00000000000..0565620f14b --- /dev/null +++ b/src/EditorFeatures/CSharp/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs @@ -0,0 +1,393 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.AddImports; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.CSharp; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.CodeActions.CodeAction; + +namespace Microsoft.CodeAnalysis.CSharp.MisplacedUsingDirectives +{ + /// + /// Implements a code fix for all misplaced using statements. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.MoveMisplacedUsingDirectives)] + [Shared] + internal sealed partial class MisplacedUsingDirectivesCodeFixProvider : CodeFixProvider + { + private static readonly SyntaxAnnotation s_usingPlacementCodeFixAnnotation = new SyntaxAnnotation(nameof(s_usingPlacementCodeFixAnnotation)); + + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IDEDiagnosticIds.MoveMisplacedUsingDirectivesDiagnosticId); + + public override FixAllProvider GetFixAllProvider() + { + // Since we work on an entire document at a time fixing all contained diagnostics, the batch fixer should not have merge conflicts. + return WellKnownFixAllProviders.BatchFixer; + } + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var cancellationToken = context.CancellationToken; + + var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var compilationUnit = (CompilationUnitSyntax)syntaxRoot; + var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + + // Read the preferred placement option and verify if it can be applied to this code file. + // There are cases where we will not be able to fix the diagnostic and the user will need to resolve + // it manually. + var (placement, preferPreservation) = DeterminePlacement(compilationUnit, options); + if (preferPreservation) + { + return; + } + + foreach (var diagnostic in context.Diagnostics) + { + context.RegisterCodeFix( + new MoveMisplacedUsingsCodeAction(token => GetTransformedDocumentAsync(document, compilationUnit, options, placement, token)), + diagnostic); + } + } + + private static async Task GetTransformedDocumentAsync( + Document document, + CompilationUnitSyntax compilationUnit, + OptionSet options, + AddImportPlacement placement, + CancellationToken cancellationToken) + { + var syntaxFactsService = document.GetLanguageService(); + + // Expand usings so that they can be properly simplified after they are relocated. + var compilationUnitWithExpandedUsings = (CompilationUnitSyntax)await ExpandUsingDirectivesAsync(document, compilationUnit, cancellationToken).ConfigureAwait(false); + + // Remove the file header from the compilation unit so that we do not lose it when making changes to usings. + var (compilationUnitWithoutHeader, fileHeader) = RemoveFileHeader(compilationUnitWithExpandedUsings, syntaxFactsService); + + // A blanket warning that this codefix may change code so that it does not compile. + var warningAnnotation = WarningAnnotation.Create(CSharpEditorResources.Warning_colon_Moving_using_directives_may_change_code_meaning); + + var newCompilationUnit = placement == AddImportPlacement.InsideNamespace + ? MoveUsingsInsideNamespace(compilationUnitWithoutHeader, warningAnnotation) + : MoveUsingsOutsideNamespaces(compilationUnitWithoutHeader, warningAnnotation); + + // Re-attach the header now that using have been moved and LeadingTrivia is no longer being altered. + var newCompilationUnitWithHeader = AddFileHeader(newCompilationUnit, fileHeader); + var newDocument = document.WithSyntaxRoot(newCompilationUnitWithHeader); + + // Simplify usings now that they have been moved and are in the proper context. + return await Simplifier.ReduceAsync(newDocument, Simplifier.Annotation, options, cancellationToken).ConfigureAwait(false); + } + + private static async Task ExpandUsingDirectivesAsync(Document document, CompilationUnitSyntax containerNode, CancellationToken cancellationToken) + { + // Get all using directives so they can be expanded at one time. + var allUsingDirectives = containerNode.DescendantNodes( + node => node.IsKind(SyntaxKind.CompilationUnit, SyntaxKind.NamespaceDeclaration)).OfType(); + + // Create a map between the original node and the future expanded node. + var expandUsingDirectiveTasks = allUsingDirectives.ToDictionary( + usingDirective => (SyntaxNode)usingDirective, + usingDirective => ExpandUsingDirectiveAsync(document, usingDirective, cancellationToken)); + + // Wait for all using directives to be expanded + await Task.WhenAll(expandUsingDirectiveTasks.Values).ConfigureAwait(false); + + // Replace using directives with their expanded version. + return ((SyntaxNode)containerNode).ReplaceNodes( + expandUsingDirectiveTasks.Keys, + (node, _) => expandUsingDirectiveTasks[node].Result); + } + + private static async Task ExpandUsingDirectiveAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + { + var usingDirective = (UsingDirectiveSyntax)node; + var newName = await Simplifier.ExpandAsync(usingDirective.Name, document, cancellationToken: cancellationToken).ConfigureAwait(false); + return usingDirective.WithName(newName); + } + + private static CompilationUnitSyntax MoveUsingsInsideNamespace(CompilationUnitSyntax compilationUnit, SyntaxAnnotation warningAnnotation) + { + // Get the compilation unit usings and set them up to format when moved. + var usingsToAdd = compilationUnit.Usings.Select( + directive => directive.WithAdditionalAnnotations(Formatter.Annotation, warningAnnotation)); + + // Remove usings and fix leading trivia for compilation unit. + var compilationUnitWithoutUsings = compilationUnit.WithUsings(default); + var compilationUnitWithoutBlankLine = RemoveLeadingBlankLinesFromFirstMember(compilationUnitWithoutUsings); + + // Fix the leading trivia for the namespace declaration. + var namespaceDeclaration = (NamespaceDeclarationSyntax)compilationUnitWithoutBlankLine.Members[0]; + var namespaceDeclarationWithBlankLine = EnsureLeadingBlankLineBeforeFirstMember(namespaceDeclaration); + + // Update the namespace declaration with the usings from the compilation unit. + var newUsings = namespaceDeclarationWithBlankLine.Usings.InsertRange(0, usingsToAdd); + var namespaceDeclarationWithUsings = namespaceDeclarationWithBlankLine.WithUsings(newUsings); + + // Update the compilation unit with the new namespace declaration + return compilationUnitWithoutBlankLine.ReplaceNode(namespaceDeclaration, namespaceDeclarationWithUsings); + } + + private static CompilationUnitSyntax MoveUsingsOutsideNamespaces(CompilationUnitSyntax compilationUnit, SyntaxAnnotation warningAnnotation) + { + var namespaceDeclarations = compilationUnit.Members.OfType(); + var namespaceDeclarationMap = namespaceDeclarations.ToDictionary( + namespaceDeclaration => namespaceDeclaration, namespaceDeclaration => RemoveUsingsFromNamespace(namespaceDeclaration)); + + // Replace the namespace declarations in the compilation with the ones without using directives. + var compilationUnitWithReplacedNamespaces = compilationUnit.ReplaceNodes( + namespaceDeclarations, (node, _) => namespaceDeclarationMap[node].namespaceWithoutUsings); + + // Get the using directives from the namespaces and set them up to format when moved. + var usingsToAdd = namespaceDeclarationMap.Values.SelectMany(result => result.usingsFromNamespace) + .Select(directive => directive.WithAdditionalAnnotations(Formatter.Annotation, warningAnnotation)); + + var (deduplicatedUsings, orphanedTrivia) = RemoveDuplicateUsings(compilationUnit.Usings, usingsToAdd.ToImmutableArray()); + + // Update the compilation unit with the usings from the namespace declaration. + var newUsings = compilationUnitWithReplacedNamespaces.Usings.AddRange(deduplicatedUsings); + var compilationUnitWithUsings = compilationUnitWithReplacedNamespaces.WithUsings(newUsings); + + // Fix the leading trivia for the compilation unit. + var compilationUnitWithSeparatorLine = EnsureLeadingBlankLineBeforeFirstMember(compilationUnitWithUsings); + + if (!orphanedTrivia.Any()) + { + return compilationUnitWithSeparatorLine; + } + + // Add leading trivia that was orphaned from removing duplicate using directives to the first member in the compilation unit. + var firstMember = compilationUnitWithSeparatorLine.Members[0]; + return compilationUnitWithSeparatorLine.ReplaceNode(firstMember, firstMember.WithPrependedLeadingTrivia(orphanedTrivia)); + } + + private static (NamespaceDeclarationSyntax namespaceWithoutUsings, IEnumerable usingsFromNamespace) RemoveUsingsFromNamespace( + NamespaceDeclarationSyntax usingContainer) + { + var namespaceDeclarations = usingContainer.Members.OfType(); + var namespaceDeclarationMap = namespaceDeclarations.ToDictionary( + namespaceDeclaration => namespaceDeclaration, namespaceDeclaration => RemoveUsingsFromNamespace(namespaceDeclaration)); + + // Get the using directives from the namespaces. + var usingsFromNamespaces = namespaceDeclarationMap.Values.SelectMany(result => result.usingsFromNamespace); + var allUsings = usingContainer.Usings.AsEnumerable().Concat(usingsFromNamespaces); + + // Replace the namespace declarations in the compilation with the ones without using directives. + var namespaceDeclarationWithReplacedNamespaces = usingContainer.ReplaceNodes( + namespaceDeclarations, (node, _) => namespaceDeclarationMap[node].namespaceWithoutUsings); + + // Remove usings and fix leading trivia for namespace declaration. + var namespaceDeclarationWithoutUsings = namespaceDeclarationWithReplacedNamespaces.WithUsings(default); + var namespaceDeclarationWithoutBlankLine = RemoveLeadingBlankLinesFromFirstMember(namespaceDeclarationWithoutUsings); + + return (namespaceDeclarationWithoutBlankLine, allUsings); + } + + private static (IEnumerable deduplicatedUsings, IEnumerable orphanedTrivia) RemoveDuplicateUsings( + IEnumerable existingUsings, + ImmutableArray usingsToAdd) + { + var seenUsings = existingUsings.ToList(); + + var deduplicatedUsingsBuilder = ImmutableArray.CreateBuilder(); + var orphanedTrivia = Enumerable.Empty(); + + foreach (var usingDirective in usingsToAdd) + { + // Check is the node is a duplicate. + if (seenUsings.Any(seenUsingDirective => seenUsingDirective.IsEquivalentTo(usingDirective, topLevel: false))) + { + // If there was trivia from the duplicate node, check if any of the trivia is necessary to keep. + var leadingTrivia = usingDirective.GetLeadingTrivia(); + if (leadingTrivia.Any(trivia => !trivia.IsWhitespaceOrEndOfLine())) + { + // Capture the meaningful trivia so we can prepend it to the next kept node. + orphanedTrivia = orphanedTrivia.Concat(leadingTrivia); + } + } + else + { + seenUsings.Add(usingDirective); + + // Add any orphaned trivia to this node. + deduplicatedUsingsBuilder.Add(usingDirective.WithPrependedLeadingTrivia(orphanedTrivia)); + orphanedTrivia = Enumerable.Empty(); + } + } + + return (deduplicatedUsingsBuilder.ToImmutable(), orphanedTrivia); + } + + private static SyntaxList GetMembers(SyntaxNode node) + => node switch + { + CompilationUnitSyntax compilationUnit => compilationUnit.Members, + NamespaceDeclarationSyntax namespaceDeclaration => namespaceDeclaration.Members, + _ => throw ExceptionUtilities.UnexpectedValue(node) + }; + + private static TSyntaxNode RemoveLeadingBlankLinesFromFirstMember(TSyntaxNode node) where TSyntaxNode : SyntaxNode + { + var members = GetMembers(node); + if (members.Count == 0) + { + return node; + } + + var firstMember = members.First(); + var firstMemberTrivia = firstMember.GetLeadingTrivia(); + + // If there is no leading trivia, then return the node as it is. + if (firstMemberTrivia.Count == 0) + { + return node; + } + + var newTrivia = SplitIntoLines(firstMemberTrivia) + .SkipWhile(trivia => trivia.All(t => t.IsWhitespaceOrEndOfLine()) && trivia.Last().IsKind(SyntaxKind.EndOfLineTrivia)) + .SelectMany(t => t); + + var newFirstMember = firstMember.WithLeadingTrivia(newTrivia); + return node.ReplaceNode(firstMember, newFirstMember); + } + + private static IEnumerable> SplitIntoLines(SyntaxTriviaList triviaList) + { + var index = 0; + for (var i = 0; i < triviaList.Count; i++) + { + if (triviaList[i].IsEndOfLine()) + { + yield return triviaList.TakeRange(index, i); + index = i + 1; + } + } + + if (index < triviaList.Count) + { + yield return triviaList.TakeRange(index, triviaList.Count - 1); + } + } + + private static TSyntaxNode EnsureLeadingBlankLineBeforeFirstMember(TSyntaxNode node) where TSyntaxNode : SyntaxNode + { + var members = GetMembers(node); + if (members.Count == 0) + { + return node; + } + + var firstMember = members.First(); + var firstMemberTrivia = firstMember.GetLeadingTrivia(); + + // If the first member already contains a leading new line then, this will already break up the usings from these members. + if (firstMemberTrivia.Count > 0 && firstMemberTrivia.First().IsKind(SyntaxKind.EndOfLineTrivia)) + { + return node; + } + + var newFirstMember = firstMember.WithLeadingTrivia(firstMemberTrivia.Insert(0, SyntaxFactory.CarriageReturnLineFeed)); + return node.ReplaceNode(firstMember, newFirstMember); + } + + private static (AddImportPlacement placement, bool preferPreservation) DeterminePlacement(CompilationUnitSyntax compilationUnit, OptionSet options) + { + var codeStyleOption = options.GetOption(CSharpCodeStyleOptions.PreferredUsingDirectivePlacement); + + var placement = codeStyleOption.Value; + var preferPreservation = codeStyleOption.Notification == NotificationOption.None; + + if (preferPreservation || placement == AddImportPlacement.OutsideNamespace) + { + return (placement, preferPreservation); + } + + // Determine if we can safely apply the InsideNamespace preference. + + // Do not offer a code fix when there are multiple namespaces in the source file. When there are + // nested namespaces it is not clear if inner usings can be moved outwards without causing + // collisions. Also, when moving usings inwards it is complex to determine which namespaces they + // should be moved into. + + // Only move using declarations inside the namespace when + // - There are no global attributes + // - There are no type definitions outside of the single top level namespace + // - There is only a single namespace declared at the top level + var forcePreservation = compilationUnit.AttributeLists.Any() + || compilationUnit.Members.Count > 1 + || !HasOneNamespace(compilationUnit); + + return (AddImportPlacement.InsideNamespace, forcePreservation); + } + + private static bool HasOneNamespace(CompilationUnitSyntax compilationUnit) + { + // Find all the NamespaceDeclarations + var allNamespaces = compilationUnit.DescendantNodes( + node => node.IsKind(SyntaxKind.CompilationUnit, SyntaxKind.NamespaceDeclaration)).OfType(); + + // To determine if there are multiple namespaces we only need to look for at least two. + return allNamespaces.Take(2).Count() == 1; + } + + private static (CompilationUnitSyntax compilationUnitWithoutHeader, ImmutableArray header) RemoveFileHeader( + CompilationUnitSyntax syntaxRoot, ISyntaxFactsService syntaxFactsService) + { + var fileHeader = syntaxFactsService.GetFileBanner(syntaxRoot); + var leadingTrivia = syntaxRoot.GetLeadingTrivia(); + + for (var i = fileHeader.Length - 1; i >= 0; i--) + { + leadingTrivia = leadingTrivia.RemoveAt(i); + } + + var newCompilationUnit = syntaxRoot.WithLeadingTrivia(leadingTrivia); + + return (newCompilationUnit, fileHeader); + } + + private static CompilationUnitSyntax AddFileHeader(CompilationUnitSyntax compilationUnit, ImmutableArray fileHeader) + { + if (fileHeader.IsEmpty) + { + return compilationUnit; + } + + // Add leading trivia to the first token. + var firstToken = compilationUnit.GetFirstToken(includeZeroWidth: true); + var newLeadingTrivia = firstToken.LeadingTrivia.InsertRange(0, fileHeader); + var newFirstToken = firstToken.WithLeadingTrivia(newLeadingTrivia); + + return compilationUnit.ReplaceToken(firstToken, newFirstToken); + } + + private class MoveMisplacedUsingsCodeAction : DocumentChangeAction + { + public MoveMisplacedUsingsCodeAction(Func> createChangedDocument) + : base(CSharpEditorResources.Move_misplaced_using_directives, createChangedDocument) + { + } + } + } +} diff --git a/src/EditorFeatures/CSharp/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs b/src/EditorFeatures/CSharp/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs new file mode 100644 index 00000000000..56b36aa932c --- /dev/null +++ b/src/EditorFeatures/CSharp/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs @@ -0,0 +1,102 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.AddImports; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.CSharp; + +namespace Microsoft.CodeAnalysis.CSharp.MisplacedUsingDirectives +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal sealed class MisplacedUsingDirectivesDiagnosticAnalyzer : AbstractCodeStyleDiagnosticAnalyzer + { + private static readonly LocalizableResourceString s_localizableTitle = new LocalizableResourceString( + nameof(CSharpEditorResources.Misplaced_using_directive), CSharpEditorResources.ResourceManager, typeof(CSharpEditorResources)); + + private static readonly LocalizableResourceString s_localizableOutsideMessage = new LocalizableResourceString( + nameof(CSharpEditorResources.Using_directives_must_be_placed_outside_of_a_namespace_declaration), CSharpEditorResources.ResourceManager, typeof(CSharpEditorResources)); + + private static readonly DiagnosticDescriptor s_outsideDiagnosticDescriptor = CreateDescriptorWithId( + IDEDiagnosticIds.MoveMisplacedUsingDirectivesDiagnosticId, s_localizableTitle, s_localizableOutsideMessage); + + private static readonly LocalizableResourceString s_localizableInsideMessage = new LocalizableResourceString( + nameof(CSharpEditorResources.Using_directives_must_be_placed_inside_of_a_namespace_declaration), CSharpEditorResources.ResourceManager, typeof(CSharpEditorResources)); + + private static readonly DiagnosticDescriptor s_insideDiagnosticDescriptor = CreateDescriptorWithId( + IDEDiagnosticIds.MoveMisplacedUsingDirectivesDiagnosticId, s_localizableTitle, s_localizableInsideMessage); + + public MisplacedUsingDirectivesDiagnosticAnalyzer() + : base(new[] { s_outsideDiagnosticDescriptor, s_insideDiagnosticDescriptor }.AsImmutable()) + { + } + + protected override void InitializeWorker(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeNamespaceNode, SyntaxKind.NamespaceDeclaration); + context.RegisterSyntaxNodeAction(AnalyzeCompilationUnitNode, SyntaxKind.CompilationUnit); + } + + private void AnalyzeNamespaceNode(SyntaxNodeAnalysisContext context) + { + var option = GetPreferredPlacementOptionAsync(context).GetAwaiter().GetResult(); + if (option.Value != AddImportPlacement.OutsideNamespace) + { + return; + } + + var namespaceDeclaration = (NamespaceDeclarationSyntax)context.Node; + ReportDiagnostics(context, s_outsideDiagnosticDescriptor, namespaceDeclaration.Usings, option); + } + + private static void AnalyzeCompilationUnitNode(SyntaxNodeAnalysisContext context) + { + var option = GetPreferredPlacementOptionAsync(context).GetAwaiter().GetResult(); + var compilationUnit = (CompilationUnitSyntax)context.Node; + + if (option.Value != AddImportPlacement.InsideNamespace + || ShouldSuppressDiagnostic(compilationUnit)) + { + return; + } + + // Note: We will report diagnostics when a code file contains multiple namespaces even though we will + // not offer a code fix in these cases. + ReportDiagnostics(context, s_insideDiagnosticDescriptor, compilationUnit.Usings, option); + } + + private static bool ShouldSuppressDiagnostic(CompilationUnitSyntax compilationUnit) + { + // Suppress if there are nodes other than usings and namespaces in the + // compilation unit (including ExternAlias). + return compilationUnit.ChildNodes().Any( + t => !t.IsKind(SyntaxKind.UsingDirective, SyntaxKind.NamespaceDeclaration)); + } + + private static async Task> GetPreferredPlacementOptionAsync(SyntaxNodeAnalysisContext context) + { + var options = await context.Options.GetDocumentOptionSetAsync(context.Node.SyntaxTree, context.CancellationToken); + return options.GetOption(CSharpCodeStyleOptions.PreferredUsingDirectivePlacement); + } + + private static void ReportDiagnostics( + SyntaxNodeAnalysisContext context, DiagnosticDescriptor descriptor, + IEnumerable usingDirectives, CodeStyleOption option) + { + foreach (var usingDirective in usingDirectives) + { + context.ReportDiagnostic(DiagnosticHelper.Create( + descriptor, + usingDirective.GetLocation(), + option.Notification.Severity, + additionalLocations: null, + properties: null)); + } + } + } +} diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf index 24ffedcf6bb..9c60512ee1d 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf @@ -12,6 +12,16 @@ Opravit interpolovaný doslovný řetězec + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (Pro vložení stiskněte TAB.) @@ -27,6 +37,21 @@ Rozdělit řetězec + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf index 902caf5fc54..7de2475ca03 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf @@ -12,6 +12,16 @@ Interpolierte ausführliche Zeichenfolge korrigieren + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (Zum Einfügen TAB-TASTE drücken) @@ -27,6 +37,21 @@ Zeichenfolge teilen + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf index 2af2b48d173..8928c27435f 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf @@ -12,6 +12,16 @@ Corregir cadena textual interpolada + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (Presione TAB para insertar) @@ -27,6 +37,21 @@ Dividir cadena + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf index 65f953e3d55..e480a5b4c5b 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf @@ -12,6 +12,16 @@ Corriger la chaîne textuelle interpolée + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (Appuyez sur TAB pour insérer) @@ -27,6 +37,21 @@ Fractionner la chaîne + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf index ab9cd09ac66..208cf5ecf6c 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf @@ -12,6 +12,16 @@ Correggi stringa verbatim interpolata + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (Premere TAB per inserire) @@ -27,6 +37,21 @@ Dividi stringa + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf index 3cd473f4e2e..3858acec764 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf @@ -12,6 +12,16 @@ 挿入された逐語的文字列を修正します + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (Tab キーを押して挿入) @@ -27,6 +37,21 @@ 文字列を分割します + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf index 444ee448fb4..ef46cab2eef 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf @@ -12,6 +12,16 @@ 보간된 축자 문자열 수정 + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (삽입하려면 <Tab> 키 누름) @@ -27,6 +37,21 @@ 문자열 분할 + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf index 281a8e346e0..393bce7d811 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf @@ -12,6 +12,16 @@ Napraw interpolowany ciąg dosłownego wyrażenia + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (Naciśnij klawisz TAB, aby wstawić) @@ -27,6 +37,21 @@ Rozdziel ciąg + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf index 51d619865dc..868b050ba3d 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf @@ -12,6 +12,16 @@ Corrigir cadeia de caracteres textual interpolada + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (Pressione TAB para inserir) @@ -27,6 +37,21 @@ Dividir cadeia de caracteres + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf index 2352cb14a8e..2e766161388 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf @@ -12,6 +12,16 @@ Исправить интерполированную строку verbatim + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (Нажмите клавишу TAB для вставки) @@ -27,6 +37,21 @@ Разделить строку + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf index a86787dea00..bda9a750740 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf @@ -12,6 +12,16 @@ Ara değer olarak eklenmiş tam dizeyi düzelt + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (Eklemek için TAB tuşuna basın) @@ -27,6 +37,21 @@ Dizeyi böl + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf index d2f53afa8e2..f1c75944d68 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf @@ -12,6 +12,16 @@ 修复插值的逐字字符串 + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (按 Tab 插入) @@ -27,6 +37,21 @@ 拆分字符串 + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf index 27ac60480df..3024a50aea4 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf @@ -12,6 +12,16 @@ 修正插入的逐字字串 + + Misplaced using directive + Misplaced using directive + {Locked="using"} "using" is a C# keyword and should not be localized. + + + Move misplaced using directives + Move misplaced using directives + {Locked="using"} "using" is a C# keyword and should not be localized. + (Press TAB to insert) (按 TAB 鍵插入) @@ -27,6 +37,21 @@ 分割字串 + + Using directives must be placed inside of a namespace declaration + Using directives must be placed inside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Using directives must be placed outside of a namespace declaration + Using directives must be placed outside of a namespace declaration + {Locked="using"} "using" is a C# keyword and should not be localized. {Locked="namespace"} "namespace" is a C# keyword and should not be localized. + + + Warning: Moving using directives may change code meaning. + Warning: Moving using directives may change code meaning. + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProviderTests.cs b/src/EditorFeatures/CSharpTest/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProviderTests.cs new file mode 100644 index 00000000000..f20c3047296 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProviderTests.cs @@ -0,0 +1,961 @@ +// 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.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.AddImports; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.MisplacedUsingDirectives; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.MisplacedUsingDirectives +{ + /// + /// Unit tests for the and . + /// + public class MisplacedUsingDirectivesInCompilationUnitCodeFixProviderTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + { + internal static readonly CodeStyleOption OutsidePreferPreservationOption = + new CodeStyleOption(AddImportPlacement.OutsideNamespace, NotificationOption.None); + + internal static readonly CodeStyleOption InsidePreferPreservationOption = + new CodeStyleOption(AddImportPlacement.InsideNamespace, NotificationOption.None); + + internal static readonly CodeStyleOption InsideNamespaceOption = + new CodeStyleOption(AddImportPlacement.InsideNamespace, NotificationOption.Error); + + internal static readonly CodeStyleOption OutsideNamespaceOption = + new CodeStyleOption(AddImportPlacement.OutsideNamespace, NotificationOption.Error); + + + protected const string ClassDefinition = @"public class TestClass +{ +}"; + + protected const string StructDefinition = @"public struct TestStruct +{ +}"; + + protected const string InterfaceDefinition = @"public interface TestInterface +{ +}"; + + protected const string EnumDefinition = @"public enum TestEnum +{ + TestValue +}"; + + protected const string DelegateDefinition = @"public delegate void TestDelegate();"; + + private protected Task TestDiagnosticMissingAsync(string initialMarkup, CodeStyleOption preferredPlacementOption) + { + var options = new Dictionary { { CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, preferredPlacementOption } }; + return TestDiagnosticMissingAsync(initialMarkup, new TestParameters(options: options)); + } + + private protected Task TestMissingAsync(string initialMarkup, CodeStyleOption preferredPlacementOption) + { + var options = new Dictionary { { CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, preferredPlacementOption } }; + return TestMissingAsync(initialMarkup, new TestParameters(options: options)); + } + + private protected Task TestInRegularAndScriptAsync(string initialMarkup, string expectedMarkup, CodeStyleOption preferredPlacementOption, bool placeSystemNamespaceFirst) + { + var options = new Dictionary + { + { CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, preferredPlacementOption }, + { new OptionKey(GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.CSharp), placeSystemNamespaceFirst } + }; + return TestInRegularAndScriptAsync(initialMarkup, expectedMarkup, options: options); + } + + internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) + { + return (new MisplacedUsingDirectivesDiagnosticAnalyzer(), new MisplacedUsingDirectivesCodeFixProvider()); + } + + #region Test Preserve + + /// + /// Verifies that valid using statements in a namespace does not produce any diagnostics. + /// + [Fact] + public Task WhenPreserve_UsingsInNamespace_ValidUsingStatements() + { + var testCode = @"namespace TestNamespace +{ + [|using System; + using System.Threading;|] +} +"; + + return TestDiagnosticMissingAsync(testCode, OutsidePreferPreservationOption); + } + + /// + /// Verifies that having using statements in the compilation unit will not produce any diagnostics, nor will + /// having using statements inside a namespace. + /// + [Fact] + public Task WhenPreserve_UsingsInCompilationUnitAndNamespace_ValidUsingStatements() + { + var testCode = @"using System; + +namespace TestNamespace +{ + [|using System.Threading;|] +} +"; + + return TestDiagnosticMissingAsync(testCode, OutsidePreferPreservationOption); + } + + /// + /// Verifies that having using statements in the compilation unit will not produce any diagnostics when there are type definition present. + /// + /// The type definition to test. + [Theory] + [InlineData(ClassDefinition)] + [InlineData(StructDefinition)] + [InlineData(InterfaceDefinition)] + [InlineData(EnumDefinition)] + [InlineData(DelegateDefinition)] + public Task WhenPreserve_UsingsInCompilationUnitWithTypeDefinition_ValidUsingStatements(string typeDefinition) + { + var testCode = $@"[|using System;|] + +{typeDefinition} +"; + + return TestDiagnosticMissingAsync(testCode, InsidePreferPreservationOption); + } + + /// + /// Verifies that having using statements in the compilation unit will not produce any diagnostics when there are attributes present. + /// + [Fact] + public Task WhenPreserve_UsingsInCompilationUnitWithAttributes_ValidUsingStatements() + { + var testCode = @"[|using System.Reflection;|] + +[assembly: AssemblyVersion(""1.0.0.0"")] + +namespace TestNamespace +{ + using System; + using System.Threading; +} +"; + + return TestDiagnosticMissingAsync(testCode, InsidePreferPreservationOption); + } + + /// + /// Verifies that having using statements in the compilation unit will not produce any diagnostics, even if they could be + /// moved inside a namespace. + /// + [Fact] + public Task WhenPreserve_UsingsInCompilationUnit_ValidUsingStatements() + { + var testCode = @"[|using System; +using System.Threading;|] + +namespace TestNamespace +{ +} +"; + + return TestDiagnosticMissingAsync(testCode, InsidePreferPreservationOption); + } + + #endregion + + #region Test OutsideNamespace + + /// + /// Verifies that valid using statements in the compilation unit does not produce any diagnostics. + /// + [Fact] + public Task WhenOutsidePreferred_UsingsInCompilationUnit_ValidUsingStatements() + { + var testCode = @"[|using System; +using System.Threading;|] + +namespace TestNamespace +{ +} +"; + + return TestDiagnosticMissingAsync(testCode, OutsideNamespaceOption); + } + + /// + /// Verifies that having using statements in the compilation unit will not produce any diagnostics when there are type definition present. + /// + /// The type definition to test. + [Theory] + [InlineData(ClassDefinition)] + [InlineData(StructDefinition)] + [InlineData(InterfaceDefinition)] + [InlineData(EnumDefinition)] + [InlineData(DelegateDefinition)] + public Task WhenOutsidePreferred_UsingsInCompilationUnitWithMember_ValidUsingStatements(string typeDefinition) + { + var testCode = $@"[|using System;|] + +{typeDefinition} +"; + + return TestDiagnosticMissingAsync(testCode, OutsideNamespaceOption); + } + + /// + /// Verifies that using statements in a namespace produces the expected diagnostics. + /// + [Fact] + public Task WhenOutsidePreferred_UsingsInNamespace_UsingsMoved() + { + var testCode = @"namespace TestNamespace +{ + [|using System; + using System.Threading;|] +} +"; + var fixedTestCode = @"{|Warning:using System;|} +{|Warning:using System.Threading;|} + +namespace TestNamespace +{ +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that simplified using statements in a namespace are expanded during the code fix operation. + /// + /// A representing the asynchronous unit test. + [Fact] + public Task WhenOutsidePreferred_SimplifiedUsingInNamespace_UsingsMovedAndExpanded() + { + var testCode = @"namespace System +{ + [|using System; + using System.Threading; + using Reflection;|] +} +"; + var fixedTestCode = @"{|Warning:using System;|} +{|Warning:using System.Threading;|} +{|Warning:using System.Reflection;|} + +namespace System +{ +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + + /// + /// Verifies that the code fix will move the using directives when they are present in both the compilation unit and namespace. + /// + [Fact] + public Task WhenOutsidePreferred_UsingsInBoth_UsingsMoved() + { + var testCode = @" +using Microsoft.CodeAnalysis; + +namespace TestNamespace +{ + [|using System;|] +} +"; + + var fixedTestCode = @" +using Microsoft.CodeAnalysis; +{|Warning:using System;|} + +namespace TestNamespace +{ +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that simplified using statements in a namespace are expanded during the code fix operation. + /// + [Fact] + public Task WhenOutsidePreferred_SimplifiedUsingAliasInNamespace_UsingsMovedAndExpanded() + { + var testCode = @"namespace System.MyExtension +{ + [|using System.Threading; + using Reflection; + using Assembly = Reflection.Assembly; + using List = Collections.Generic.IList;|] +} +"; + var fixedTestCode = @"{|Warning:using System.Threading;|} +{|Warning:using System.Reflection;|} +{|Warning:using Assembly = System.Reflection.Assembly;|} +{|Warning:using List = System.Collections.Generic.IList;|} + +namespace System.MyExtension +{ +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that having using statements in the compilation unit will not produce any diagnostics when there are attributes present. + /// + /// A representing the asynchronous unit test. + [Fact] + public Task WhenOutsidePreferred_UsingsInNamespaceAndCompilationUnitWithAttributes_UsingsMoved() + { + var testCode = @"using System.Reflection; + +[assembly: AssemblyVersion(""1.0.0.0"")] + +namespace TestNamespace +{ + [|using System; + using System.Threading;|] +} +"; + var fixedTestCode = @"using System.Reflection; +{|Warning:using System;|} +{|Warning:using System.Threading;|} + +[assembly: AssemblyVersion(""1.0.0.0"")] + +namespace TestNamespace +{ +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that the file header of a file is properly preserved when moving using statements out of a namespace. + /// + /// A representing the asynchronous unit test. + [Fact] + public Task WhenOutsidePreferred_UsingsInNamespaceAndCompilationUnitHasFileHeader_UsingsMovedAndHeaderPreserved() + { + var testCode = @"// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace TestNamespace +{ + [|using System;|] +} +"; + var fixedTestCode = @"// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +{|Warning:using System;|} + +namespace TestNamespace +{ +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + [Fact] + public Task WhenOutsidePreferred_UsingsInNamespaceWithCommentsAndCompilationUnitHasFileHeader_UsingsMovedWithCommentsAndHeaderPreserved() + { + var testCode = @"// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace TestNamespace +{ + // Separated Comment + + [|using System.Collections; + // Comment + using System;|] +} +"; + var fixedTestCode = @"// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +// Separated Comment + +{|Warning:using System.Collections;|} +// Comment +{|Warning:using System;|} + +namespace TestNamespace +{ +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + [Fact] + public Task WhenOutsidePreferred_UsingsInNamespace_UsingsMovedAndSystemPlacedFirstIgnored() + { + var testCode = @"namespace Foo +{ + [|using Microsoft.CodeAnalysis; + using SystemAction = System.Action; + using static System.Math; + using System; + + using static System.String; + using MyFunc = System.Func; + + using System.Collections.Generic; + using System.Collections;|] + + public class Bar + { + } +} +"; + + var fixedTestCode = @"{|Warning:using Microsoft.CodeAnalysis;|} +{|Warning:using SystemAction = System.Action;|} +{|Warning:using static System.Math;|} +{|Warning:using System;|} + +{|Warning:using static System.String;|} +{|Warning:using MyFunc = System.Func;|} + +{|Warning:using System.Collections.Generic;|} +{|Warning:using System.Collections;|} + +namespace Foo +{ + public class Bar + { + } +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + [Fact] + public Task WhenOutsidePreferred_UsingsInNamespace_UsingsMovedAndAlphaSortIgnored() + { + var testCode = @"namespace Foo +{ + [|using Microsoft.CodeAnalysis; + using SystemAction = System.Action; + using static System.Math; + using System; + + using static System.String; + using MyFunc = System.Func; + + using System.Collections.Generic; + using System.Collections;|] + + public class Bar + { + } +} +"; + + var fixedTestCode = @"{|Warning:using Microsoft.CodeAnalysis;|} +{|Warning:using SystemAction = System.Action;|} +{|Warning:using static System.Math;|} +{|Warning:using System;|} + +{|Warning:using static System.String;|} +{|Warning:using MyFunc = System.Func;|} + +{|Warning:using System.Collections.Generic;|} +{|Warning:using System.Collections;|} + +namespace Foo +{ + public class Bar + { + } +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: false); + } + + /// + /// Verifies that simplified using statements in nested namespace are expanded during the code fix operation. + /// + /// A representing the asynchronous unit test. + [Fact] + public Task WhenOutsidePreferred_UsingsInNestedNamespaces_UsingsMovedAndExpanded() + { + var testCode = @"using System; + +namespace System.Namespace +{ + // Outer Comment + [|using Threading; + + namespace OtherNamespace + { + // Inner Comment + using Reflection;|] + } +} +"; + var fixedTestCode = @"using System; +// Outer Comment +{|Warning:using System.Threading;|} +// Inner Comment +{|Warning:using System.Reflection;|} + +namespace System.Namespace +{ + namespace OtherNamespace + { + } +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that simplified using statements in multiple namespaces are expanded during the code fix operation. + /// + /// A representing the asynchronous unit test. + [Fact] + public Task WhenOutsidePreferred_UsingsInMultipleNamespaces_UsingsMovedAndExpanded() + { + var testCode = @"using System; + +namespace System.Namespace +{ + // A Comment + [|using Threading; +} + +namespace System.OtherNamespace +{ + // Another Comment + using Reflection;|] +} +"; + var fixedTestCode = @"using System; +// A Comment +{|Warning:using System.Threading;|} +// Another Comment +{|Warning:using System.Reflection;|} + +namespace System.Namespace +{ +} + +namespace System.OtherNamespace +{ +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that simplified using statements in multiple namespaces are deduplicated during the code fix operation. + /// + /// A representing the asynchronous unit test. + [Fact] + public Task WhenOutsidePreferred_UsingsInMultipleNamespaces_UsingsMovedAndDeduplicated() + { + var testCode = @"using System; + +namespace System.Namespace +{ + // Orphaned Comment 1 + [|using System; + // A Comment + using Threading; +} + +namespace B +{ + // Orphaned Comment 2 + using System.Threading;|] +} +"; + var fixedTestCode = @"using System; +// Orphaned Comment 1 +// A Comment +{|Warning:using System.Threading;|} +// Orphaned Comment 2 + +namespace System.Namespace +{ +} + +namespace B +{ +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, OutsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + #endregion + + #region Test InsideNamespace + + /// + /// Verifies that valid using statements in a namespace does not produce any diagnostics. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInNamespace_ValidUsingStatements() + { + var testCode = @"namespace TestNamespace +{ + [|using System; + using System.Threading;|] +} +"; + + return TestDiagnosticMissingAsync(testCode, InsideNamespaceOption); + } + + /// + /// Verifies that having using statements in the compilation unit will not produce any diagnostics when there are type definition present. + /// + /// The type definition to test. + [Theory] + [InlineData(ClassDefinition)] + [InlineData(StructDefinition)] + [InlineData(InterfaceDefinition)] + [InlineData(EnumDefinition)] + [InlineData(DelegateDefinition)] + public Task WhenInsidePreferred_UsingsInCompilationUnitWithTypeDefinition_ValidUsingStatements(string typeDefinition) + { + var testCode = $@"[|using System;|] + +{typeDefinition} +"; + + return TestDiagnosticMissingAsync(testCode, InsideNamespaceOption); + } + + /// + /// Verifies that having using statements in the compilation unit will not produce any diagnostics when there are attributes present. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInCompilationUnitWithAttributes_ValidUsingStatements() + { + var testCode = @"[|using System.Reflection;|] + +[assembly: AssemblyVersion(""1.0.0.0"")] + +namespace TestNamespace +{ + using System; + using System.Threading; +} +"; + + return TestDiagnosticMissingAsync(testCode, InsideNamespaceOption); + } + + /// + /// Verifies that the code fix will move the using directives and not place System directives first. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInCompilationUnit_UsingsMovedAndSystemPlacedFirstIgnored() + { + var testCode = @"[|using Microsoft.CodeAnalysis; +using SystemAction = System.Action; +using static System.Math; +using System; + +using static System.String; +using MyFunc = System.Func; + +using System.Collections.Generic; +using System.Collections;|] + +namespace Foo +{ + public class Bar + { + } +} +"; + + var fixedTestCode = @"namespace Foo +{ + {|Warning:using Microsoft.CodeAnalysis;|} + {|Warning:using SystemAction = System.Action;|} + {|Warning:using static System.Math;|} + {|Warning:using System;|} + + {|Warning:using static System.String;|} + {|Warning:using MyFunc = System.Func;|} + + {|Warning:using System.Collections.Generic;|} + {|Warning:using System.Collections;|} + + public class Bar + { + } +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, InsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that the code fix will move the using directives and not sort them alphabetically. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInCompilationUnit_UsingsAndWithAlphaSortIgnored() + { + var testCode = @"[|using Microsoft.CodeAnalysis; +using SystemAction = System.Action; +using static System.Math; +using System; + +using static System.String; +using MyFunc = System.Func; + +using System.Collections.Generic; +using System.Collections;|] + +namespace NamespaceName +{ + public class Bar + { + } +} +"; + + var fixedTestCode = @"namespace NamespaceName +{ + {|Warning:using Microsoft.CodeAnalysis;|} + {|Warning:using SystemAction = System.Action;|} + {|Warning:using static System.Math;|} + {|Warning:using System;|} + + {|Warning:using static System.String;|} + {|Warning:using MyFunc = System.Func;|} + + {|Warning:using System.Collections.Generic;|} + {|Warning:using System.Collections;|} + + public class Bar + { + } +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, InsideNamespaceOption, placeSystemNamespaceFirst: false); + } + + /// + /// Verifies that the code fix will move the using directives, but will not move a file header comment separated by an new line. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInCompilationUnitWithFileHeader_UsingsMovedNotHeader() + { + var testCode = @"// This is a file header. +[|using Microsoft.CodeAnalysis; +using System;|] + +namespace TestNamespace +{ +} +"; + + var fixedTestCode = @"// This is a file header. +namespace TestNamespace +{ + {|Warning:using Microsoft.CodeAnalysis;|} + {|Warning:using System;|} +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, InsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that the code fix will move the using directives when they are present in both the compilation unit and namespace. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInBoth_UsingsMoved() + { + var testCode = @"[|using Microsoft.CodeAnalysis;|] + +namespace TestNamespace +{ + using System; +} +"; + + var fixedTestCode = @"namespace TestNamespace +{ + {|Warning:using Microsoft.CodeAnalysis;|} + using System; +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, InsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that the code fix will properly move separated trivia, but will not move a file header comment. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInCompilationUnitWithFileHeaderAndTrivia_UsingsAndTriviaMovedNotHeader() + { + var testCode = @"// File Header + +// Leading Comment + +[|using Microsoft.CodeAnalysis; +using System;|] + +namespace TestNamespace +{ +} +"; + + var fixedTestCode = @"// File Header + +namespace TestNamespace +{ + // Leading Comment + + {|Warning:using Microsoft.CodeAnalysis;|} + {|Warning:using System;|} +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, InsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that a code fix will not be offered for MisplacedUsing diagnostics when multiple namespaces are present. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInCompilationUnitWithMultipleNamespaces_NoCodeFixOffered() + { + var testCode = @"[|using System;|] + +namespace TestNamespace1 +{ + public class TestClass1 + { + } +} + +namespace TestNamespace2 +{ +} +"; + + return TestMissingAsync(testCode, InsideNamespaceOption); + } + + /// + /// Verifies that the code fix will properly move pragmas. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInCompilationUnitWithPragma_PragmaMoved() + { + var testCode = @"#pragma warning disable 1573 // Comment +[|using System; +using System.Threading;|] + +namespace TestNamespace +{ +} +"; + + var fixedTestCode = @"namespace TestNamespace +{ +#pragma warning disable 1573 // Comment + {|Warning:using System;|} + {|Warning:using System.Threading;|} +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, InsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that the code fix will properly move regions. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInCompilationUnitWithRegion_RegionMoved() + { + var testCode = @"#region Comment +#endregion Comment +[|using System; +using System.Threading;|] + +namespace TestNamespace +{ +} +"; + + var fixedTestCode = @"namespace TestNamespace +{ + #region Comment + #endregion Comment + {|Warning:using System;|} + {|Warning:using System.Threading;|} +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, InsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + /// + /// Verifies that the code fix will properly move comment trivia. + /// + [Fact] + public Task WhenInsidePreferred_UsingsInCompilationUnitWithCommentTrivia_TriviaMoved() + { + var testCode = @" +// Some comment +[|using System; +using System.Threading;|] + +namespace TestNamespace +{ +} +"; + + var fixedTestCode = @"namespace TestNamespace +{ + + // Some comment + {|Warning:using System;|} + {|Warning:using System.Threading;|} +} +"; + + return TestInRegularAndScriptAsync(testCode, fixedTestCode, InsideNamespaceOption, placeSystemNamespaceFirst: true); + } + + #endregion + } +} diff --git a/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs index 661b50e2fbf..d7b0a76bad9 100644 --- a/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs +++ b/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs @@ -40,6 +40,7 @@ internal static class PredefinedCodeFixProviderNames public const string MakeFieldReadonly = nameof(MakeFieldReadonly); public const string MakeStatementAsynchronous = nameof(MakeStatementAsynchronous); public const string MakeMethodSynchronous = nameof(MakeMethodSynchronous); + public const string MoveMisplacedUsingDirectives = nameof(MoveMisplacedUsingDirectives); public const string MoveToTopOfFile = nameof(MoveToTopOfFile); public const string PopulateSwitch = nameof(PopulateSwitch); public const string QualifyMemberAccess = nameof(QualifyMemberAccess); diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs index ad081293c35..a42770a665f 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs @@ -107,6 +107,7 @@ internal static class IDEDiagnosticIds public const string MakeStructFieldsWritable = "IDE0064"; + public const string MoveMisplacedUsingDirectivesDiagnosticId = "IDE0065"; // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs b/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs index 5847b3fc3f3..bf0b3c1b102 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs @@ -654,6 +654,15 @@ internal class CSharpVSResources { } } + /// + /// Looks up a localized string similar to Inside namespace. + /// + internal static string Inside_namespace { + get { + return ResourceManager.GetString("Inside_namespace", resourceCulture); + } + } + /// /// Looks up a localized string similar to Label Indentation. /// @@ -780,6 +789,15 @@ internal class CSharpVSResources { } } + /// + /// Looks up a localized string similar to Outside namespace. + /// + internal static string Outside_namespace { + get { + return ResourceManager.GetString("Outside_namespace", resourceCulture); + } + } + /// /// Looks up a localized string similar to Perform additional code cleanup during formatting. /// @@ -1077,6 +1095,15 @@ internal class CSharpVSResources { } } + /// + /// Looks up a localized string similar to Preferred 'using' directive placement. + /// + internal static string Preferred_using_directive_placement { + get { + return ResourceManager.GetString("Preferred_using_directive_placement", resourceCulture); + } + } + /// /// Looks up a localized string similar to Qualify event access with 'this'. /// @@ -1392,6 +1419,15 @@ internal class CSharpVSResources { } } + /// + /// Looks up a localized string similar to 'using' preferences:. + /// + internal static string using_preferences_colon { + get { + return ResourceManager.GetString("using_preferences_colon", resourceCulture); + } + } + /// /// Looks up a localized string similar to 'var' preferences:. /// diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx index b096c536548..ae2f717d634 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx @@ -577,4 +577,20 @@ Show items from unimported namespaces (experimental) + + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + 'namespace' is a C# keyword and should not be localized + + + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + + + 'using' preferences: + 'using' is a C# keyword and should not be localized + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs index 156b2174f6d..044dae74b8d 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Windows.Data; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.AddImports; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.Options; @@ -1164,6 +1165,34 @@ class Customer2 }} "; + private static readonly string[] s_usingDirectivePlacement = new[] { $@" +//[ + namespace Namespace + {{ + // {CSharpVSResources.Inside_namespace} + using System; + using System.Linq; + + class Customer + {{ + }} + }} +//]", $@" +//[ + // {CSharpVSResources.Outside_namespace} + using System; + using System.Linq; + + namespace Namespace + {{ + class Customer + {{ + }} + }} +//] +" }; + + private static readonly string s_preferStaticLocalFunction = $@" class Customer1 {{ @@ -1510,12 +1539,19 @@ internal StyleViewModel(OptionStore optionStore, IServiceProvider serviceProvide var predefinedTypesGroupTitle = CSharpVSResources.predefined_type_preferences_colon; var varGroupTitle = CSharpVSResources.var_preferences_colon; var nullCheckingGroupTitle = CSharpVSResources.null_checking_colon; + var usingsGroupTitle = CSharpVSResources.using_preferences_colon; var modifierGroupTitle = ServicesVSResources.Modifier_preferences_colon; var codeBlockPreferencesGroupTitle = ServicesVSResources.Code_block_preferences_colon; var expressionPreferencesGroupTitle = ServicesVSResources.Expression_preferences_colon; var variablePreferencesGroupTitle = ServicesVSResources.Variable_preferences_colon; var parameterPreferencesGroupTitle = ServicesVSResources.Parameter_preferences_colon; + var usingDirectivePlacementPreferences = new List + { + new CodeStylePreference(CSharpVSResources.Inside_namespace, isChecked: false), + new CodeStylePreference(CSharpVSResources.Outside_namespace, isChecked: false), + }; + var qualifyMemberAccessPreferences = new List { new CodeStylePreference(CSharpVSResources.Prefer_this, isChecked: true), @@ -1585,6 +1621,12 @@ internal StyleViewModel(OptionStore optionStore, IServiceProvider serviceProvide CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferNullPropagation, ServicesVSResources.Prefer_null_propagation, s_preferNullPropagation, s_preferNullPropagation, this, optionStore, nullCheckingGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferIsNullCheckOverReferenceEqualityMethod, CSharpVSResources.Prefer_is_null_for_reference_equality_checks, s_preferIsNullOverReferenceEquals, s_preferIsNullOverReferenceEquals, this, optionStore, nullCheckingGroupTitle)); + // Using directive preferences. + CodeStyleItems.Add(new EnumCodeStyleOptionViewModel( + CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, CSharpVSResources.Preferred_using_directive_placement, + new[] { AddImportPlacement.InsideNamespace, AddImportPlacement.OutsideNamespace }, + s_usingDirectivePlacement, this, optionStore, usingsGroupTitle, usingDirectivePlacementPreferences)); + // Modifier preferences. CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferReadonly, ServicesVSResources.Prefer_readonly_fields, s_preferReadonly, s_preferReadonly, this, optionStore, modifierGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferStaticLocalFunction, ServicesVSResources.Prefer_static_local_functions, s_preferStaticLocalFunction, s_preferStaticLocalFunction, this, optionStore, modifierGroupTitle)); diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf index cfd8e6c956f..2ee1b1db193 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf @@ -42,6 +42,16 @@ V relačních operátorech: < > <= >= is as == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting Při formátování provést dodatečné čištění kódu @@ -52,6 +62,11 @@ U kontrol rovnosti odkazů dávat přednost možnosti is null 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings Odebrat nepotřebné direktivy using @@ -597,6 +612,11 @@ 'Předvolby pro „this.“: + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'Předvolby pro „var“: diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf index d49b1d9be59..6f082fcb729 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf @@ -42,6 +42,16 @@ In relationalen Operatoren: < > <= >= is as == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting Zusätzliche Codebereinigung während der Formatierung durchführen @@ -52,6 +62,11 @@ "is null" für Verweisübereinstimmungsprüfungen vorziehen 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings Nicht benötigte Using-Direktiven entfernen @@ -597,6 +612,11 @@ 'this.-Einstellungen: + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'var-Einstellungen: diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf index 7b0953539c3..694a139f32e 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf @@ -42,6 +42,16 @@ En los operadores relacionales: < > <= >= is as == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting Realizar limpieza de código adicional durante la aplicación de formato @@ -52,6 +62,11 @@ Preferir “is null” para comprobaciones de igualdad de referencias 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings Eliminar instrucciones Using innecesarias @@ -597,6 +612,11 @@ 'Preferencias de "this.": + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'Preferencias de 'var': diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf index 4cd9270226a..2701391a18f 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf @@ -42,6 +42,16 @@ Dans les opérateurs relationnels : < > <= >= is as == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting Effectuer un nettoyage supplémentaire du code pendant la mise en forme @@ -52,6 +62,11 @@ Préférer 'is nul' pour les vérifications d'égalité de référence 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings Supprimer les Usings inutiles @@ -597,6 +612,11 @@ 'Préférences 'this.' : + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'Préférences 'var' : diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf index 13b2735d954..35bb1cddc76 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf @@ -42,6 +42,16 @@ In operatori relazionali: < > <= >= is as == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting Esegui la pulizia aggiuntiva del codice durante la formattazione @@ -52,6 +62,11 @@ Preferisci 'is null' per i controlli di uguaglianza dei riferimenti 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings Rimuovi istruzioni using non necessarie @@ -597,6 +612,11 @@ 'Preferenze per 'this.': + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'Preferenze per 'var': diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf index d2b50779abe..445a1eb7a14 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf @@ -42,6 +42,16 @@ 関係演算子: < > <= >= is as == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting 書式設定中に追加のコード クリーンアップを実行 @@ -52,6 +62,11 @@ 参照の等値性のチェックには 'is null' を優先する 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings 不要な using の削除 @@ -597,6 +612,11 @@ 'this' の優先: + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'var' を優先: diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf index 17d3a03d3aa..08808134e1d 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf @@ -42,6 +42,16 @@ 관계 연산자: < > <= >= is as == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting 서식을 지정하는 동안 추가 코드 정리 수행 @@ -52,6 +62,11 @@ 참조 같음 검사에 대해 'is null' 선호 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings 불필요한 Using 제거 @@ -597,6 +612,11 @@ 'this.' 기본 설정: + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'var' 기본 설정: diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf index 92425c2b97b..e8848abcc15 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf @@ -42,6 +42,16 @@ W operatorach relacyjnych: < > <= >= is as == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting Wykonaj dodatkowe oczyszczanie kodu podczas formatowania @@ -52,6 +62,11 @@ Preferuj wyrażenie „is null” w przypadku sprawdzeń odwołań pod kątem równości 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings Usuń niepotrzebne użycia @@ -597,6 +612,11 @@ 'Preferencje dotyczące elementu „this.”: + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'Preferencje elementu „var”: diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf index a2e7eeca566..5f0f3d1c0f7 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf @@ -42,6 +42,16 @@ Em operadores relacionais: < > <= >= is as == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting Executar limpeza de código adicional durante a formatação @@ -52,6 +62,11 @@ Preferir 'is null' para as verificações de igualdade de referência 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings Remover Usos Desnecessários @@ -597,6 +612,11 @@ 'Preferências de 'this.': + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'Preferências de 'var': diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf index 6f5c0cb6618..d67f7a81604 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf @@ -42,6 +42,16 @@ В реляционных операторах: < > <= >= is as == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting Выполнять при форматировании дополнительную очистку кода @@ -52,6 +62,11 @@ Использовать "is null" вместо проверки ссылок на равенство. 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings Удалить ненужные директивы using @@ -597,6 +612,11 @@ 'предпочтения "this.": + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'предпочтения "var": diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf index 6364e7f8b89..b9a7d1f1953 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf @@ -42,6 +42,16 @@ İlişkisel işleçleri içinde: <> = < > = == gibi! = + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting Biçimlendirme sırasında ek kod temizleme gerçekleştir @@ -52,6 +62,11 @@ Başvuru eşitliği denetimleri için 'is null'ı tercih et 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings Gereksiz Kullanımları Kaldır @@ -597,6 +612,11 @@ 'This' tercihleri: + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'var' tercihleri: diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf index d559ab4eaaa..5a26cf58f22 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf @@ -42,6 +42,16 @@ 在关系运算符中: < > <= >= 等同于 == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting 在格式设置期间执行其他代码清理 @@ -52,6 +62,11 @@ 引用相等检查偏好 “is null” 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings 删除不必要的 Using @@ -597,6 +612,11 @@ '"this." 首选项: + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: '"var" 首选项: diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf index d21a00b156b..957e96ecb3e 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf @@ -42,6 +42,16 @@ 在關係運算子中: < > <= >= 如同 == != + + Inside namespace + Inside namespace + 'namespace' is a C# keyword and should not be localized + + + Outside namespace + Outside namespace + 'namespace' is a C# keyword and should not be localized + Perform additional code cleanup during formatting 在格式化期間執行額外程式碼清除 @@ -52,6 +62,11 @@ 參考相等檢查最好使用 'is null' 'is null' is a C# string and should not be localized. + + Preferred 'using' directive placement + Preferred 'using' directive placement + 'using' is a C# keyword and should not be localized + Remove unnecessary usings 移除不必要的 Using @@ -597,6 +612,11 @@ 'this.' 喜好設定: + + 'using' preferences: + 'using' preferences: + 'using' is a C# keyword and should not be localized + 'var' preferences: 'var' 喜歡設定: diff --git a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb index 4509168827f..95497f2204f 100644 --- a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb @@ -120,6 +120,9 @@ csharp_style_prefer_range_operator = true:suggestion csharp_style_unused_value_assignment_preference = discard_variable:suggestion csharp_style_unused_value_expression_statement_preference = discard_variable:silent +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + #### C# Formatting Rules #### # New line preferences @@ -325,6 +328,9 @@ csharp_style_prefer_range_operator = true:suggestion csharp_style_unused_value_assignment_preference = discard_variable:suggestion csharp_style_unused_value_expression_statement_preference = discard_variable:silent +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + #### C# Formatting Rules #### # New line preferences diff --git a/src/Workspaces/CSharp/Portable/CSharpWorkspaceResources.Designer.cs b/src/Workspaces/CSharp/Portable/CSharpWorkspaceResources.Designer.cs index 8c0e6852b49..0b37ee9480b 100644 --- a/src/Workspaces/CSharp/Portable/CSharpWorkspaceResources.Designer.cs +++ b/src/Workspaces/CSharp/Portable/CSharpWorkspaceResources.Designer.cs @@ -10,7 +10,6 @@ namespace Microsoft.CodeAnalysis.CSharp { using System; - using System.Reflection; /// @@ -20,7 +19,7 @@ namespace Microsoft.CodeAnalysis.CSharp { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class CSharpWorkspaceResources { @@ -40,7 +39,7 @@ internal class CSharpWorkspaceResources { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CodeAnalysis.CSharp.CSharpWorkspaceResources", typeof(CSharpWorkspaceResources).GetTypeInfo().Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CodeAnalysis.CSharp.CSharpWorkspaceResources", typeof(CSharpWorkspaceResources).Assembly); resourceMan = temp; } return resourceMan; @@ -197,6 +196,15 @@ internal class CSharpWorkspaceResources { } } + /// + /// Looks up a localized string similar to 'using' directive preferences. + /// + internal static string using_directive_preferences { + get { + return ResourceManager.GetString("using_directive_preferences", resourceCulture); + } + } + /// /// Looks up a localized string similar to var preferences. /// diff --git a/src/Workspaces/CSharp/Portable/CSharpWorkspaceResources.resx b/src/Workspaces/CSharp/Portable/CSharpWorkspaceResources.resx index 19a32539a3c..b5319a9b609 100644 --- a/src/Workspaces/CSharp/Portable/CSharpWorkspaceResources.resx +++ b/src/Workspaces/CSharp/Portable/CSharpWorkspaceResources.resx @@ -168,4 +168,8 @@ Wrapping preferences + + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs b/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs index 90fedcef8fc..4c54a1c33a5 100644 --- a/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs +++ b/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Microsoft.CodeAnalysis.AddImports; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Options; @@ -227,6 +228,19 @@ private static Option CreateOption(OptionGroup group, string name, T defau EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_style_pattern_local_over_anonymous_function"), new RoamingProfileStorageLocation($"TextEditor.CSharp.Specific.{nameof(PreferLocalOverAnonymousFunction)}")}); + public static readonly CodeStyleOption PreferOutsidePlacementWithSilentEnforcement = + new CodeStyleOption(AddImportPlacement.OutsideNamespace, NotificationOption.Silent); + + public static readonly Option> PreferredUsingDirectivePlacement = CreateOption( + CSharpCodeStyleOptionGroups.UsingDirectivePreferences, nameof(PreferredUsingDirectivePlacement), + defaultValue: PreferOutsidePlacementWithSilentEnforcement, + storageLocations: new OptionStorageLocation[]{ + new EditorConfigStorageLocation>( + "csharp_using_directive_placement", + s => ParseUsingDirectivesPlacement(s, PreferOutsidePlacementWithSilentEnforcement), + GetUsingDirectivesPlacementEditorConfigString), + new RoamingProfileStorageLocation($"TextEditor.CSharp.Specific.{nameof(PreferredUsingDirectivePlacement)}") }); + internal static readonly Option> UnusedValueExpressionStatement = CodeStyleHelpers.CreateUnusedExpressionAssignmentOption( CSharpCodeStyleOptionGroups.ExpressionLevelPreferences, @@ -287,5 +301,6 @@ internal static class CSharpCodeStyleOptionGroups public static readonly OptionGroup Modifier = new OptionGroup(WorkspacesResources.Modifier_preferences, priority: 5); public static readonly OptionGroup CodeBlockPreferences = new OptionGroup(CSharpWorkspaceResources.Code_block_preferences, priority: 6); public static readonly OptionGroup ExpressionLevelPreferences = new OptionGroup(WorkspacesResources.Expression_level_preferences, priority: 7); + public static readonly OptionGroup UsingDirectivePreferences = new OptionGroup(CSharpWorkspaceResources.using_directive_preferences, priority: 8); } } diff --git a/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions_Parsing.cs b/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions_Parsing.cs index 2389fb17fad..dcf6bde024e 100644 --- a/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions_Parsing.cs +++ b/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions_Parsing.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; +using Microsoft.CodeAnalysis.AddImports; using Microsoft.CodeAnalysis.CodeStyle; using Roslyn.Utilities; @@ -51,6 +52,45 @@ private static string GetExpressionBodyPreferenceEditorConfigString(CodeStyleOpt } } + public static CodeStyleOption ParseUsingDirectivesPlacement( + string optionString, CodeStyleOption @default) + { + // optionString must be similar to outside_namespace:error or inside_namespace:suggestion. + if (CodeStyleHelpers.TryGetCodeStyleValueAndOptionalNotification( + optionString, out var value, out var notificationOpt)) + { + // A notification value must be provided. + if (notificationOpt != null) + { + switch (value) + { + case "inside_namespace": + return new CodeStyleOption(AddImportPlacement.InsideNamespace, notificationOpt); + case "outside_namespace": + return new CodeStyleOption(AddImportPlacement.OutsideNamespace, notificationOpt); + default: + throw new NotSupportedException(); + } + } + } + + return @default; + } + + public static string GetUsingDirectivesPlacementEditorConfigString(CodeStyleOption value) + { + Debug.Assert(value.Notification != null); + + var notificationString = value.Notification.ToEditorConfigString(); + switch (value.Value) + { + case AddImportPlacement.InsideNamespace: return $"inside_namespace:{notificationString}"; + case AddImportPlacement.OutsideNamespace: return $"outside_namespace:{notificationString}"; + default: + throw new NotSupportedException(); + } + } + private static CodeStyleOption ParsePreferBracesPreference( string optionString, CodeStyleOption defaultValue) diff --git a/src/Workspaces/CSharp/Portable/Execution/CSharpOptionsSerializationService.cs b/src/Workspaces/CSharp/Portable/Execution/CSharpOptionsSerializationService.cs index 537470ccbe1..88307e213b7 100644 --- a/src/Workspaces/CSharp/Portable/Execution/CSharpOptionsSerializationService.cs +++ b/src/Workspaces/CSharp/Portable/Execution/CSharpOptionsSerializationService.cs @@ -49,6 +49,7 @@ public override void WriteTo(OptionSet options, ObjectWriter writer, Cancellatio WriteOptionTo(options, CSharpCodeStyleOptions.PreferBraces, writer, cancellationToken); WriteOptionTo(options, CSharpCodeStyleOptions.PreferredModifierOrder, writer, cancellationToken); + WriteOptionTo(options, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, writer, cancellationToken); WriteOptionTo(options, CSharpCodeStyleOptions.PreferStaticLocalFunction, writer, cancellationToken); } @@ -70,6 +71,7 @@ public override OptionSet ReadOptionSetFrom(ObjectReader reader, CancellationTok options = ReadOptionFrom(options, CSharpCodeStyleOptions.PreferBraces, reader, cancellationToken); options = ReadOptionFrom(options, CSharpCodeStyleOptions.PreferredModifierOrder, reader, cancellationToken); + options = ReadOptionFrom(options, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, reader, cancellationToken); options = ReadOptionFrom(options, CSharpCodeStyleOptions.PreferStaticLocalFunction, reader, cancellationToken); return options; diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.cs.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.cs.xlf index 644b79ba3bf..43ed76122d3 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.cs.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.cs.xlf @@ -82,6 +82,11 @@ Předvolby obálek + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences Předvolby pro var diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.de.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.de.xlf index a8dd8201f29..5a5ecba2911 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.de.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.de.xlf @@ -82,6 +82,11 @@ Einstellungen für Umbrüche + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences Var-Einstellungen diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.es.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.es.xlf index 75ab1dff95f..0c411d8a230 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.es.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.es.xlf @@ -82,6 +82,11 @@ Preferencias de encapsulado + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences Preferencias de var diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.fr.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.fr.xlf index 29e8b4e4689..8a89f40dff6 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.fr.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.fr.xlf @@ -82,6 +82,11 @@ Préférences de retour à la ligne + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences Préférences de var diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.it.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.it.xlf index d70ebaa29c3..0296b1fb3bc 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.it.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.it.xlf @@ -82,6 +82,11 @@ Preferenze per ritorno a capo + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences Preferenze per var diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ja.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ja.xlf index 879d322be20..be610fc9b19 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ja.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ja.xlf @@ -82,6 +82,11 @@ 折り返しの設定 + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences var を優先 diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ko.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ko.xlf index 32f9aa6b1bd..2f2334d462f 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ko.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ko.xlf @@ -82,6 +82,11 @@ 기본 설정 줄 바꿈 + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences var 기본 설정 diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.pl.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.pl.xlf index b0c431ec32e..a6ed2bd3102 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.pl.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.pl.xlf @@ -82,6 +82,11 @@ Preferencje zawijania + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences Preferencje zmiennych diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.pt-BR.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.pt-BR.xlf index ca4eb877653..a18adb4b269 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.pt-BR.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.pt-BR.xlf @@ -82,6 +82,11 @@ Preferências de disposição + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences preferências de var diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ru.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ru.xlf index ab9a62aa7f8..c09ffb21fd9 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ru.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.ru.xlf @@ -82,6 +82,11 @@ Предпочтения переноса + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences Предпочтения var diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.tr.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.tr.xlf index 4950c920a2a..6c1224d1f11 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.tr.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.tr.xlf @@ -82,6 +82,11 @@ Kaydırma tercihleri + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences var tercihleri diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.zh-Hans.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.zh-Hans.xlf index b8288774bc7..d661a281643 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.zh-Hans.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.zh-Hans.xlf @@ -82,6 +82,11 @@ 包装首选项 + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences var 首选项 diff --git a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.zh-Hant.xlf b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.zh-Hant.xlf index d7ee717a62c..61c5002febe 100644 --- a/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.zh-Hant.xlf +++ b/src/Workspaces/CSharp/Portable/xlf/CSharpWorkspaceResources.zh-Hant.xlf @@ -82,6 +82,11 @@ 換行喜好設定 + + 'using' directive preferences + 'using' directive preferences + {Locked="using"} "using" is a C# keyword and should not be localized. + var preferences var 喜好設定 diff --git a/src/Workspaces/Core/Portable/AddImports/AddImportPlacement.cs b/src/Workspaces/Core/Portable/AddImports/AddImportPlacement.cs new file mode 100644 index 00000000000..a2c494029fc --- /dev/null +++ b/src/Workspaces/Core/Portable/AddImports/AddImportPlacement.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.AddImports +{ + /// + /// Specifies the desired placement of added imports. + /// + internal enum AddImportPlacement + { + /// + /// Place imports inside the namespace definition. + /// + InsideNamespace, + + /// + /// Place imports outside the namespace definition. + /// + OutsideNamespace + } +} -- GitLab