blob: bb570d875ce79ac8e0742b79f58a039d8e0dd381 [file] [log] [blame] [view] [edit]
# Refactors
LSP: [textDocument_codeActions][] request
Legacy: `edit.getAssists`, `edit.getAvailableRefactorings`, `edit.getFixes`,
and `edit.getRefactoring` requests
Refactors are actions that users can select that make changes to their code.
## Kinds
While users tend to think of all refactors as essentially being the same,
internally we divide them into three kinds. This is in part due to
implementation considerations, but more importantly because there are
implications in terms of the UX.
The kinds are based on two characteristics of the refactor:
<dl>
<dt>scope</dt>
<dd>
A local refactor is limited to making changes within the library in which the
refactor is invoked. A global refactor can make changes in any of the
libraries that are in the IDE's workspace.
</dd>
<dt>availability</dt>
<dd>
Some refactors are only available when there is a diagnostic indicating a
problem. Others are available whenever the editor's selection is on the
appropriate tokens.
</dd>
</dl>
The three kinds are described below.
### Fixes
Fixes are code changes that are designed to resolve a problem in the code that
is indicated by a diagnostic. The changes are always local in scope.
Fixes can be initiated to
- fix a single diagnostic at a single location
- fix all of the locations of a single diagnostic in a single file
- fix all of the locations of all diagnostics (via `dart fix` and in LSP-based
IDEs)
### Assists
Assists are code changes that are available even when there is no diagnostic.
The changes are always local in scope.
Assists can only be initiated at a single location.
### Global Refactors
Global refactors are code changes that might involve changes to multiple
libraries and possibly across multiple packages, when those packages are all
open in the IDE's workspace.
Global refactors can only be initiated at a single location.
Note that in many contexts (such as the issue tracker) we use the term
'refactor' to sometimes mean any kind of refactor and sometimes to mean a global
refactor. In this document we'll use the longer name for clarity.
## Preserving semantics
There is no rule against refactors that change the semantics of the code. Some
refactors are only useful _because_ they change the semantics. It could be
argued that most of the fixes are semantics changing: taking the code from being
broken to being compilable. This section discusses the criteria we use to decide
when it's appropriate for a refactor to be semantic preserving and when it's
reasonable for it to change the semantics.
### User expectations
One question that should be asked is how likely it is that a user would
reasonably expect the semantics to be preserved. For example, it's reasonable
for a user to assume that a refactor that converts a switch statement into a
switch expression would preserve the semantics of the switch. On the other hand,
it's reasonable for a user to expect that a refactor that changes a method to be
marked as `async` and changes the return type to be a `Future` would change the
semantics of the code by doing so.
### Subtle vs. obvious changes
If a refactor is going to change the semantics of the code then it ought to be
obvious to the user that the semantics have changed. The more subtle the change
to the semantics, the less appropriate it is for the semantics to change. For
example, the assist that converts a method to be marked as `async` changes the
semantics, but the change is easy to see because the return type is changed and
a new keyword is added. On the other hand, a change that impacts the lookup
scope in such a way that some identifiers are resolved to different targets
without any indication that this is the case is probably too subtle.
If a fix is being applied at a single location, then the semantic changes will
generally be more obvious. If a fix is being applied across a large code base,
then the semantic changes might easily not be noticed because the affected files
might not be open.
## Producing broken code
There are few, if any, valid reasons for a refactor to produce code that doesn't
compile. Some refactors will work on code that is already broken, in which case
it's reasonable for the result to also be broken, as long as it isn't broken
worse. But it usually isn't reasonable for a refactor to introduce new
diagnostics into the code.
[textDocument_codeActions]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeActions