Skip to content
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 @@ -1532,4 +1532,34 @@ struct MyType2
{
{"A.MyType", "B.MyType" }
});

[Theory]
[InlineData("class MyClass[||](int x, int y)")]
[InlineData("class [||]MyClass(int x, int y)")]
[InlineData("class MyC[||]lass(int x, int y)")]
public Task MoveToNamespace_PrimaryConstructor(string decl)
=> TestMoveToNamespaceAsync(
$$"""
namespace A;

{{decl}}
{
public int X => x;
public int Y => y;
}
""",
expectedMarkup: """
namespace {|Warning:B|};

class MyClass(int x, int y)
{
public int X => x;
public int Y => y;
}
""",
targetNamespace: "B",
expectedSymbolChanges: new Dictionary<string, string>()
{
{"A.MyClass", "B.MyClass" }
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ internal class CSharpMoveToNamespaceService(
[Import(AllowDefault = true)] IMoveToNamespaceOptionsService optionsService) :
AbstractMoveToNamespaceService<CompilationUnitSyntax, BaseNamespaceDeclarationSyntax, BaseTypeDeclarationSyntax>(optionsService)
{
protected override BaseTypeDeclarationSyntax? GetNamedTypeDeclarationSyntax(SyntaxNode node)
=> node switch
{
BaseTypeDeclarationSyntax namedTypeDeclaration => namedTypeDeclaration,
ParameterListSyntax parameterList => parameterList.Parent as BaseTypeDeclarationSyntax,
_ => null
};

protected override string GetNamespaceName(SyntaxNode container)
=> container switch
{
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;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand Down Expand Up @@ -40,6 +38,7 @@ internal abstract class AbstractMoveToNamespaceService<TCompilationUnitSyntax, T
{
protected abstract string GetNamespaceName(SyntaxNode namespaceSyntax);
protected abstract bool IsContainedInNamespaceDeclaration(TNamespaceDeclarationSyntax namespaceSyntax, int position);
protected abstract TNamedTypeDeclarationSyntax? GetNamedTypeDeclarationSyntax(SyntaxNode node);

public IMoveToNamespaceOptionsService OptionsService { get; } = moveToNamespaceOptionsService;

Expand All @@ -66,10 +65,14 @@ public async Task<MoveToNamespaceAnalysisResult> AnalyzeTypeAtPositionAsync(
int position,
CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

var token = root.FindToken(position);
var node = token.Parent;
if (node is null)
{
return MoveToNamespaceAnalysisResult.Invalid;
}

var moveToNamespaceAnalysisResult = await TryAnalyzeNamespaceAsync(document, node, position, cancellationToken).ConfigureAwait(false);

Expand All @@ -81,8 +84,7 @@ public async Task<MoveToNamespaceAnalysisResult> AnalyzeTypeAtPositionAsync(
moveToNamespaceAnalysisResult = await TryAnalyzeNamedTypeAsync(document, node, cancellationToken).ConfigureAwait(false);
return moveToNamespaceAnalysisResult ?? MoveToNamespaceAnalysisResult.Invalid;
}

private async Task<MoveToNamespaceAnalysisResult> TryAnalyzeNamespaceAsync(
private async Task<MoveToNamespaceAnalysisResult?> TryAnalyzeNamespaceAsync(
Document document, SyntaxNode node, int position, CancellationToken cancellationToken)
{
var declarationSyntax = node.FirstAncestorOrSelf<TNamespaceDeclarationSyntax>();
Expand All @@ -94,7 +96,7 @@ private async Task<MoveToNamespaceAnalysisResult> TryAnalyzeNamespaceAsync(
// The underlying ChangeNamespace service doesn't support nested namespace declaration.
if (GetNamespaceInSpineCount(declarationSyntax) == 1)
{
var changeNamespaceService = document.GetLanguageService<IChangeNamespaceService>();
var changeNamespaceService = document.GetRequiredLanguageService<IChangeNamespaceService>();
if (await changeNamespaceService.CanChangeNamespaceAsync(document, declarationSyntax, cancellationToken).ConfigureAwait(false))
{
var namespaceName = GetNamespaceName(declarationSyntax);
Expand All @@ -118,38 +120,45 @@ private async Task<MoveToNamespaceAnalysisResult> TryAnalyzeNamedTypeAsync(
return MoveToNamespaceAnalysisResult.Invalid;
}

SyntaxNode container = null;
SyntaxNode? container = null;

// Moving one of the many members declared in global namespace is not currently supported,
// but if it's the only member declared, then that's fine.
if (namespaceInSpineCount == 0)
{
container = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
container = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();

if (syntaxFacts.GetMembersOfCompilationUnit(container).Count > 1)
{
return MoveToNamespaceAnalysisResult.Invalid;
}
}

if (node is TNamedTypeDeclarationSyntax namedTypeDeclarationSyntax)
var namedTypeDeclarationSyntax = GetNamedTypeDeclarationSyntax(node);
if (namedTypeDeclarationSyntax is null)
{
// If we are inside a namespace declaration, then find it as the container.
container ??= GetContainingNamespace(namedTypeDeclarationSyntax);
var changeNamespaceService = document.GetLanguageService<IChangeNamespaceService>();
return MoveToNamespaceAnalysisResult.Invalid;
}

if (await changeNamespaceService.CanChangeNamespaceAsync(document, container, cancellationToken).ConfigureAwait(false))
{
var namespaces = await GetNamespacesAsync(document, cancellationToken).ConfigureAwait(false);
return new MoveToNamespaceAnalysisResult(document, namedTypeDeclarationSyntax, GetNamespaceName(container), [.. namespaces], MoveToNamespaceAnalysisResult.ContainerType.NamedType);
}
// If we are inside a namespace declaration, then find it as the container.
container ??= GetContainingNamespace(namedTypeDeclarationSyntax);
if (container is null)
{
return MoveToNamespaceAnalysisResult.Invalid;
}

var changeNamespaceService = document.GetRequiredLanguageService<IChangeNamespaceService>();
if (await changeNamespaceService.CanChangeNamespaceAsync(document, container, cancellationToken).ConfigureAwait(false))
{
var namespaces = await GetNamespacesAsync(document, cancellationToken).ConfigureAwait(false);
return new MoveToNamespaceAnalysisResult(document, namedTypeDeclarationSyntax, GetNamespaceName(container), [.. namespaces], MoveToNamespaceAnalysisResult.ContainerType.NamedType);
}

return MoveToNamespaceAnalysisResult.Invalid;
}

private static TNamespaceDeclarationSyntax GetContainingNamespace(TNamedTypeDeclarationSyntax namedTypeSyntax)
private static TNamespaceDeclarationSyntax? GetContainingNamespace(TNamedTypeDeclarationSyntax namedTypeSyntax)
=> namedTypeSyntax.FirstAncestorOrSelf<TNamespaceDeclarationSyntax>();

private static int GetNamespaceInSpineCount(SyntaxNode node)
Expand All @@ -176,20 +185,26 @@ public Task<MoveToNamespaceResult> MoveToNamespaceAsync(

private static async Task<ImmutableArray<ISymbol>> GetMemberSymbolsAsync(Document document, SyntaxNode container, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
switch (container)
{
case TNamespaceDeclarationSyntax namespaceNode:
var namespaceMembers = syntaxFacts.GetMembersOfBaseNamespaceDeclaration(namespaceNode);
return namespaceMembers.SelectAsArray(member => semanticModel.GetDeclaredSymbol(member, cancellationToken));
return namespaceMembers
.Select(member => semanticModel.GetDeclaredSymbol(member, cancellationToken))
.WhereNotNull()
.ToImmutableArray();
case TCompilationUnitSyntax compilationUnit:
var compilationUnitMembers = syntaxFacts.GetMembersOfCompilationUnit(compilationUnit);
// We are trying to move a selected type from global namespace to the target namespace.
// This is supported if the selected type is the only member declared in the global namespace in this document.
// (See `TryAnalyzeNamedTypeAsync`)
Debug.Assert(compilationUnitMembers.Count == 1);
return compilationUnitMembers.SelectAsArray(member => semanticModel.GetDeclaredSymbol(member, cancellationToken));
return compilationUnitMembers
.Select(member => semanticModel.GetDeclaredSymbol(member, cancellationToken))
.WhereNotNull()
.ToImmutableArray();

default:
throw ExceptionUtilities.UnexpectedValue(container);
Expand Down Expand Up @@ -236,15 +251,15 @@ private static async Task<MoveToNamespaceResult> MoveTypeToNamespaceAsync(
moveSpan,
MoveTypeOperationKind.MoveTypeNamespaceScope,
cancellationToken).ConfigureAwait(false);
var modifiedDocument = modifiedSolution.GetDocument(document.Id);
var modifiedDocument = modifiedSolution.GetRequiredDocument(document.Id);

// Since MoveTypeService doesn't handle linked files, we need to merge the diff ourselves,
// otherwise, we will end up with multiple linked documents with different content.
var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(cancellationToken).ConfigureAwait(false);
var mergedSolution = await PropagateChangeToLinkedDocumentsAsync(modifiedDocument, formattingOptions, cancellationToken).ConfigureAwait(false);
var mergedDocument = mergedSolution.GetDocument(document.Id);
var mergedDocument = mergedSolution.GetRequiredDocument(document.Id);

var syntaxRoot = await mergedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var syntaxRoot = await mergedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var syntaxNode = syntaxRoot.GetAnnotatedNodes(AbstractMoveTypeService.NamespaceScopeMovedAnnotation).SingleOrDefault();

// The type might be declared in global namespace
Expand Down Expand Up @@ -289,7 +304,7 @@ protected static string GetQualifiedName(INamespaceSymbol namespaceSymbol)

private static async Task<IEnumerable<string>> GetNamespacesAsync(Document document, CancellationToken cancellationToken)
{
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);

return compilation.GlobalNamespace.GetAllNamespaces(cancellationToken)
.Where(n => n.NamespaceKind == NamespaceKind.Module && n.ContainingAssembly == compilation.Assembly)
Expand Down
Loading