diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index a55f7f03b6f95..e003555af544b 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -4439,5 +4439,17 @@ await TestAsync( testHost, RecordStruct("R")); } + + [Theory] + [CombinatorialData] + public async Task BasicFileScopedNamespaceClassification(TestHost testHost) + { + await TestAsync( +@"namespace NS; + +class C { }", + testHost, + Namespace("NS")); + } } } diff --git a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs index 230bc23d1b54e..ff27fdbe06be6 100644 --- a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs @@ -2151,5 +2151,23 @@ void Goo(object o) Punctuation.CloseCurly, Punctuation.CloseCurly); } + + [Theory] + [CombinatorialData] + public async Task BasicFileScopedNamespaceClassification(TestHost testHost) + { + await TestAsync( +@"namespace NS; + +class C { }", + testHost, + Keyword("namespace"), + Namespace("NS"), + Punctuation.Semicolon, + Keyword("class"), + Class("C"), + Punctuation.OpenCurly, + Punctuation.CloseCurly); + } } } diff --git a/src/EditorFeatures/CSharpTest/Debugging/LocationInfoGetterTests.cs b/src/EditorFeatures/CSharpTest/Debugging/LocationInfoGetterTests.cs index d83091ddd94c6..dbb181a8ebce3 100644 --- a/src/EditorFeatures/CSharpTest/Debugging/LocationInfoGetterTests.cs +++ b/src/EditorFeatures/CSharpTest/Debugging/LocationInfoGetterTests.cs @@ -68,6 +68,24 @@ void Method() }", "Namespace.Class.Method()", 2); } + [Fact, Trait(Traits.Feature, Traits.Features.DebuggingLocationName)] + [WorkItem(49000, "https://github.com/dotnet/roslyn/issues/49000")] + public async Task TestFileScopedNamespace() + { + // This test behavior is incorrect. This should be Namespace.Class.Method. + // See the associated WorkItem for details. + await TestAsync( +@"namespace Namespace; + +class Class +{ + void Method() + { + }$$ +} +", "Class.Method()", 2); + } + [Fact, Trait(Traits.Feature, Traits.Features.DebuggingLocationName)] [WorkItem(527668, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/527668")] public async Task TestDottedNamespace() diff --git a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs index e0d74c1c3fd7c..bbeaec5418239 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs @@ -170,6 +170,11 @@ namespace NS code, indentationLine: 3, expectedIndentation: 4); + + AssertSmartIndent( + code, + indentationLine: 4, + expectedIndentation: 4); } [WpfFact] @@ -203,6 +208,32 @@ namespace NS expectedIndentation: 4); } + [WpfFact] + [Trait(Traits.Feature, Traits.Features.SmartIndent)] + public void FileScopedNamespace() + { + var code = @"using System; + +namespace NS; + + +"; + AssertSmartIndent( + code, + indentationLine: 1, + expectedIndentation: 0); + + AssertSmartIndent( + code, + indentationLine: 2, + expectedIndentation: 0); + + AssertSmartIndent( + code, + indentationLine: 4, + expectedIndentation: 0); + } + [WpfFact] [Trait(Traits.Feature, Traits.Features.SmartIndent)] public void Class() diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ClassKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ClassKeywordRecommenderTests.cs index c476df6205119..3bc8f4502dd40 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ClassKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ClassKeywordRecommenderTests.cs @@ -401,5 +401,12 @@ public async Task TestAfterRecord() await VerifyKeywordAsync( @"record $$"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterFileScopedNamespace() + { + await VerifyKeywordAsync( +@"namespace NS; $$"); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/NamespaceKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/NamespaceKeywordRecommenderTests.cs index 88f2441e98b7d..d05ad1b05aedc 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/NamespaceKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/NamespaceKeywordRecommenderTests.cs @@ -83,6 +83,34 @@ await VerifyKeywordAsync(SourceCodeKind.Regular, $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterPreviousFileScopedNamespace() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"namespace N; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterUsingInFileScopedNamespace() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"namespace N; +using U; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterUsingInNamespace() + { + await VerifyKeywordAsync(SourceCodeKind.Regular, +@"namespace N +{ + using U; + $$ +}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterPreviousNamespace_Interactive() { @@ -107,6 +135,26 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotAfterExternInFileScopedNamespace() + { + await VerifyAbsenceAsync(SourceCodeKind.Regular, +@"namespace N; +extern alias A; +$$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterExternInNamespace() + { + await VerifyKeywordAsync(SourceCodeKind.Regular, +@"namespace N +{ + extern alias A; + $$ +}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestAfterUsing() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs index ab43e259be52d..4ef315b6fa638 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs @@ -194,6 +194,14 @@ await VerifyKeywordAsync( $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterFileScopedNamespace() + { + await VerifyKeywordAsync( +@"namespace N; +$$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestNotAfterUsingKeyword_InsideNamespace() { @@ -416,6 +424,14 @@ namespace NS {}"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestBeforeFileScopedNamespace() + { + await VerifyKeywordAsync( +@"$$ +namespace NS;"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestBeforeClass() { diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NamespaceKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NamespaceKeywordRecommender.cs index 25636c745941b..cb1cc5ff656ca 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NamespaceKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/NamespaceKeywordRecommender.cs @@ -116,7 +116,8 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context // | if (token.Kind() == SyntaxKind.SemicolonToken) { - if (token.Parent.IsKind(SyntaxKind.ExternAliasDirective, SyntaxKind.UsingDirective)) + if (token.Parent.IsKind(SyntaxKind.ExternAliasDirective, SyntaxKind.UsingDirective) + && !token.Parent.Parent.IsKind(SyntaxKind.FileScopedNamespaceDeclaration)) { return true; } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/UsingKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/UsingKeywordRecommender.cs index b9adb86cabfcd..b8e23691c8eb3 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/UsingKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/UsingKeywordRecommender.cs @@ -79,7 +79,9 @@ internal static bool IsUsingDirectiveContext(CSharpSyntaxContext context, bool f // root: u| - // ns Goo { u| + // namespace N { u| + + // namespace N; u| // extern alias a; // u| @@ -98,8 +100,8 @@ internal static bool IsUsingDirectiveContext(CSharpSyntaxContext context, bool f return IsValidContextAtTheRoot(context, originalToken, cancellationToken); } - if (token.Kind() == SyntaxKind.OpenBraceToken && - token.Parent.IsKind(SyntaxKind.NamespaceDeclaration, out NamespaceDeclarationSyntax _)) + if ((token.Kind() == SyntaxKind.OpenBraceToken && token.Parent.IsKind(SyntaxKind.NamespaceDeclaration)) + || (token.Kind() == SyntaxKind.SemicolonToken && token.Parent.IsKind(SyntaxKind.FileScopedNamespaceDeclaration))) { // a child using can't come before externs var nextToken = originalToken.GetNextToken(includeSkipped: true); diff --git a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs index b7827c8bb2ee8..cc962a692f109 100644 --- a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs +++ b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs @@ -10078,5 +10078,21 @@ await AssertFormatAsync( object F = Func((A,B)((A,B)t)=>t); }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task FileScopedNamespace() + { + await AssertFormatAsync( + expected: @" +namespace NS; + +class C { } +", + code: @" +namespace NS; + + class C { } +"); + } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs index 853461ea064cf..ddce77cdad465 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs @@ -1046,7 +1046,7 @@ public static IEnumerable GetMembers(this SyntaxNode? n { case CompilationUnitSyntax compilation: return compilation.Members; - case NamespaceDeclarationSyntax @namespace: + case BaseNamespaceDeclarationSyntax @namespace: return @namespace.Members; case TypeDeclarationSyntax type: return type.Members; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 49c4a03e97194..4c026ee3c76cd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -773,7 +773,7 @@ node is EnumMemberDeclarationSyntax || public bool IsTopLevelNodeWithMembers([NotNullWhen(true)] SyntaxNode? node) { - return node is NamespaceDeclarationSyntax || + return node is BaseNamespaceDeclarationSyntax || node is TypeDeclarationSyntax || node is EnumDeclarationSyntax; } @@ -857,7 +857,8 @@ public string GetDisplayName(SyntaxNode? node, DisplayNameOptions options, strin case SyntaxKind.IncompleteMember: return missingTokenPlaceholder; case SyntaxKind.NamespaceDeclaration: - return GetName(((NamespaceDeclarationSyntax)node).Name, options); + case SyntaxKind.FileScopedNamespaceDeclaration: + return GetName(((BaseNamespaceDeclarationSyntax)node).Name, options); case SyntaxKind.QualifiedName: var qualified = (QualifiedNameSyntax)node; return GetName(qualified.Left, options) + dotToken + GetName(qualified.Right, options);