From 92e24c0ce56b0a8887fdd00f41926a15a917f819 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 21 Jul 2021 11:54:06 -0700 Subject: [PATCH 1/4] Fix 'match folder and namespace' with file scoped namespaces --- ...tchFolderAndNamespaceDiagnosticAnalyzer.cs | 10 +-- .../CSharpMatchFolderAndNamespaceTests.cs | 85 +++++++++++++++++-- ...tchFolderAndNamespaceDiagnosticAnalyzer.cs | 10 ++- .../CSharpChangeNamespaceService.cs | 80 +++++++---------- .../Services/SyntaxFacts/CSharpSyntaxFacts.cs | 11 ++- 5 files changed, 129 insertions(+), 67 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceDiagnosticAnalyzer.cs index a04f3b2dd3674..41957869f042b 100644 --- a/src/Analyzers/CSharp/Analyzers/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceDiagnosticAnalyzer.cs @@ -7,17 +7,17 @@ using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.CSharp.LanguageServices; +using System.Collections.Immutable; namespace Microsoft.CodeAnalysis.CSharp.Analyzers.MatchFolderAndNamespace { [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class CSharpMatchFolderAndNamespaceDiagnosticAnalyzer : AbstractMatchFolderAndNamespaceDiagnosticAnalyzer + internal class CSharpMatchFolderAndNamespaceDiagnosticAnalyzer + : AbstractMatchFolderAndNamespaceDiagnosticAnalyzer { protected override ISyntaxFacts GetSyntaxFacts() => CSharpSyntaxFacts.Instance; - protected override void InitializeWorker(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeNamespaceNode, SyntaxKind.NamespaceDeclaration); - } + protected override ImmutableArray GetSyntaxKindsToAnalyze() + => ImmutableArray.Create(SyntaxKind.NamespaceDeclaration, SyntaxKind.FileScopedNamespaceDeclaration); } } diff --git a/src/Analyzers/CSharp/Tests/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceTests.cs b/src/Analyzers/CSharp/Tests/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceTests.cs index 70db7f77267f4..56ff6f07be92f 100644 --- a/src/Analyzers/CSharp/Tests/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceTests.cs +++ b/src/Analyzers/CSharp/Tests/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceTests.cs @@ -45,19 +45,16 @@ private static Task RunTestAsync(IEnumerable<(string, string)> originalSources, var testState = new VerifyCS.Test { EditorConfig = editorconfig ?? EditorConfig, - CodeFixTestBehaviors = CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInDocumentCheck + CodeFixTestBehaviors = CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInDocumentCheck, + LanguageVersion = LanguageVersion.CSharp10, }; foreach (var (fileName, content) in originalSources) - { testState.TestState.Sources.Add((fileName, content)); - } fixedSources ??= Array.Empty<(string, string)>(); foreach (var (fileName, content) in fixedSources) - { testState.FixedState.Sources.Add((fileName, content)); - } return testState.RunAsync(); } @@ -82,6 +79,26 @@ class Class1 directory: folder); } + [Fact] + public Task InvalidFolderName1_NoDiagnostic_FileScopedNamespace() + { + // No change namespace action because the folder name is not valid identifier + var folder = CreateFolderPath(new[] { "3B", "C" }); + var code = +@" +namespace A.B; + +class Class1 +{ +} +"; + + return RunTestAsync( + "File1.cs", + code, + directory: folder); + } + [Fact] public Task InvalidFolderName2_NoDiagnostic() { @@ -167,6 +184,32 @@ await RunTestAsync( fixedCode: fixedCode); } + [Fact] + public async Task SingleDocumentNoReference_FileScopedNamespace() + { + var folder = CreateFolderPath("B", "C"); + var code = +@"namespace [|A.B|]; + +class Class1 +{ +} +"; + + var fixedCode = +@$"namespace {DefaultNamespace}.B.C; + +class Class1 +{{ +}} +"; + await RunTestAsync( + fileName: "Class1.cs", + fileContents: code, + directory: folder, + fixedCode: fixedCode); + } + [Fact] public async Task SingleDocumentNoReference_NoDefaultNamespace() { @@ -199,6 +242,38 @@ await RunTestAsync( editorConfig: editorConfig); } + [Fact] + public async Task SingleDocumentNoReference_NoDefaultNamespace_FileScopedNamespace() + { + var editorConfig = @$" +is_global=true +build_property.ProjectDir = {Directory} +"; + + var folder = CreateFolderPath("B", "C"); + var code = +@"namespace [|A.B|]; + +class Class1 +{ +} +"; + + var fixedCode = +@$"namespace B.C; + +class Class1 +{{ +}} +"; + await RunTestAsync( + fileName: "Class1.cs", + fileContents: code, + directory: folder, + fixedCode: fixedCode, + editorConfig: editorConfig); + } + [Fact] public async Task NamespaceWithSpaces_NoDiagnostic() { diff --git a/src/Analyzers/Core/Analyzers/MatchFolderAndNamespace/AbstractMatchFolderAndNamespaceDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/MatchFolderAndNamespace/AbstractMatchFolderAndNamespaceDiagnosticAnalyzer.cs index 41929932279c7..89b33401c61a8 100644 --- a/src/Analyzers/Core/Analyzers/MatchFolderAndNamespace/AbstractMatchFolderAndNamespaceDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/MatchFolderAndNamespace/AbstractMatchFolderAndNamespaceDiagnosticAnalyzer.cs @@ -16,7 +16,9 @@ namespace Microsoft.CodeAnalysis.Analyzers.MatchFolderAndNamespace { - internal abstract class AbstractMatchFolderAndNamespaceDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + internal abstract class AbstractMatchFolderAndNamespaceDiagnosticAnalyzer + : AbstractBuiltInCodeStyleDiagnosticAnalyzer + where TSyntaxKind : struct where TNamespaceSyntax : SyntaxNode { private static readonly LocalizableResourceString s_localizableTitle = new( @@ -39,11 +41,15 @@ protected AbstractMatchFolderAndNamespaceDiagnosticAnalyzer() } protected abstract ISyntaxFacts GetSyntaxFacts(); + protected abstract ImmutableArray GetSyntaxKindsToAnalyze(); + + protected sealed override void InitializeWorker(AnalysisContext context) + => context.RegisterSyntaxNodeAction(AnalyzeNamespaceNode, GetSyntaxKindsToAnalyze()); public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; - protected void AnalyzeNamespaceNode(SyntaxNodeAnalysisContext context) + private void AnalyzeNamespaceNode(SyntaxNodeAnalysisContext context) { // It's ok to not have a rootnamespace property, but if it's there we want to use it correctly context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue(MatchFolderAndNamespaceConstants.RootNamespaceOption, out var rootNamespace); diff --git a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs index 0eefbb097f6dd..2ed899fee3d1b 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs @@ -25,7 +25,7 @@ namespace Microsoft.CodeAnalysis.CSharp.ChangeNamespace { [ExportLanguageService(typeof(IChangeNamespaceService), LanguageNames.CSharp), Shared] internal sealed class CSharpChangeNamespaceService : - AbstractChangeNamespaceService + AbstractChangeNamespaceService { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -45,7 +45,7 @@ public CSharpChangeNamespaceService() } TextSpan containerSpan; - if (container is NamespaceDeclarationSyntax) + if (container is BaseNamespaceDeclarationSyntax) { containerSpan = container.Span; } @@ -61,40 +61,30 @@ public CSharpChangeNamespaceService() } if (!IsSupportedLinkedDocument(document, out var allDocumentIds)) - { return default; - } - return await TryGetApplicableContainersFromAllDocumentsAsync(document.Project.Solution, allDocumentIds, containerSpan, cancellationToken) - .ConfigureAwait(false); + return await TryGetApplicableContainersFromAllDocumentsAsync( + document.Project.Solution, allDocumentIds, containerSpan, cancellationToken).ConfigureAwait(false); } protected override string GetDeclaredNamespace(SyntaxNode container) { if (container is CompilationUnitSyntax) - { return string.Empty; - } - if (container is NamespaceDeclarationSyntax namespaceDecl) - { + if (container is BaseNamespaceDeclarationSyntax namespaceDecl) return CSharpSyntaxGenerator.Instance.GetName(namespaceDecl); - } throw ExceptionUtilities.Unreachable; } protected override SyntaxList GetMemberDeclarationsInContainer(SyntaxNode container) { - if (container is NamespaceDeclarationSyntax namespaceDecl) - { + if (container is BaseNamespaceDeclarationSyntax namespaceDecl) return namespaceDecl.Members; - } if (container is CompilationUnitSyntax compilationUnit) - { return compilationUnit.Members; - } throw ExceptionUtilities.Unreachable; } @@ -117,7 +107,7 @@ public override bool TryGetReplacementReferenceSyntax( [NotNullWhen(returnValue: true)] out SyntaxNode? oldNode, [NotNullWhen(returnValue: true)] out SyntaxNode? newNode) { - if (!(reference is SimpleNameSyntax nameRef)) + if (reference is not SimpleNameSyntax nameRef) { oldNode = newNode = null; return false; @@ -251,13 +241,11 @@ protected override CompilationUnitSyntax ChangeNamespaceDeclaration( return MoveMembersFromGlobalToNamespace(compilationUnit, targetNamespaceParts); } - if (container is NamespaceDeclarationSyntax namespaceDecl) + if (container is BaseNamespaceDeclarationSyntax namespaceDecl) { // Move everything to global namespace if (IsGlobalNamespace(targetNamespaceParts)) - { return MoveMembersFromNamespaceToGlobal(root, namespaceDecl); - } // Change namespace name return root.ReplaceNode( @@ -271,7 +259,8 @@ protected override CompilationUnitSyntax ChangeNamespaceDeclaration( throw ExceptionUtilities.Unreachable; } - private static CompilationUnitSyntax MoveMembersFromNamespaceToGlobal(CompilationUnitSyntax root, NamespaceDeclarationSyntax namespaceDecl) + private static CompilationUnitSyntax MoveMembersFromNamespaceToGlobal( + CompilationUnitSyntax root, BaseNamespaceDeclarationSyntax namespaceDecl) { var (namespaceOpeningTrivia, namespaceClosingTrivia) = GetOpeningAndClosingTriviaOfNamespaceDeclaration(namespaceDecl); @@ -319,7 +308,7 @@ private static CompilationUnitSyntax MoveMembersFromNamespaceToGlobal(Compilatio private static CompilationUnitSyntax MoveMembersFromGlobalToNamespace(CompilationUnitSyntax compilationUnit, ImmutableArray targetNamespaceParts) { - Debug.Assert(!compilationUnit.Members.Any(m => m is NamespaceDeclarationSyntax)); + Debug.Assert(!compilationUnit.Members.Any(m => m is BaseNamespaceDeclarationSyntax)); var targetNamespaceDecl = SyntaxFactory.NamespaceDeclaration( name: CreateNamespaceAsQualifiedName(targetNamespaceParts, aliasQualifier: null, targetNamespaceParts.Length - 1) @@ -356,9 +345,7 @@ private static CompilationUnitSyntax MoveMembersFromGlobalToNamespace(Compilatio if (span.IsEmpty) { if (ContainsNamespaceDeclaration(compilationUnit)) - { return null; - } container = compilationUnit; } @@ -367,27 +354,19 @@ private static CompilationUnitSyntax MoveMembersFromGlobalToNamespace(Compilatio // Otherwise, the span should contain a namespace declaration node, which must be the only one // in the entire syntax spine to enable the change namespace operation. if (!compilationUnit.Span.Contains(span)) - { return null; - } var node = compilationUnit.FindNode(span, getInnermostNodeForTie: true); - var namespaceDecl = node.AncestorsAndSelf().OfType().SingleOrDefault(); + var namespaceDecl = node.AncestorsAndSelf().OfType().SingleOrDefault(); if (namespaceDecl == null) - { return null; - } if (namespaceDecl.Name.GetDiagnostics().Any(diag => diag.DefaultSeverity == DiagnosticSeverity.Error)) - { return null; - } if (ContainsNamespaceDeclaration(node)) - { return null; - } container = namespaceDecl; } @@ -396,15 +375,13 @@ private static CompilationUnitSyntax MoveMembersFromGlobalToNamespace(Compilatio await ContainsPartialTypeWithMultipleDeclarationsAsync(document, container, cancellationToken).ConfigureAwait(false); if (containsPartial) - { return null; - } return container; static bool ContainsNamespaceDeclaration(SyntaxNode node) - => node.DescendantNodes(n => n is CompilationUnitSyntax || n is NamespaceDeclarationSyntax) - .OfType().Any(); + => node.DescendantNodes(n => n is CompilationUnitSyntax || n is BaseNamespaceDeclarationSyntax) + .OfType().Any(); } private static string? GetAliasQualifier(SyntaxNode? name) @@ -435,11 +412,7 @@ private static NameSyntax CreateNamespaceAsQualifiedName(ImmutableArray var namePiece = SyntaxFactory.IdentifierName(part); if (index == 0) - { - return aliasQualifier == null - ? (NameSyntax)namePiece - : SyntaxFactory.AliasQualifiedName(aliasQualifier, namePiece); - } + return aliasQualifier == null ? namePiece : SyntaxFactory.AliasQualifiedName(aliasQualifier, namePiece); return SyntaxFactory.QualifiedName(CreateNamespaceAsQualifiedName(namespaceParts, aliasQualifier, index - 1), namePiece); } @@ -470,16 +443,25 @@ private static ExpressionSyntax CreateNamespaceAsMemberAccess(ImmutableArray private static (ImmutableArray openingTrivia, ImmutableArray closingTrivia) - GetOpeningAndClosingTriviaOfNamespaceDeclaration(NamespaceDeclarationSyntax namespaceDeclaration) + GetOpeningAndClosingTriviaOfNamespaceDeclaration(BaseNamespaceDeclarationSyntax baseNamespace) { var openingBuilder = ArrayBuilder.GetInstance(); - openingBuilder.AddRange(namespaceDeclaration.GetLeadingTrivia()); - openingBuilder.AddRange(namespaceDeclaration.OpenBraceToken.LeadingTrivia); - openingBuilder.AddRange(namespaceDeclaration.OpenBraceToken.TrailingTrivia); - var closingBuilder = ArrayBuilder.GetInstance(); - closingBuilder.AddRange(namespaceDeclaration.CloseBraceToken.LeadingTrivia); - closingBuilder.AddRange(namespaceDeclaration.CloseBraceToken.TrailingTrivia); + + openingBuilder.AddRange(baseNamespace.GetLeadingTrivia()); + + if (baseNamespace is NamespaceDeclarationSyntax namespaceDeclaration) + { + openingBuilder.AddRange(namespaceDeclaration.OpenBraceToken.LeadingTrivia); + openingBuilder.AddRange(namespaceDeclaration.OpenBraceToken.TrailingTrivia); + + closingBuilder.AddRange(namespaceDeclaration.CloseBraceToken.LeadingTrivia); + closingBuilder.AddRange(namespaceDeclaration.CloseBraceToken.TrailingTrivia); + } + else if (baseNamespace is FileScopedNamespaceDeclarationSyntax fileScopedNamespace) + { + openingBuilder.AddRange(fileScopedNamespace.SemicolonToken.TrailingTrivia); + } return (openingBuilder.ToImmutableAndFree(), closingBuilder.ToImmutableAndFree()); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 734e129d6ce0f..8e608e84c532a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -814,7 +814,7 @@ public string GetDisplayName(SyntaxNode? node, DisplayNameOptions options, strin // containing namespace(s) in source (if any) if ((options & DisplayNameOptions.IncludeNamespaces) != 0) { - while (parent != null && parent.Kind() == SyntaxKind.NamespaceDeclaration) + while (parent is BaseNamespaceDeclarationSyntax) { names.Add(GetName(parent, options)); parent = parent.Parent; @@ -948,18 +948,16 @@ public bool IsClassDeclaration([NotNullWhen(true)] SyntaxNode? node) => node?.Kind() == SyntaxKind.ClassDeclaration; public bool IsNamespaceDeclaration([NotNullWhen(true)] SyntaxNode? node) - => node?.Kind() == SyntaxKind.NamespaceDeclaration; + => node is BaseNamespaceDeclarationSyntax; public SyntaxNode? GetNameOfNamespaceDeclaration(SyntaxNode? node) - => node is NamespaceDeclarationSyntax namespaceDeclaration - ? namespaceDeclaration.Name - : null; + => (node as BaseNamespaceDeclarationSyntax)?.Name; public SyntaxList GetMembersOfTypeDeclaration(SyntaxNode typeDeclaration) => ((TypeDeclarationSyntax)typeDeclaration).Members; public SyntaxList GetMembersOfNamespaceDeclaration(SyntaxNode namespaceDeclaration) - => ((NamespaceDeclarationSyntax)namespaceDeclaration).Members; + => ((BaseNamespaceDeclarationSyntax)namespaceDeclaration).Members; public SyntaxList GetMembersOfCompilationUnit(SyntaxNode compilationUnit) => ((CompilationUnitSyntax)compilationUnit).Members; @@ -1973,6 +1971,7 @@ public override DeclarationKind GetDeclarationKind(SyntaxNode declaration) case SyntaxKind.CompilationUnit: return DeclarationKind.CompilationUnit; case SyntaxKind.NamespaceDeclaration: + case SyntaxKind.FileScopedNamespaceDeclaration: return DeclarationKind.Namespace; case SyntaxKind.UsingDirective: return DeclarationKind.NamespaceImport; From 55227d800cc1718b4c055fc5cac731f5d02ce40f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 21 Jul 2021 12:01:30 -0700 Subject: [PATCH 2/4] Fix 'move type' with file scoped namespaces --- .../MoveType/MoveTypeTests.MoveToNewFile.cs | 25 +++++++++++++++++++ .../MoveType/CSharpMoveTypeService.cs | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs b/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs index c3a43618a02c9..433b73614d3df 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs @@ -320,6 +320,31 @@ class Class1 { } await TestMoveTypeToNewFileAsync(code, codeAfterMove, expectedDocumentName, destinationDocumentText); } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveType)] + public async Task MoveTypeWithWithFileScopedNamespace() + { + var code = +@"namespace N1; + +[||]class Class1 { } +class Class2 { } +"; + + var codeAfterMove = +@"namespace N1; +class Class2 { } +"; + + var expectedDocumentName = "Class1.cs"; + + var destinationDocumentText = +@"namespace N1; + +class Class1 { } +"; + await TestMoveTypeToNewFileAsync(code, codeAfterMove, expectedDocumentName, destinationDocumentText); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveType)] public async Task MoveNestedTypeToNewFile_Simple() { diff --git a/src/Features/CSharp/Portable/CodeRefactorings/MoveType/CSharpMoveTypeService.cs b/src/Features/CSharp/Portable/CodeRefactorings/MoveType/CSharpMoveTypeService.cs index 0265ec306557f..7717b4d20dd47 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/MoveType/CSharpMoveTypeService.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/MoveType/CSharpMoveTypeService.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.MoveType { [ExportLanguageService(typeof(IMoveTypeService), LanguageNames.CSharp), Shared] internal class CSharpMoveTypeService : - AbstractMoveTypeService + AbstractMoveTypeService { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] From 2a73ad77493db8f3adf5772e67ba8e04926eb1c5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 21 Jul 2021 12:12:24 -0700 Subject: [PATCH 3/4] Fix 'sync namespace' with file scoped namespaces --- .../SyncNamespaceTests_ChangeNamespace.cs | 31 ++++++++++ .../SyncNamespaceTests_MoveFile.cs | 61 +++++++++++++++++++ .../SyncNamespaceTests_NoAction.cs | 22 +++++++ ...arpSyncNamespaceCodeRefactoringProvider.cs | 11 +--- 4 files changed, 117 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_ChangeNamespace.cs b/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_ChangeNamespace.cs index 9846f97165c66..15e99dce66ef3 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_ChangeNamespace.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_ChangeNamespace.cs @@ -95,6 +95,37 @@ class Class1 await TestChangeNamespaceAsync(code, expectedSourceOriginal); } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)] + public async Task ChangeNamespace_SingleDocumentNoReference_FileScopedNamespace() + { + var defaultNamespace = "A"; + var declaredNamespace = "Foo.Bar"; + + var (folder, filePath) = CreateDocumentFilePath(new[] { "B", "C" }, "File1.cs"); + var code = +$@" + + + +namespace [||]{declaredNamespace}; + +class Class1 +{{ +}} + + +"; + + var expectedSourceOriginal = +@"namespace A.B.C; + +class Class1 +{ +} +"; + await TestChangeNamespaceAsync(code, expectedSourceOriginal); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)] public async Task ChangeNamespace_SingleDocumentLocalReference() { diff --git a/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_MoveFile.cs b/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_MoveFile.cs index 80aba8f0f3c28..542e910a631f2 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_MoveFile.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_MoveFile.cs @@ -42,6 +42,32 @@ class Class1 await TestMoveFileToMatchNamespace(code, expectedFolders); } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)] + public async Task MoveFile_DeclarationNotContainedInDefaultNamespace_FileScopedNamespace() + { + // No "move file" action because default namespace is not container of declared namespace + var defaultNamespace = "A"; + var declaredNamespace = "Foo.Bar"; + + var expectedFolders = new List(); + + var (folder, filePath) = CreateDocumentFilePath(Array.Empty(), "File1.cs"); + var code = +$@" + + + +namespace [||]{declaredNamespace}; + +class Class1 +{{ +}} + + +"; + await TestMoveFileToMatchNamespace(code, expectedFolders); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)] public async Task MoveFile_SingleAction1() { @@ -310,6 +336,41 @@ namespace Foo class Class2 {{ }} +}} + + +"; + await TestMoveFileToMatchNamespace(code, expectedFolders); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)] + public async Task MoveFile_FromOneFolderToAnother2_FileScopedNamespace() + { + var defaultNamespace = "A"; + var declaredNamespace = "A.B.C.D.E"; + + var expectedFolders = new List(); + expectedFolders.Add(new[] { "B", "C", "D", "E" }); + + var (folder, filePath) = CreateDocumentFilePath(new[] { "Foo.Bar", "Baz" }, "File1.cs"); // file1 is in \Foo.Bar\Baz\ + var documentPath2 = CreateDocumentFilePath(new[] { "B", "Foo" }, "File2.cs"); // file2 is in \B\Foo\ + + var code = +$@" + + + +namespace [||]{declaredNamespace}; + +class Class1 +{{ +}} + + +namespace Foo; + +class Class2 +{{ }} diff --git a/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_NoAction.cs b/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_NoAction.cs index 688b349b55a63..b5d0edac2ce3d 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_NoAction.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_NoAction.cs @@ -38,6 +38,28 @@ class [||]Class1 await TestMissingInRegularAndScriptAsync(code); } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)] + public async Task NoAction_NotOnNamespaceDeclaration_FileScopedNamespace() + { + var folders = new[] { "A", "B" }; + var (folder, filePath) = CreateDocumentFilePath(folders); + + var code = +$@" + + + +namespace NS; + +class [||]Class1 +{{ +}} + + +"; + + await TestMissingInRegularAndScriptAsync(code); + } [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)] public async Task NoAction_NotOnFirstMemberInGlobal() diff --git a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpSyncNamespaceCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpSyncNamespaceCodeRefactoringProvider.cs index 1290abd33da17..5b6fe2319f245 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpSyncNamespaceCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpSyncNamespaceCodeRefactoringProvider.cs @@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.SyncNamespace { [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.SyncNamespace), Shared] internal sealed class CSharpSyncNamespaceCodeRefactoringProvider - : AbstractSyncNamespaceCodeRefactoringProvider + : AbstractSyncNamespaceCodeRefactoringProvider { [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] @@ -31,24 +31,19 @@ public CSharpSyncNamespaceCodeRefactoringProvider() protected override async Task TryGetApplicableInvocationNodeAsync(Document document, TextSpan span, CancellationToken cancellationToken) { if (!span.IsEmpty) - { return null; - } var position = span.Start; var compilationUnit = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var namespaceDecls = compilationUnit.DescendantNodes(n => n is CompilationUnitSyntax || n is NamespaceDeclarationSyntax) - .OfType().ToImmutableArray(); + var namespaceDecls = compilationUnit.DescendantNodes(n => n is CompilationUnitSyntax || n is BaseNamespaceDeclarationSyntax) + .OfType().ToImmutableArray(); if (namespaceDecls.Length == 1 && compilationUnit.Members.Count == 1) { var namespaceDeclaration = namespaceDecls[0]; - if (namespaceDeclaration.Name.Span.IntersectsWith(position)) - { return namespaceDeclaration; - } } if (namespaceDecls.Length == 0) From 00b7a7a0b9a4e22e76958c270fd57b11bf66e421 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 21 Jul 2021 16:48:05 -0700 Subject: [PATCH 4/4] Fix test --- .../CSharpTest/Debugging/LocationInfoGetterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/Debugging/LocationInfoGetterTests.cs b/src/EditorFeatures/CSharpTest/Debugging/LocationInfoGetterTests.cs index dbb181a8ebce3..5a08718e34e9d 100644 --- a/src/EditorFeatures/CSharpTest/Debugging/LocationInfoGetterTests.cs +++ b/src/EditorFeatures/CSharpTest/Debugging/LocationInfoGetterTests.cs @@ -83,7 +83,7 @@ void Method() { }$$ } -", "Class.Method()", 2); +", "Namespace.Class.Method()", 2); } [Fact, Trait(Traits.Feature, Traits.Features.DebuggingLocationName)]