Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 'line separators' with file scoped namespaces #54966

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Collections.Generic;
using System.Composition;
Expand Down Expand Up @@ -37,7 +35,7 @@ public async Task<IEnumerable<TextSpan>> GetLineSeparatorsAsync(
TextSpan textSpan,
CancellationToken cancellationToken)
{
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var node = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);
var spans = new List<TextSpan>();

Expand All @@ -46,16 +44,14 @@ public async Task<IEnumerable<TextSpan>> GetLineSeparatorsAsync(
foreach (var block in blocks)
{
if (cancellationToken.IsCancellationRequested)
{
return SpecializedCollections.EmptyEnumerable<TextSpan>();
}

switch (block)
{
case TypeDeclarationSyntax typeBlock:
ProcessNodeList(typeBlock.Members, spans, cancellationToken);
continue;
case NamespaceDeclarationSyntax namespaceBlock:
case BaseNamespaceDeclarationSyntax namespaceBlock:
ProcessUsings(namespaceBlock.Usings, spans, cancellationToken);
ProcessNodeList(namespaceBlock.Members, spans, cancellationToken);
continue;
Expand Down Expand Up @@ -97,11 +93,7 @@ private static bool IsSeparableBlock(SyntaxNode node)

/// <summary>Node types that may contain separable blocks.</summary>
private static bool IsSeparableContainer(SyntaxNode node)
{
return node is TypeDeclarationSyntax ||
node is NamespaceDeclarationSyntax ||
node is CompilationUnitSyntax;
}
=> node is TypeDeclarationSyntax or BaseNamespaceDeclarationSyntax or CompilationUnitSyntax;

private static bool IsBadType(SyntaxNode node)
{
Expand Down Expand Up @@ -155,12 +147,10 @@ private static bool IsBadEvent(SyntaxNode node)
private static bool IsBadIndexer(SyntaxNode node)
=> IsBadAccessorList(node as IndexerDeclarationSyntax);

private static bool IsBadAccessorList(BasePropertyDeclarationSyntax baseProperty)
private static bool IsBadAccessorList(BasePropertyDeclarationSyntax? baseProperty)
{
if (baseProperty?.AccessorList == null)
{
return false;
}

return baseProperty.AccessorList.OpenBraceToken.IsMissing ||
baseProperty.AccessorList.CloseBraceToken.IsMissing;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,22 @@ public async Task NamespaceDeclaration_Qualified()
await VerifyBuilderAsync(markup);
}

[WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task FileScopedNamespaceDeclaration_Unqualified()
{
var markup = @"namespace $$;";
await VerifyBuilderAsync(markup);
}

[WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task FileScopedNamespaceDeclaration_Qualified()
{
var markup = @"namespace A.$$;";
await VerifyBuilderAsync(markup);
}

[WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task PartialClassName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,60 @@ void Method()
}
}

internal class NewClass
{
public int A { get; }
public int B { get; }

public NewClass(int a, int b)
{
A = a;
B = b;
}

public override bool Equals(object obj)
{
return obj is NewClass other &&
A == other.A &&
B == other.B;
}

public override int GetHashCode()
{
var hashCode = -1817952719;
hashCode = hashCode * -1521134295 + A.GetHashCode();
hashCode = hashCode * -1521134295 + B.GetHashCode();
return hashCode;
}
}";
await TestInRegularAndScriptAsync(text, expected, options: this.PreferImplicitTypeWithInfo(), parseOptions: CSharp8);
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToClass)]
public async Task ConvertSingleAnonymousType_FileScopedNamespace()
{
var text = @"
namespace N;

class Test
{
void Method()
{
var t1 = [||]new { a = 1, b = 2 };
}
}
";
var expected = @"
namespace N;

class Test
{
void Method()
{
var t1 = new {|Rename:NewClass|}(1, 2);
}
}

internal class NewClass
{
public int A { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,46 @@ void Method()
}
}

internal record struct NewStruct(int a, int b)
{
public static implicit operator (int a, int b)(NewStruct value)
{
return (value.a, value.b);
}

public static implicit operator NewStruct((int a, int b) value)
{
return new NewStruct(value.a, value.b);
}
}";
await TestAsync(text, expected, languageVersion: LanguageVersion.Preview, options: PreferImplicitTypeWithInfo(), testHost: host);
}

[Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)]
public async Task ConvertSingleTupleTypeToRecord_FileScopedNamespace(TestHost host)
{
var text = @"
namespace N;

class Test
{
void Method()
{
var t1 = [||](a: 1, b: 2);
}
}
";
var expected = @"
namespace N;

class Test
{
void Method()
{
var t1 = new NewStruct(a: 1, b: 2);
}
}

internal record struct NewStruct(int a, int b)
{
public static implicit operator (int a, int b)(NewStruct value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Editor.CSharp.LineSeparator;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
Expand Down Expand Up @@ -282,6 +281,20 @@ class C
await AssertTagsOnBracesOrSemicolonsAsync(file, 0, 2);
}

[Fact, Trait(Traits.Feature, Traits.Features.LineSeparators)]
public async Task UsingDirectiveInFileScopedNamespace()
{
var file = @"namespace N;

using System;

class C
{
}
";
await AssertTagsOnBracesOrSemicolonsAsync(file, 1);
}

[Fact, Trait(Traits.Feature, Traits.Features.LineSeparators)]
public async Task PropertyStyleEventDeclaration()
{
Expand Down Expand Up @@ -521,13 +534,15 @@ private static async Task AssertTagsOnBracesOrSemicolonsAsync(string contents, p
await AssertTagsOnBracesOrSemicolonsTokensAsync(contents, tokenIndices, Options.Script);
}

private static async Task AssertTagsOnBracesOrSemicolonsTokensAsync(string contents, int[] tokenIndices, CSharpParseOptions options = null)
private static async Task AssertTagsOnBracesOrSemicolonsTokensAsync(string contents, int[] tokenIndices, CSharpParseOptions? options = null)
{
using var workspace = TestWorkspace.CreateCSharp(contents, options);
var document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id);
var lineSeparatorService = Assert.IsType<CSharpLineSeparatorService>(workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetService<ILineSeparatorService>());
var spans = await lineSeparatorService.GetLineSeparatorsAsync(document, (await document.GetSyntaxRootAsync()).FullSpan, CancellationToken.None);
var tokens = (await document.GetSyntaxRootAsync(CancellationToken.None)).DescendantTokens().Where(t => t.Kind() == SyntaxKind.CloseBraceToken || t.Kind() == SyntaxKind.SemicolonToken);
var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id);
var root = await document.GetRequiredSyntaxRootAsync(default);

var lineSeparatorService = Assert.IsType<CSharpLineSeparatorService>(workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService<ILineSeparatorService>());
var spans = await lineSeparatorService.GetLineSeparatorsAsync(document, root.FullSpan, CancellationToken.None);
var tokens = root.DescendantTokens().Where(t => t.Kind() is SyntaxKind.CloseBraceToken or SyntaxKind.SemicolonToken);

Assert.Equal(tokenIndices.Length, spans.Count());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,30 @@ class A
}");
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSimplifyTypeNames)]
public async Task UseAlias00_FileScopedNamespace()
{
await TestInRegularAndScriptAsync(
@"namespace Root;
using MyType = System.IO.File;
class A
{
[|System.IO.File|] c;
}
",
@"namespace Root;
using MyType = System.IO.File;
class A
{
MyType c;
}
");
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSimplifyTypeNames)]
public async Task UseAlias()
{
Expand Down Expand Up @@ -754,6 +778,40 @@ class A
}");
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSimplifyTypeNames)]
public async Task SimplifyTypeName1_FileScopedNamespace()
{
var source =
@"using System;
namespace Root;
class A
{
[|System.Exception|] c;
}";

await TestInRegularAndScriptAsync(source,
@"using System;
namespace Root;
class A
{
Exception c;
}");
await TestActionCountAsync(source, 1);
await TestSpansAsync(
@"using System;
namespace Root;
class A
{
[|System|].Exception c;
}");
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSimplifyTypeNames)]
public async Task SimplifyTypeName2()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ await VerifyKeywordAsync(
namespace Goo {");
}

[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem(362, "https://github.com/dotnet/roslyn/issues/362")]
public async Task TestInAttributeBeforeFileScopedNamespace()
{
await VerifyKeywordAsync(
@"[$$
namespace Goo;");
}

[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem(362, "https://github.com/dotnet/roslyn/issues/362")]
public async Task TestNotInAttributeBeforeNamespaceWithoutOpenBracket()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
Expand Down Expand Up @@ -202,6 +200,13 @@ await VerifyKeywordAsync(
$$");
}

[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestInsideFileScopedNamespace()
{
await VerifyKeywordAsync(
@"namespace N;$$");
}

[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestNotAfterExternKeyword_InsideNamespace()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders
{
Expand All @@ -22,14 +21,14 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context
var token = context.TargetToken;

if (token.Kind() == SyntaxKind.OpenBracketToken &&
token.Parent.Kind() == SyntaxKind.AttributeList)
token.GetRequiredParent().Kind() == SyntaxKind.AttributeList)
{
var attributeList = token.Parent;
var attributeList = token.GetRequiredParent();
var parentSyntax = attributeList.Parent;
switch (parentSyntax)
{
case CompilationUnitSyntax _:
case NamespaceDeclarationSyntax _:
case CompilationUnitSyntax:
case BaseNamespaceDeclarationSyntax:
// The case where the parent of attributeList is (Class/Interface/Enum/Struct)DeclarationSyntax, like:
// [$$
// class Goo {
Expand Down
Loading