Skip to content
This repository was archived by the owner on Mar 6, 2022. It is now read-only.

Rename LSP support #21

Closed
wants to merge 43 commits into from
Closed

Rename LSP support #21

wants to merge 43 commits into from

Conversation

BladeMF
Copy link
Contributor

@BladeMF BladeMF commented Jan 8, 2021

I am creating this PR so we can discuss the code. It's not going to get any better if I wait longer :-)

The code works by using Find all references to find everything that can be renamed.

  1. Rename type. If type has the same name as the file, rename the file.
  2. Rename local variable.
  3. Rename method.

phpactor/phpactor#1140

Copy link
Contributor

@dantleech dantleech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great that you've achieved what you have so far.

In general I think this is looking good, I want to suggest a different architetcture though:

Model\Rename.php // main class
Model\Renamer.php // interface
Model\Renamer\VariableRenamer.php
Model\Renamer\ClassLikeRenamer.php
Model\Renamer\MethodRenamer.php

This could be implemented by the Rename service having a collection of Rename instances, resolving the appropriate renamer for the node:

$this->resolveRenamer($node)->rename(...);
private function resolveRenamer(Node $node): Renamer
{
    foreach ($this->renamers as $renamer) {
        if (!$renamer->supports($node)) {
            continue;
        }
        return $renamer;
    }

    throw new CouldNotFindRenamer(sprintf('Could not find renamer for node "%s"', get_class($node));
}

We can start with only implementing VariableRenamer: this will allow us to merge and build a solid base for implementing the remaining functionality - some of which is not trivial (e.g. class renaming aka. class moving)

some other comments:

  • The public API for the Rename service should accept the Phpactor value objects (Phpactor\...\TextDocument etc) ...
  • ... as the Renamer class may be split into it's own "package" or otherwise extracted so that it can be used by the CLI and the legacy RPC implementation at a later date.

Some of the coding-standard is not what I'd expect - not sure if the CS fixer is working or not.

@@ -62,6 +62,8 @@
"Phpactor\\Extension\\LanguageServer\\Tests\\": "tests/LanguageServer/",
"Phpactor\\Extension\\LanguageServerCompletion\\Tests\\": "tests/LanguageServerCompletion/",
"Phpactor\\Extension\\LanguageServerReferenceFinder\\Tests\\": "tests/LanguageServerReferenceFinder/",
"Phpactor\\Extension\\LanguageServerRename\\Tests\\": "tests/LanguageServerRename/",
"Phpactor\\Extension\\LanguageServerSelectionRange\\Tests\\": "tests/LanguageServerSelectionRange/",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added in error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, otherwise "Fix namespace..." didn't work properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed that and composer install (supposedly to update the autoloader) for it to work. The other namespace I added I think you've missed because tests have weird namespace (note the missing starting letter of Selection):

namespace Phpactor\Extension\LanguageServer\Tests\electionRange\Unit\Model;

@@ -176,7 +176,7 @@ private function renderMember(SymbolContext $symbolContext): string
private function renderFunction(SymbolContext $symbolContext): string
{
$name = $symbolContext->symbol()->name();
$function = $this->reflector->reflectFunction($name);
$function = $this->reflector->reflectFunction((string)$symbolContext->name());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for a type cast. the WR API can accept either Name or string

$this->nodeUtils = $nodeUtils;
}

public function prepareRename(TextDocumentItem $textDocument, Position $position): ?Range
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public APIs should accept Phpactor documents, in this case the text document and range from the Phpactor phpactor/text-document library. This would enable us to use this service outside of the LS (CLI / RPC)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, for the next version.

/** @var Location $location */
$node = $rootNode->getDescendantNodeAtPosition($location->offset()->toInt());

if(($r = $this->renameFileIfNeeded($node, $documentUri, $oldName, $newName)) !== null){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be happy if we removed the file-rename functionality for now, this is a more complex topic I think. Let's concentrate on a smaller set of features.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean, 'more complex'? Specifically renaming when renaming the class itself is pretty straight-forward - either they have the same name or they don't (unless I am missing something). The other way around is the complicated bit, I think - when the class file is being moved..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean it will spiral out of control probably. If we focus on a single feature then we can be happy with it faster and merge it, then see about other features.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am inclined to agree, although I'd be happy to hear the scenario you imagine.

Copy link
Contributor

@dantleech dantleech Jan 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Off the top of my head the class moving stuff:

  • Rename is moving.
  • Moving is moving a file - finding out where that file should live depending on the namespace (Phpactor already has a solution for this problem)
  • The current Phpactor implementation moves the file in GiT too (via. the source code filesystem)
  • Dealing with class / trait / blah / aliases is always fun.
  • The current RPC handler for move will also move any related test files.

There will always be more complexity that will take you by surprise.

For the member (constant,property,method) renaming etc. it may be simpler, but the logic still differs from local variables considerably (no need to worry about scope etc).

In fact, member renaming may even be the easiest thing, but there again there are subtle differences between the members (as with the $)

}
}

return [ $edits, $rename ];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we remove renmae file then this becomes returns only TextEdits

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is going to be reworked completely (we'd be returning RenameResults) so no need to clear that now, I think.

@BladeMF
Copy link
Contributor Author

BladeMF commented Jan 9, 2021

Thank you for your notes and ideas! I appreciate your time with this.

Just so I am sure I understand your idea correctly, let me try to explain how it currently works and then we can get to me understanding your idea.

How it currently works

  1. Use the current DefinitionLocator to locate the definition using the current offset
  2. Use the current ReferenceFinder to locate all the references at the current offset
  3. Collect all the locations and group them by document
  4. For each document, for each location:
    4.1. Check if file needs renaming (are we renaming a type with the same name as the file?)
    4.2. Get the precise range for each edit
  5. Create a WorkspaceEdit object.

Your idea

While I agree that class moving should be somewhere in there, I have trouble seeing how we can use this architecture while using DefinitionLocator, ReferenceFinder because they currently implement the task of splitting between class, members variables and methods and variables.

Can you please elaborate more on how the above logic will go into three classes as it does not cite classes or members or variables with the exception of when deciding whether to rename the file. Do you want to reimplement the same reference finding logic in VariableRenamer, ClassRenamer and MethodRenamer or am I misunderstanding you?

@weeman1337
Copy link
Contributor

This one seems to be replaced by #24 ? Could it then be closed?

@dantleech
Copy link
Contributor

yes, this has been extracted and implemented in other PRs :)

@dantleech dantleech closed this Apr 18, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants