diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.Editor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.Editor.cs index bbdd9444b28fa..35aede8ef24c6 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.Editor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.Editor.cs @@ -2,13 +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.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; @@ -19,15 +16,16 @@ internal abstract partial class AbstractMoveTypeService private abstract class Editor( TService service, - State state, + SemanticDocument semanticDocument, + TTypeDeclarationSyntax typeDeclaration, string fileName, CancellationToken cancellationToken) { - protected State State { get; } = state; protected TService Service { get; } = service; + protected SemanticDocument SemanticDocument { get; } = semanticDocument; + protected TTypeDeclarationSyntax TypeDeclaration { get; } = typeDeclaration; protected string FileName { get; } = fileName; protected CancellationToken CancellationToken { get; } = cancellationToken; - protected SemanticDocument SemanticDocument => State.SemanticDocument; /// /// Operations performed by CodeAction. @@ -35,27 +33,21 @@ private abstract class Editor( public virtual async Task> GetOperationsAsync() { var solution = await GetModifiedSolutionAsync().ConfigureAwait(false); - - if (solution == null) - { - return []; - } - - return [new ApplyChangesOperation(solution)]; + return solution == null ? [] : [new ApplyChangesOperation(solution)]; } /// /// Incremental solution edits that correlate to code operations /// - public abstract Task GetModifiedSolutionAsync(); + public abstract Task GetModifiedSolutionAsync(); - public static Editor GetEditor(MoveTypeOperationKind operationKind, TService service, State state, string fileName, CancellationToken cancellationToken) + public static Editor GetEditor(MoveTypeOperationKind operationKind, TService service, SemanticDocument document, TTypeDeclarationSyntax typeDeclaration, string fileName, CancellationToken cancellationToken) => operationKind switch { - MoveTypeOperationKind.MoveType => new MoveTypeEditor(service, state, fileName, cancellationToken), - MoveTypeOperationKind.RenameType => new RenameTypeEditor(service, state, fileName, cancellationToken), - MoveTypeOperationKind.RenameFile => new RenameFileEditor(service, state, fileName, cancellationToken), - MoveTypeOperationKind.MoveTypeNamespaceScope => new MoveTypeNamespaceScopeEditor(service, state, fileName, cancellationToken), + MoveTypeOperationKind.MoveType => new MoveTypeEditor(service, document, typeDeclaration, fileName, cancellationToken), + MoveTypeOperationKind.RenameType => new RenameTypeEditor(service, document, typeDeclaration, fileName, cancellationToken), + MoveTypeOperationKind.RenameFile => new RenameFileEditor(service, document, typeDeclaration, fileName, cancellationToken), + MoveTypeOperationKind.MoveTypeNamespaceScope => new MoveTypeNamespaceScopeEditor(service, document, typeDeclaration, fileName, cancellationToken), _ => throw ExceptionUtilities.UnexpectedValue(operationKind), }; } diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeCodeAction.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeCodeAction.cs index 768660883752b..c279936be45a9 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeCodeAction.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeCodeAction.cs @@ -2,14 +2,11 @@ // 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.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; @@ -17,19 +14,22 @@ internal abstract partial class AbstractMoveTypeService _operationKind switch { MoveTypeOperationKind.MoveType => string.Format(FeaturesResources.Move_type_to_0, _fileName), - MoveTypeOperationKind.RenameType => string.Format(FeaturesResources.Rename_type_to_0, _state.DocumentNameWithoutExtension), + MoveTypeOperationKind.RenameType => string.Format(FeaturesResources.Rename_type_to_0, GetDocumentNameWithoutExtension(_document)), MoveTypeOperationKind.RenameFile => string.Format(FeaturesResources.Rename_file_to_0, _fileName), MoveTypeOperationKind.MoveTypeNamespaceScope => string.Empty, _ => throw ExceptionUtilities.UnexpectedValue(_operationKind), @@ -50,7 +50,7 @@ private string CreateDisplayText() protected override async Task> ComputeOperationsAsync( IProgress progress, CancellationToken cancellationToken) { - var editor = Editor.GetEditor(_operationKind, _service, _state, _fileName, cancellationToken); + var editor = Editor.GetEditor(_operationKind, _service, _document, _typeDeclaration, _fileName, cancellationToken); return await editor.GetOperationsAsync().ConfigureAwait(false); } } diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs index f904672a73af1..e4fc8389b3993 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs @@ -25,9 +25,10 @@ internal abstract partial class AbstractMoveTypeService /// Given a document and a type contained in it, moves the type @@ -42,10 +43,10 @@ private sealed class MoveTypeEditor( /// 3. Add this forked document to the solution. /// 4. Finally, update the original document and remove the type from it. /// - public override async Task GetModifiedSolutionAsync() + public override async Task GetModifiedSolutionAsync() { // Fork, update and add as new document. - var projectToBeUpdated = SemanticDocument.Document.Project; + var projectToBeUpdated = SemanticDocument.Project; var newDocumentId = DocumentId.CreateNewId(projectToBeUpdated.Id, FileName); // We do this process in the following steps: @@ -142,7 +143,7 @@ private async Task AddNewDocumentWithSingleTypeDeclarationAsync(Docume documentEditor.RemoveAllAttributes(root); // Now remove any leading directives on the type-node that actually correspond to prior nodes we removed. - var leadingTrivia = State.TypeNode.GetLeadingTrivia().ToSet(); + var leadingTrivia = this.TypeDeclaration.GetLeadingTrivia().ToSet(); foreach (var directive in correspondingDirectives) { if (leadingTrivia.Contains(directive.ParentTrivia)) @@ -185,7 +186,7 @@ void AddCorrespondingDirectives(SyntaxNode member, HashSet directive private void RemoveLeadingBlankLinesFromMovedType(DocumentEditor documentEditor) { - documentEditor.ReplaceNode(State.TypeNode, + documentEditor.ReplaceNode(this.TypeDeclaration, (currentNode, generator) => { var currentTypeNode = (TTypeDeclarationSyntax)currentNode; @@ -240,7 +241,7 @@ private async Task RemoveTypeFromSourceDocumentAsync(Document sourceDo // Now cleanup and remove the type we're moving to the new file. RemoveLeadingBlankLinesFromMovedType(documentEditor); - documentEditor.RemoveNode(State.TypeNode, SyntaxRemoveOptions.KeepUnbalancedDirectives); + documentEditor.RemoveNode(this.TypeDeclaration, SyntaxRemoveOptions.KeepUnbalancedDirectives); var updatedDocument = documentEditor.GetChangedDocument(); updatedDocument = await AddFileBannerHelpers.CopyBannerAsync(updatedDocument, sourceDocument.FilePath, sourceDocument, this.CancellationToken).ConfigureAwait(false); @@ -260,12 +261,12 @@ private ISet GetMembersToRemove(SyntaxNode root) var spine = new HashSet(); // collect the parent chain of declarations to keep. - spine.AddRange(State.TypeNode.GetAncestors()); + spine.AddRange(this.TypeDeclaration.GetAncestors()); // get potential namespace, types and members to remove. var removableCandidates = root .DescendantNodes(spine.Contains) - .Where(n => FilterToTopLevelMembers(n, State.TypeNode)).ToSet(); + .Where(n => FilterToTopLevelMembers(n, this.TypeDeclaration)).ToSet(); // diff candidates with items we want to keep. removableCandidates.ExceptWith(spine); @@ -304,12 +305,12 @@ private void AddPartialModifiersToTypeChain( bool removeTypeInheritance, bool removePrimaryConstructor) { - var semanticFacts = State.SemanticDocument.Document.GetRequiredLanguageService(); - var typeChain = State.TypeNode.Ancestors().OfType(); + var semanticFacts = SemanticDocument.GetRequiredLanguageService(); + var typeChain = this.TypeDeclaration.Ancestors().OfType(); foreach (var node in typeChain) { - var symbol = (INamedTypeSymbol?)State.SemanticDocument.SemanticModel.GetDeclaredSymbol(node, CancellationToken); + var symbol = (INamedTypeSymbol)SemanticDocument.SemanticModel.GetRequiredDeclaredSymbol(node, CancellationToken); Contract.ThrowIfNull(symbol); if (!semanticFacts.IsPartial(symbol, CancellationToken)) { @@ -338,8 +339,8 @@ private void AddPartialModifiersToTypeChain( private TTypeDeclarationSyntax RemoveLeadingBlankLines( TTypeDeclarationSyntax currentTypeNode) { - var syntaxFacts = State.SemanticDocument.Document.GetRequiredLanguageService(); - var bannerService = State.SemanticDocument.Document.GetRequiredLanguageService(); + var syntaxFacts = SemanticDocument.GetRequiredLanguageService(); + var bannerService = SemanticDocument.GetRequiredLanguageService(); var withoutBlankLines = bannerService.GetNodeWithoutLeadingBlankLines(currentTypeNode); diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeNamespaceScopeEditor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeNamespaceScopeEditor.cs index 73aa4788eb51b..596ed816f2a80 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeNamespaceScopeEditor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeNamespaceScopeEditor.cs @@ -20,43 +20,40 @@ internal abstract partial class AbstractMoveTypeService< /// it will evaluate if the namespace scope needs to be closed and reopened to create a new scope. /// private sealed class MoveTypeNamespaceScopeEditor( - TService service, State state, string fileName, CancellationToken cancellationToken) - : Editor(service, state, fileName, cancellationToken) + TService service, + SemanticDocument document, + TTypeDeclarationSyntax typeDeclaration, + string fileName, + CancellationToken cancellationToken) + : Editor(service, document, typeDeclaration, fileName, cancellationToken) { public override async Task GetModifiedSolutionAsync() { - var node = State.TypeNode; - var documentToEdit = State.SemanticDocument.Document; - - if (node.Parent is not TNamespaceDeclarationSyntax namespaceDeclaration) - return null; - - return await GetNamespaceScopeChangedSolutionAsync(namespaceDeclaration, node, documentToEdit, CancellationToken).ConfigureAwait(false); + return TypeDeclaration.Parent is TNamespaceDeclarationSyntax namespaceDeclaration + ? await GetNamespaceScopeChangedSolutionAsync(namespaceDeclaration).ConfigureAwait(false) + : null; } - private static async Task GetNamespaceScopeChangedSolutionAsync( - TNamespaceDeclarationSyntax namespaceDeclaration, - TTypeDeclarationSyntax typeToMove, - Document documentToEdit, - CancellationToken cancellationToken) + private async Task GetNamespaceScopeChangedSolutionAsync( + TNamespaceDeclarationSyntax namespaceDeclaration) { - var syntaxFactsService = documentToEdit.GetRequiredLanguageService(); + var syntaxFactsService = SemanticDocument.GetRequiredLanguageService(); var childNodes = syntaxFactsService.GetMembersOfBaseNamespaceDeclaration(namespaceDeclaration); if (childNodes.Count <= 1) return null; - var editor = await DocumentEditor.CreateAsync(documentToEdit, cancellationToken).ConfigureAwait(false); - editor.RemoveNode(typeToMove, SyntaxRemoveOptions.KeepNoTrivia); + var editor = await DocumentEditor.CreateAsync(SemanticDocument.Document, this.CancellationToken).ConfigureAwait(false); + editor.RemoveNode(this.TypeDeclaration, SyntaxRemoveOptions.KeepNoTrivia); var generator = editor.Generator; - var index = childNodes.IndexOf(typeToMove); + var index = childNodes.IndexOf(this.TypeDeclaration); var itemsBefore = childNodes.Take(index).ToImmutableArray(); var itemsAfter = childNodes.Skip(index + 1).ToImmutableArray(); var name = syntaxFactsService.GetDisplayName(namespaceDeclaration, DisplayNameOptions.IncludeNamespaces); - var newNamespaceDeclaration = generator.NamespaceDeclaration(name, WithElasticTrivia(typeToMove)).WithAdditionalAnnotations(NamespaceScopeMovedAnnotation); + var newNamespaceDeclaration = generator.NamespaceDeclaration(name, WithElasticTrivia(this.TypeDeclaration)).WithAdditionalAnnotations(NamespaceScopeMovedAnnotation); if (itemsBefore.Any() && itemsAfter.Any()) { diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameFileEditor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameFileEditor.cs index 216dee90638cb..73607e6b9e236 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameFileEditor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameFileEditor.cs @@ -11,23 +11,25 @@ namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; internal abstract partial class AbstractMoveTypeService { - private sealed class RenameFileEditor(TService service, State state, string fileName, CancellationToken cancellationToken) : Editor(service, state, fileName, cancellationToken) + /// + /// Renames the file to match the type contained in it. + /// + private sealed class RenameFileEditor( + TService service, + SemanticDocument document, + TTypeDeclarationSyntax typeDeclaration, + string fileName, + CancellationToken cancellationToken) : Editor(service, document, typeDeclaration, fileName, cancellationToken) { - /// - /// Renames the file to match the type contained in it. - /// public override async Task> GetOperationsAsync() { var newSolution = await GetModifiedSolutionAsync().ConfigureAwait(false); + Contract.ThrowIfNull(newSolution); return [new ApplyChangesOperation(newSolution)]; } - public override Task GetModifiedSolutionAsync() - { - var modifiedSolution = SemanticDocument.Project.Solution - .WithDocumentName(SemanticDocument.Document.Id, FileName); - - return Task.FromResult(modifiedSolution); - } + public override Task GetModifiedSolutionAsync() + => Task.FromResult( + SemanticDocument.Project.Solution.WithDocumentName(SemanticDocument.Document.Id, FileName)); } } diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameTypeEditor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameTypeEditor.cs index a7ede6d827d32..6ec382833414d 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameTypeEditor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameTypeEditor.cs @@ -2,29 +2,32 @@ // 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 System.Threading.Tasks; using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; internal abstract partial class AbstractMoveTypeService { - private sealed class RenameTypeEditor(TService service, State state, string fileName, CancellationToken cancellationToken) : Editor(service, state, fileName, cancellationToken) + private sealed class RenameTypeEditor( + TService service, + SemanticDocument document, + TTypeDeclarationSyntax typeDeclaration, + string fileName, + CancellationToken cancellationToken) : Editor(service, document, typeDeclaration, fileName, cancellationToken) { - /// /// Renames a type to match its containing file name. /// - public override async Task GetModifiedSolutionAsync() + public override async Task GetModifiedSolutionAsync() { // TODO: detect conflicts ahead of time and open an inline rename session if any exists. // this will bring up dashboard with conflicts and will allow the user to resolve them. // if no such conflicts exist, proceed with RenameSymbolAsync. - var solution = SemanticDocument.Document.Project.Solution; - var symbol = State.SemanticDocument.SemanticModel.GetDeclaredSymbol(State.TypeNode, CancellationToken); + var solution = SemanticDocument.Project.Solution; + var symbol = SemanticDocument.SemanticModel.GetRequiredDeclaredSymbol(this.TypeDeclaration, CancellationToken); return await Renamer.RenameSymbolAsync(solution, symbol, new SymbolRenameOptions(), FileName, CancellationToken).ConfigureAwait(false); } } diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.State.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.State.cs deleted file mode 100644 index c5eef926d44a7..0000000000000 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.State.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.IO; -using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Shared.Extensions; - -namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; - -internal abstract partial class AbstractMoveTypeService -{ - private sealed class State - { - public SemanticDocument SemanticDocument { get; } - - public TTypeDeclarationSyntax TypeNode { get; } - public string DocumentNameWithoutExtension { get; } - - private State(SemanticDocument document, TTypeDeclarationSyntax typeNode) - { - SemanticDocument = document; - TypeNode = typeNode; - - DocumentNameWithoutExtension = Path.GetFileNameWithoutExtension(SemanticDocument.Document.Name); - } - - public static State? Generate(TService service, SemanticDocument document, TTypeDeclarationSyntax typeDeclaration) - => service.GetSymbolName(typeDeclaration) is "" ? null : new State(document, typeDeclaration); - } -} diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs index 8fbfe13f78558..39ff19a2d8b3b 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs @@ -48,54 +48,58 @@ protected string GetSymbolName(TTypeDeclarationSyntax syntax) public override async Task> GetRefactoringAsync( Document document, TextSpan textSpan, CancellationToken cancellationToken) { - var state = await CreateStateAsync(document, textSpan, cancellationToken).ConfigureAwait(false); - return CreateActions(state); + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var typeDeclaration = await GetTypeDeclarationAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + return CreateActions(semanticDocument, typeDeclaration); } public override async Task GetModifiedSolutionAsync(Document document, TextSpan textSpan, MoveTypeOperationKind operationKind, CancellationToken cancellationToken) { - var state = await CreateStateAsync(document, textSpan, cancellationToken).ConfigureAwait(false); - if (state == null) + var typeDeclaration = await GetTypeDeclarationAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + if (typeDeclaration == null) return document.Project.Solution; - var suggestedFileNames = GetSuggestedFileNames( - state.TypeNode, state.SemanticDocument.Document.Name, includeComplexFileNames: false); + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var suggestedFileNames = GetSuggestedFileNames(semanticDocument, typeDeclaration, includeComplexFileNames: false); - var editor = Editor.GetEditor(operationKind, (TService)this, state, suggestedFileNames.FirstOrDefault(), cancellationToken); + var editor = Editor.GetEditor(operationKind, (TService)this, semanticDocument, typeDeclaration, suggestedFileNames.First(), cancellationToken); var modifiedSolution = await editor.GetModifiedSolutionAsync().ConfigureAwait(false); return modifiedSolution ?? document.Project.Solution; } - private async Task CreateStateAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) + private async Task GetTypeDeclarationAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) { var nodeToAnalyze = await GetRelevantNodeAsync(document, textSpan, cancellationToken).ConfigureAwait(false); if (nodeToAnalyze == null) return null; - var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - return State.Generate((TService)this, semanticDocument, nodeToAnalyze); + var name = this.GetSymbolName(nodeToAnalyze); + return name == "" ? null : nodeToAnalyze; } - private ImmutableArray CreateActions(State? state) + private ImmutableArray CreateActions( + SemanticDocument document, TTypeDeclarationSyntax? typeDeclaration) { - if (state is null) + if (typeDeclaration is null) return []; - var typeMatchesDocumentName = TypeMatchesDocumentName(state.TypeNode, state.DocumentNameWithoutExtension); + var documentNameWithoutExtension = GetDocumentNameWithoutExtension(document); + var typeMatchesDocumentName = TypeMatchesDocumentName(typeDeclaration, documentNameWithoutExtension); // if type name matches document name, per style conventions, we have nothing to do. if (typeMatchesDocumentName) return []; using var _ = ArrayBuilder.GetInstance(out var actions); - var manyTypes = MultipleTopLevelTypeDeclarationInSourceDocument(state.SemanticDocument.Root); - var isNestedType = IsNestedType(state.TypeNode); - var syntaxFacts = state.SemanticDocument.Document.GetRequiredLanguageService(); - var isClassNextToGlobalStatements = !manyTypes && ClassNextToGlobalStatements(state.SemanticDocument.Root, syntaxFacts); + var manyTypes = MultipleTopLevelTypeDeclarationInSourceDocument(document.Root); + var isNestedType = IsNestedType(typeDeclaration); + + var syntaxFacts = document.GetRequiredLanguageService(); + var isClassNextToGlobalStatements = !manyTypes && ClassNextToGlobalStatements(document.Root, syntaxFacts); var suggestedFileNames = GetSuggestedFileNames( - state.TypeNode, state.SemanticDocument.Document.Name, includeComplexFileNames: false); + document, typeDeclaration, includeComplexFileNames: false); // (1) Add Move type to new file code action: // case 1: There are multiple type declarations in current document. offer, move to new file. @@ -107,21 +111,21 @@ private ImmutableArray CreateActions(State? state) if (manyTypes || isNestedType || isClassNextToGlobalStatements) { foreach (var fileName in suggestedFileNames) - actions.Add(GetCodeAction(state, fileName, operationKind: MoveTypeOperationKind.MoveType)); + actions.Add(GetCodeAction(fileName, operationKind: MoveTypeOperationKind.MoveType)); } // (2) Add rename file and rename type code actions: // Case: No type declaration in file matches the file name. - if (!AnyTopLevelTypeMatchesDocumentName(state)) + if (!AnyTopLevelTypeMatchesDocumentName()) { foreach (var fileName in suggestedFileNames) - actions.Add(GetCodeAction(state, fileName, operationKind: MoveTypeOperationKind.RenameFile)); + actions.Add(GetCodeAction(fileName, operationKind: MoveTypeOperationKind.RenameFile)); // Only if the document name can be legal identifier in the language, offer to rename type with document name - if (syntaxFacts.IsValidIdentifier(state.DocumentNameWithoutExtension)) + if (syntaxFacts.IsValidIdentifier(documentNameWithoutExtension)) { actions.Add(GetCodeAction( - state, fileName: state.DocumentNameWithoutExtension, + fileName: documentNameWithoutExtension, operationKind: MoveTypeOperationKind.RenameType)); } } @@ -129,14 +133,19 @@ private ImmutableArray CreateActions(State? state) Debug.Assert(actions.Count != 0, "No code actions found for MoveType Refactoring"); return actions.ToImmutableAndClear(); + + bool AnyTopLevelTypeMatchesDocumentName() + => TopLevelTypeDeclarations(document.Root).Any( + typeDeclaration => TypeMatchesDocumentName( + typeDeclaration, documentNameWithoutExtension)); + + MoveTypeCodeAction GetCodeAction(string fileName, MoveTypeOperationKind operationKind) + => new((TService)this, document, typeDeclaration, operationKind, fileName); } private static bool ClassNextToGlobalStatements(SyntaxNode root, ISyntaxFactsService syntaxFacts) => syntaxFacts.ContainsGlobalStatement(root); - private MoveTypeCodeAction GetCodeAction(State state, string fileName, MoveTypeOperationKind operationKind) - => new((TService)this, state, operationKind, fileName); - private static bool IsNestedType(TTypeDeclarationSyntax typeNode) => typeNode.Parent is TTypeDeclarationSyntax; @@ -152,14 +161,8 @@ private static bool MultipleTopLevelTypeDeclarationInSourceDocument(SyntaxNode r private static IEnumerable TopLevelTypeDeclarations(SyntaxNode root) => root.DescendantNodes(n => n is TCompilationUnitSyntax or TNamespaceDeclarationSyntax).OfType(); - private bool AnyTopLevelTypeMatchesDocumentName(State state) - { - var root = state.SemanticDocument.Root; - - return TopLevelTypeDeclarations(root).Any( - typeDeclaration => TypeMatchesDocumentName( - typeDeclaration, state.DocumentNameWithoutExtension)); - } + private static string GetDocumentNameWithoutExtension(SemanticDocument document) + => Path.GetFileNameWithoutExtension(document.Document.Name); /// /// checks if type name matches its parent document name, per style rules. @@ -207,10 +210,11 @@ private static bool TypeNameMatches(string documentNameWithoutExtension, string } private ImmutableArray GetSuggestedFileNames( + SemanticDocument document, TTypeDeclarationSyntax typeNode, - string documentNameWithExtension, bool includeComplexFileNames) { + var documentNameWithExtension = document.Document.Name; var isNestedType = IsNestedType(typeNode); var (typeName, arity) = this.GetSymbolNameAndArity(typeNode); var fileExtension = Path.GetExtension(documentNameWithExtension);