Skip to content

Commit

Permalink
Move Code Actions docs to the site
Browse files Browse the repository at this point in the history
Reviewed By: alanz

Differential Revision: D48872316

fbshipit-source-id: f79be126b9d2381b11f07a1582ea119a46be8868
  • Loading branch information
robertoaloi authored and facebook-github-bot committed Aug 31, 2023
1 parent c83c1a1 commit e7188da
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 48 deletions.
Binary file removed docs/images/code-action-add-edoc.png
Binary file not shown.
Binary file removed docs/images/code-action-remove-function.png
Binary file not shown.
99 changes: 51 additions & 48 deletions docs/CODE_ACTIONS.md → website/docs/contributing/code-actions.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,44 @@
---
sidebar_position: 2
---

# Code Actions (a.k.a. Assists)

_Code actions_, also known as _assists_, are small local refactorings, often rendered in the text editor using a light bulb icon (💡). They are triggered by either clicking the light bulb icon in the editor or by using a shortcut.

Code actions often provide the user with possible corrective actions right next to an error or warning (known as a _diagnostic_ message using LSP jargon). They can also occur independently of diagnostics.

Here is an example of a _code action_ prompting the user to "Add Edoc comment" for a function which lacks Erlang EDoc documentation.
Here is an example of a _code action_ prompting the user to _add an EDoc comment_ for a function which lacks Erlang EDoc documentation.

![Code Action - Add Edoc](../../static/img/code-action-add-edoc.png)

And this is what the code looks like after the suggestion has been applied:

![Code Action - Add Edoc](./images/code-action-add-edoc.png)
![Code Action - Add Edoc Fix](../../static/img/code-action-add-edoc-fix.png)

## The _Code Action_ request

Code actions are requested by the editor using the [textDocument/codeAction](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction) LSP request. Code action requests are handled by the `handlers::handle_code_action` function in the `elp` crate.

## Adding a new code action

### Creating the handler
In this section we will go through the process of adding a new code action from scratch. The code action (or _assist_) will suggest the user to delete a function, if it is deemed as unused by the Erlang compiler.

In this section we will go through the process of adding a new code action from scratch. The code action (aka _assist_) will suggest the user to delete a function, if it is deemed as unused by the Erlang compiler.
### Creating the handler

Let's start by creating a file called `delete_function.rs` in the `crates/ide_assists/src/handlers` folder, containing a single function declaration:
Let's start by creating a new file named `delete_function.rs`, containing a single function declaration:

```
```rust title="crates/ide_assists/src/handlers/delete_function.rs"
use crate::assist_context::{Assists, AssistContext};

// Assist: delete_function
//
// Delete a function, if deemed as unused by the Erlang compiler.
//
// ```
// -module(life).
//
// heavy_calculations(X) -> X.
// %% ^^^^^^^^^^^^^^^ 💡 L1230: Function heavy_calculations/1 is unused
//
// meaning() ->
// 42.
// ```
// ->
// ```
// -module(life).
// meaning() ->
// 42.
// ```
pub(crate) fn delete_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
todo!()
}
```

Notice how the function is accompanied by a comment which explains the expected transformation. In the first snippet, we notice that the `heavy_calculations` function is unused and we get a code action for it. In the second snippet we see the outcome of executing the given code action: the unused function is gone. We will go back to the funny syntax used in the snippet in a second.

Before we can start implementing our code action, there's one more thing we need to do: add our new function to the list of _ELP assists_. Open the `crates/ide_assists/src/lib.rs` file and amend the list of handlers:
Before we can start implementing our code action, there's one more thing we need to do: ensure our new function is invoked by adding it to the list of _ELP assists_. Open the `crates/ide_assists/src/lib.rs` file and amend the list of handlers:

```
```rust title="crates/ide_assists/src/lib.rs"
mod handlers {
[...]
mod delete_function
Expand All @@ -69,11 +56,11 @@ mod handlers {

### Adding a test case

The easiest way to verify our new code action behaves in the expected way is to start with a test case. ELP allows us to write tests in a very intuitive and straightforward way. Do you remember the funny syntax which we introduced in the previous paragraph? We can reuse it to write a testcase!
The easiest way to verify our new code action behaves in the expected way is to start with a test case. ELP allows us to write tests in a very intuitive and straightforward way.

Add the following to the `delete_function.rs` file:

```
```rust title="crates/ide_assists/src/handlers/delete_function.rs"
#[cfg(test)]
mod tests {
use expect_test::expect;
Expand All @@ -85,27 +72,41 @@ mod tests {
fn test_delete_unused_function() {
check_assist(
delete_function,
"Remove the unused function `heavy_calculations/1`",
r#"
-module(life).
heavy_calculations(X) -> X.
%% ^^^^^^^^^^^^^^^ 💡 L1230: Function heavy_calculations/1 is unused
-module(life).
meaning() ->
42.
heavy_cal~culations(X) ->
%% ^^^^^^^^^^^^^^^^^^^ 💡 L1230: Function heavy_calculations/1 is unused
X.
meaning() ->
42.
"#,
expect![[r#"
-module(life).
meaning() ->
42.
expect![[
r#"
-module(life).
meaning() ->
42.
"#]],
)
}
}
```

The `~` in the snippet represents the cursor position. In our testcase, we are asserting that, given a diagnostic message pointing to the unused function, if the user triggers the respective code action when the cursor is inside the function name, the unused function gets deleted.
There is a lot happening here, so let's go through the code. We are defining a new test, named `test_delete_unused_function`, which uses an auxiliary function (`check_assist`) to verify that a given assist behaves as expected.

The `check_assist` function takes 4 arguments:

* The assist _handler_ (`delete_function`)
* A _label_ for the assist
* An input fixture representing what the code looks like **before** a fix is applied
* An output fixture (wrapped in an `expect` macro) showing what the code looks like **after** a fix is applied

If we try running the test, it should fail with a _not yet implemented_ error:
The `~` in the first snippet represents the cursor position. We are asserting that - given a diagnostic message pointing to the unused function - if the user triggers the respective code action when the cursor is hovering the function name range, the unused function gets deleted.

Let's try running the test, it should fail with a _not yet implemented_ error:

```
$ cargo test --package elp_ide_assists --lib -- handlers::delete_function::tests::test_delete_unused_function --exact --nocapture
Expand All @@ -121,7 +122,7 @@ thread 'handlers::delete_function::tests::test_delete_unused_function' panicked
Before starting with the actual implementation, let's for a second go back to the syntax we used to specify the _unused function_ diagnostic:

```
%% ^^^^^^^^^^^^^^^ 💡 L1230: Function heavy_calculations/1 is unused
%% ^^^^^^^^^^^^^^^^^^^ 💡 L1230: Function heavy_calculations/1 is unused
```

This is a test _annotation_ which is used by the ELP testing framework to populate the "context" which is passed to our handler. This is a way to simulate diagnostics coming from external sources (such as the Erlang compiler or a linter), which would be received by the Language Server as part of a `textDocument/codeAction` request.
Expand All @@ -138,9 +139,10 @@ Essentially, a number of spaces, followed by the `%%` which resembles an Erlang

To be able to match the `L1230` error code, we need to add a new variant to the `AssistContextDiagnosticCode` enum. Open the `crates/ide_db/src/assists.rs` file and include the new error code. Don't forget to map it to the `L1230` string.

```
```rust title="crates/ide_db/src/assists.rs"
pub enum AssistContextDiagnosticCode {
UnusedFunction, // <--- Add this
[...]
UnusedVariable,
}

Expand All @@ -149,6 +151,7 @@ impl FromStr for AssistContextDiagnosticCode {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"L1230" => Ok(AssistContextDiagnosticCode::UnusedFunction), // <--- Add this
[...]
"L1268" => Ok(AssistContextDiagnosticCode::UnusedVariable),
unknown => Err(format!("Unknown AssistContextDiagnosticCode: '{unknown}'")),
}
Expand All @@ -170,7 +173,7 @@ pub(crate) fn delete_function(acc: &mut Assists, ctx: &AssistContext) -> Option<

We have two input arguments: a mutable _accumulator_ which contains the list of code actions (or _assists_) which we want to return and a _context_, from which we can extract diagnostics.

The following code iterates through the list of diagnostics and, for each diagnostic matching the `UnusedFunction` kind, prints the diagnostic for debugging purposes. We also return `Some(())` to comply with the function signature.
The following code iterates through the list of diagnostics and, for each diagnostic matching the `UnusedFunction` kind, prints the diagnostic for debugging purposes. We return `Some(())` to comply with the function signature.

```
use elp_ide_db::assists::AssistContextDiagnosticCode;
Expand Down Expand Up @@ -285,11 +288,11 @@ cargo build
Then visit the Erlang extension settings page and edit the `elp.path` value to point to the newly built executable, which should reside in:

```
/Users/$USER/fbsource/buck-out/elp/debug/elp
./debug/elp
```

Open a VS Code @ Meta (or reload the window if you have one open) and visit an Erlang file from the WASERVER repo. You should see something like:
Open VS Code (or reload the window if you have it already open) and visit an Erlang file which contains an unused function. You should see something like:

![Code Action - Remove Function](./images/code-action-remove-function.png)
![Code Action - Remove Function](../../static/img/code-action-remove-function.png)

If that worked, congratulations! You managed to write your first ELP code action!
5 changes: 5 additions & 0 deletions website/docs/contributing/contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
sidebar_position: 1
---

# Contributing
6 changes: 6 additions & 0 deletions website/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
position: 'left',
label: 'Erlang Error Index',
},
{
type: 'doc',
docId: 'contributing/contributing',
position: 'left',
label: 'Contributing',
},
{
href: 'https://github.com/whatsapp/erlang-language-platform',
label: 'GitHub',
Expand Down
Binary file added website/static/img/code-action-add-edoc-fix.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website/static/img/code-action-add-edoc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit e7188da

Please sign in to comment.