Skip to content

Commit

Permalink
Merge pull request #60492 from genlu/NRTSyncNamespace
Browse files Browse the repository at this point in the history
Enable NRT in AbstractSyncNamespaceCodeRefactoringProvider
  • Loading branch information
genlu committed Apr 1, 2022
2 parents 6888cd6 + 84c0f52 commit 65942dc
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 35 deletions.
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.Collections.Immutable;
using System.Composition;
using System.Diagnostics.CodeAnalysis;
Expand All @@ -28,14 +26,15 @@ public CSharpSyncNamespaceCodeRefactoringProvider()
{
}

protected override async Task<SyntaxNode> TryGetApplicableInvocationNodeAsync(Document document, TextSpan span, CancellationToken cancellationToken)
protected override async Task<SyntaxNode?> TryGetApplicableInvocationNodeAsync(Document document, TextSpan span, CancellationToken cancellationToken)
{
if (!span.IsEmpty)
return null;

var position = span.Start;
if (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) is not CompilationUnitSyntax compilationUnit)
return null;

var compilationUnit = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var position = span.Start;
var namespaceDecls = compilationUnit.DescendantNodes(n => n is CompilationUnitSyntax or BaseNamespaceDeclarationSyntax)
.OfType<BaseNamespaceDeclarationSyntax>().ToImmutableArray();

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 All @@ -14,6 +12,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CodeRefactorings.SyncNamespace
Expand Down Expand Up @@ -42,12 +41,11 @@ public MoveFileCodeAction(State state, ImmutableArray<string> newFolders)

protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(CancellationToken cancellationToken)
{
var id = _state.Document.Id;
var document = _state.Document;
var solution = _state.Document.Project.Solution;
var document = solution.GetDocument(id);
var newDocumentId = DocumentId.CreateNewId(document.Project.Id, document.Name);

solution = solution.RemoveDocument(id);
solution = solution.RemoveDocument(document.Id);

var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
solution = solution.AddDocument(newDocumentId, document.Name, text, folders: _newfolders);
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.Collections.Generic;
using System.Diagnostics;
using System.IO;
Expand Down Expand Up @@ -43,29 +41,29 @@ internal sealed class State
/// This is the new name we want to change the namespace to.
/// Empty string means global namespace, whereas null means change namespace action is not available.
/// </summary>
public string TargetNamespace { get; }
public string? TargetNamespace { get; }

/// <summary>
/// This is the part of the declared namespace that is contained in default namespace.
/// We will use this to construct target folder to move the file to.
/// For example, if default namespace is `A` and declared namespace is `A.B.C`,
/// this would be `B.C`.
/// </summary>
public string RelativeDeclaredNamespace { get; }
public string? RelativeDeclaredNamespace { get; }

private State(
Document document,
SyntaxNode container,
string targetNamespace,
string relativeDeclaredNamespace)
string? targetNamespace,
string? relativeDeclaredNamespace)
{
Document = document;
Container = container;
TargetNamespace = targetNamespace;
RelativeDeclaredNamespace = relativeDeclaredNamespace;
}

public static async Task<State> CreateAsync(
public static async Task<State?> CreateAsync(
AbstractSyncNamespaceCodeRefactoringProvider<TNamespaceDeclarationSyntax, TCompilationUnitSyntax, TMemberDeclarationSyntax> provider,
Document document,
TextSpan textSpan,
Expand Down Expand Up @@ -93,15 +91,15 @@ public static async Task<State> CreateAsync(
return null;
}

var changeNamespaceService = document.GetLanguageService<IChangeNamespaceService>();
var changeNamespaceService = document.GetRequiredLanguageService<IChangeNamespaceService>();
var canChange = await changeNamespaceService.CanChangeNamespaceAsync(document, applicableNode, cancellationToken).ConfigureAwait(false);

if (!canChange || !IsDocumentPathRootedInProjectFolder(document))
{
return null;
}

var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();

// We can't determine what the expected namespace would be without knowing the default namespace.
var defaultNamespace = GetDefaultNamespace(document, syntaxFacts);
Expand Down Expand Up @@ -152,36 +150,40 @@ public static async Task<State> CreateAsync(
/// </summary>
private static bool IsDocumentPathRootedInProjectFolder(Document document)
{
var absoluteDircetoryPath = PathUtilities.GetDirectoryName(document.FilePath);
if (absoluteDircetoryPath is null)
return false;

var projectRoot = PathUtilities.GetDirectoryName(document.Project.FilePath);
var folderPath = Path.Combine(document.Folders.ToArray());
if (projectRoot is null)
return false;

var absoluteDircetoryPath = PathUtilities.GetDirectoryName(document.FilePath);
var folderPath = Path.Combine(document.Folders.ToArray());
var logicalDirectoryPath = PathUtilities.CombineAbsoluteAndRelativePaths(projectRoot, folderPath);
if (logicalDirectoryPath is null)
return false;

return PathUtilities.PathsEqual(absoluteDircetoryPath, logicalDirectoryPath);
}

private static string GetDefaultNamespace(Document document, ISyntaxFactsService syntaxFacts)
private static string? GetDefaultNamespace(Document document, ISyntaxFactsService syntaxFacts)
{
var solution = document.Project.Solution;
var linkedIds = document.GetLinkedDocumentIds();
var documents = linkedIds.SelectAsArray(id => solution.GetDocument(id)).Add(document);
var documents = linkedIds.SelectAsArray(id => solution.GetRequiredDocument(id)).Add(document);

// For all projects containing all the linked documents, bail if
// 1. Any of them doesn't have default namespace, or
// 2. Multiple default namespace are found. (this might be possible by tweaking project file).
// The refactoring depends on a single default namespace to operate.
var defaultNamespaceFromProjects = new HashSet<string>(
var defaultNamespaceFromProjects = new HashSet<string?>(
documents.Select(d => d.Project.DefaultNamespace),
syntaxFacts.StringComparer);

if (defaultNamespaceFromProjects.Count != 1
|| defaultNamespaceFromProjects.First() == null)
{
if (defaultNamespaceFromProjects.Count > 1)
return null;
}

return defaultNamespaceFromProjects.Single();
return defaultNamespaceFromProjects.SingleOrDefault();
}

/// <summary>
Expand All @@ -195,7 +197,7 @@ private static string GetDefaultNamespace(Document document, ISyntaxFactsService
/// the relative namespace is "".
/// - If <paramref name="relativeTo"/> is "" then the relative namespace us <paramref name="namespace"/>.
/// </summary>
private static string GetRelativeNamespace(string relativeTo, string @namespace, ISyntaxFactsService syntaxFacts)
private static string? GetRelativeNamespace(string relativeTo, string @namespace, ISyntaxFactsService syntaxFacts)
{
Debug.Assert(relativeTo != null && @namespace != null);

Expand All @@ -213,7 +215,7 @@ private static string GetRelativeNamespace(string relativeTo, string @namespace,
}

var containingText = relativeTo + ".";
var namespacePrefix = @namespace.Substring(0, containingText.Length);
var namespacePrefix = @namespace[..containingText.Length];

return syntaxFacts.StringComparer.Equals(containingText, namespacePrefix)
? @namespace[(relativeTo.Length + 1)..]
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.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -69,7 +67,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
// is global namespace, i.e. default namespace is "" and the file is located at project
// root directory, and no namespace declaration in the document, respectively.

var service = document.GetLanguageService<IChangeNamespaceService>();
var service = document.GetRequiredLanguageService<IChangeNamespaceService>();

var solutionChangeAction = new ChangeNamespaceCodeAction(
state.TargetNamespace.Length == 0
Expand All @@ -91,7 +89,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
/// declaration in global namespace and there's no namespace declaration in this document.
/// (3) otherwise, null.
/// </returns>
protected abstract Task<SyntaxNode> TryGetApplicableInvocationNodeAsync(Document document, TextSpan span, CancellationToken cancellationToken);
protected abstract Task<SyntaxNode?> TryGetApplicableInvocationNodeAsync(Document document, TextSpan span, CancellationToken cancellationToken);

protected abstract string EscapeIdentifier(string identifier);

Expand Down

0 comments on commit 65942dc

Please sign in to comment.