| # 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 partially 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>edit creation time</dt> | |
| <dd> | |
| <p> | |
| An _eager refactor_ is one in which the code changes are computed before the | |
| code action is returned. If code edits can't be computed, then the code action | |
| isn't returned. This ensures that if the user selects the action it will | |
| successfully apply the edits. | |
| </p><p> | |
| A _lazy refactor_ is one in which the code action is returned without | |
| computing the code edits. Typically some minimal set of checks will be | |
| performed so that the code action is only shown when the refactor makes sense, | |
| but it's still possible for the refactor to fail after the user has selected | |
| it. In those cases we display a message telling the user why it failed. | |
| </p> | |
| </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 computed eagerly. | |
| Fixes can be initiated to | |
| - fix a single diagnostic at a single location in a single file | |
| - fix all of the locations in a single file where a single diagnostic is | |
| reported | |
| - fix all of the locations of all diagnostics in all files in the workspace | |
| (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 computed eagerly. | |
| Assists can only be initiated at a single location in a single file. | |
| ### 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 are always computed lazily. | |
| Global refactors can only be initiated at a single location in a single file. | |
| 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. There are a couple of known exceptions: | |
| - 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. | |
| - If the client allows the server to notify the user of the situation and the | |
| user indicates that they want to proceed, then it makes sense to proceed with | |
| the refactor. | |
| ## Generated code | |
| Most refactors produce code as part of their function. This section describes | |
| the practices that should be followed when generating code. | |
| One of the best ways to adhere to these practices is to use the utilities | |
| defined in the `DartFileEditBuilder` and `DartEditBuilder` classes when | |
| generating code. | |
| ### Style | |
| The generated code should be as close as possible to what the user would likely | |
| write. For example, if we believe that users are more likely to write methods | |
| with block bodies than with expression bodies, then we should produce block | |
| bodies when generating a method. Similarly, if we believe that most users use | |
| `async` for methods that return a `Future`, then we should include the modifier | |
| when generating such a method. | |
| The generated code should, where possible, follow the style that is enforced by | |
| the enabled lints. For example, if the lint is enabled that enforces the use of | |
| one delimiter for string literals, then any string literals that are generated | |
| should use that delimiter. | |
| The generated code should be formatted similar to what the formatter produces. | |
| Reasonable attempts are made to generate code with appropriate indentation and | |
| space characters between tokens, but generated code does not need to include | |
| line-wrapping, intentionally added or omitted commas, etc. If we get the | |
| ability to run the formatter over portions of a file, then we should use the | |
| formatter on generated code. | |
| ### Incomplete code | |
| Sometimes it's necessary to generate incomplete code. For example, when | |
| generating a method based on an example invocation, the server can infer the | |
| number and types of the parameters and even the return type, but can't know how | |
| to implement the method. | |
| When incomplete code is generated, it should be as obvious as possible that the | |
| code is incomplete. There are two ways to do this: generating code that has a | |
| compilation error or generating a `TODO` comment as part of the generated code. | |
| For example, if a method that returns a value is being generated, then we could | |
| choose to generate code that returns `null`. However, if the return type is | |
| nullable, then there won't be any signal that the method is incomplete. It would | |
| be better to generate code that doesn't return a value. | |
| As a counter-example, when the server generates an override of a concrete method | |
| it adds a super-invocation of the overridden method. If the method returns a | |
| value then we return the value from the overridden method. That leaves the code | |
| with no diagnostics, so we add a `TODO` comment. It also leaves the code being | |
| semantically equivalent. That might seem like it violates the advice above, but | |
| technically the code isn't incomplete, even though it usually isn't what the | |
| user intends to write. | |
| [textDocument_codeActions]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeActions |