sway/sway-lsp/tests/integration/code_actions.rs
Sophie Dankel 38a2b55991
LSP: generate doc comments for trait functions (#4665)
## Description

Closes https://github.com/FuelLabs/sway/issues/4645

This PR lets the user generate doc comments for ABI methods. Previously
it was working for functions and function implementations of ABI/trait,
but not on the ABI/trait definition itself.

Since the output is exactly the same for FunctionDecl to TraitFn, I
wanted to do this in a way where they could share the code. I ended up
writing a trait called `FunctionSignature` that both types implement
that exposes `parameters()` and `return_type()`. This allows me to use
the same code for code actions and I suspect it will come in handy in
the future.

One problem was that `FunctionDecl`'s return_type was `TypeArgument`
while `TraitFn` had its return type as `TypeId` with an additional
`return_type_span` field. I changed it so that `TraitFn`'s return_type
is now `TypeArgument` and removed `return_type_span`.

I also needed to add a `span` field to `TraitFn` since previously its
`span()` was returning only the span of the function name, not the whole
function. From what I could see, this doesn't impact the spans of any
existing errors.

### Testing

I added an integration test to the LSP for this scenario.

### Example

![Jun-13-2023
14-40-21](5ba750b6-9f9d-4cb9-96a8-c791bf93b77d)

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [ ] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.
2023-06-15 19:30:38 +02:00

509 lines
15 KiB
Rust

//! This file contains methods used for simulating LSP code action json-rpc notifications and requests.
//! The methods are used to build and send requests and notifications to the LSP service
//! and assert the expected responses.
use crate::integration::lsp::{build_request_with_id, call_request};
use assert_json_diff::assert_json_eq;
use serde_json::json;
use sway_lsp::server::Backend;
use tower_lsp::{
jsonrpc::{Request, Response},
lsp_types::*,
LspService,
};
pub(crate) async fn code_action_abi_request(
service: &mut LspService<Backend>,
uri: &Url,
) -> Request {
let params = json!({
"textDocument": {
"uri": uri,
},
"range" : {
"start":{
"line": 27,
"character": 4
},
"end":{
"line": 27,
"character": 9
},
},
"context": {
"diagnostics": [],
"triggerKind": 2
}
});
let code_action = build_request_with_id("textDocument/codeAction", params, 1);
let response = call_request(service, code_action.clone()).await;
let uri_string = uri.to_string();
let expected = Response::from_ok(
1.into(),
json!([{
"data": uri,
"edit": {
"changes": {
uri_string: [
{
"newText": "\nimpl FooABI for Contract {\n fn main() -> u64 {}\n}\n",
"range": {
"end": {
"character": 0,
"line": 31
},
"start": {
"character": 0,
"line": 31
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate impl for `FooABI`"
}]),
);
assert_json_eq!(expected, response.ok().unwrap());
code_action
}
pub(crate) async fn code_action_function_request(
service: &mut LspService<Backend>,
uri: &Url,
) -> Request {
let params = json!({
"textDocument": {
"uri": uri,
},
"range" : {
"start": {
"line": 18,
"character": 4
},
"end": {
"line": 18,
"character": 4
}
},
"context": {
"diagnostics": [],
"triggerKind": 2
}
});
let code_action = build_request_with_id("textDocument/codeAction", params, 1);
let response = call_request(service, code_action.clone()).await;
let uri_string = uri.to_string();
let expected = Response::from_ok(
1.into(),
json!([
{
"data": uri,
"edit": {
"changes": {
uri_string: [
{
"newText": "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n/// \n/// ### Reverts\n/// \n/// * List any cases where the function will revert\n/// \n/// ### Number of Storage Accesses\n/// \n/// * Reads: `0`\n/// * Writes: `0`\n/// * Clears: `0`\n/// \n/// ### Examples\n/// \n/// ```sway\n/// let x = test();\n/// ```\n",
"range": {
"end": {
"character": 0,
"line": 18
},
"start": {
"character": 0,
"line": 18
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate a documentation template"
}
]),
);
assert_json_eq!(expected, response.ok().unwrap());
code_action
}
pub(crate) async fn code_action_trait_fn_request(
service: &mut LspService<Backend>,
uri: &Url,
) -> Request {
let params = json!({
"textDocument": {
"uri": uri,
},
"range" : {
"start": {
"line": 10,
"character": 10
},
"end": {
"line": 10,
"character": 10
}
},
"context": {
"diagnostics": [],
"triggerKind": 2
}
});
let code_action = build_request_with_id("textDocument/codeAction", params, 1);
let response = call_request(service, code_action.clone()).await;
let uri_string = uri.to_string();
let expected = Response::from_ok(
1.into(),
json!([
{
"data": uri,
"edit": {
"changes": {
uri_string: [
{
"newText": " /// Add a brief description.\n /// \n /// ### Additional Information\n /// \n /// Provide information beyond the core purpose or functionality.\n /// \n /// ### Returns\n /// \n /// * [Empty] - Add description here\n /// \n /// ### Reverts\n /// \n /// * List any cases where the function will revert\n /// \n /// ### Number of Storage Accesses\n /// \n /// * Reads: `0`\n /// * Writes: `0`\n /// * Clears: `0`\n /// \n /// ### Examples\n /// \n /// ```sway\n /// let x = test_function();\n /// ```\n",
"range": {
"end": {
"character": 0,
"line": 10
},
"start": {
"character": 0,
"line": 10
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate a documentation template"
}
]),
);
assert_json_eq!(expected, response.ok().unwrap());
code_action
}
pub(crate) async fn code_action_struct_request(
service: &mut LspService<Backend>,
uri: &Url,
) -> Request {
let params = json!({
"textDocument": {
"uri": uri,
},
"range" : {
"start": {
"line": 19,
"character": 11
},
"end": {
"line": 19,
"character": 11
}
},
"context": {
"diagnostics": [],
"triggerKind": 2
}
});
let code_action = build_request_with_id("textDocument/codeAction", params, 1);
let response = call_request(service, code_action.clone()).await;
let uri_string = uri.to_string();
let expected = Response::from_ok(
1.into(),
json!([
{
"data": uri,
"edit": {
"changes": {
uri_string.clone(): [
{
"newText": "\nimpl Data {\n \n}\n",
"range": {
"end": {
"character": 0,
"line": 25
},
"start": {
"character": 0,
"line": 25
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate impl for `Data`"
},
{
"data": uri,
"edit": {
"changes": {
uri_string.clone(): [
{
"newText": "\nimpl Data {\n fn new(value: NumberOrString, address: u64) -> Self { Self { value, address } }\n}\n",
"range": {
"end": {
"character": 0,
"line": 25
},
"start": {
"character": 0,
"line": 25
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate `new`"
},
{
"data": uri,
"edit": {
"changes": {
uri_string: [
{
"newText": "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n", "range": {
"end": {
"character": 0,
"line": 19
},
"start": {
"character": 0,
"line": 19
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate a documentation template"
}
]),
);
assert_json_eq!(expected, response.ok().unwrap());
code_action
}
pub(crate) async fn code_action_struct_type_params_request(
service: &mut LspService<Backend>,
uri: &Url,
) -> Request {
let params = json!({
"textDocument": {
"uri": uri
},
"range": {
"start": {
"line": 4,
"character": 9
},
"end": {
"line": 4,
"character": 9
}
},
"context": {
"diagnostics": [],
"triggerKind": 2
}
});
let code_action = build_request_with_id("textDocument/codeAction", params, 1);
let response = call_request(service, code_action.clone()).await;
let uri_string = uri.to_string();
let expected = Response::from_ok(
1.into(),
json!([
{
"data": uri,
"edit": {
"changes": {
uri_string.clone(): [
{
"newText": "\nimpl<T> Data<T> {\n \n}\n",
"range": {
"end": {
"character": 0,
"line": 7
},
"start": {
"character": 0,
"line": 7
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate impl for `Data`"
},
{
"data": uri,
"disabled": {
"reason": "Struct Data already has a `new` function"
},
"edit": {
"changes": {
uri_string.clone(): [
{
"newText": " fn new(value: T) -> Self { Self { value } }\n",
"range": {
"end": {
"character": 0,
"line": 9
},
"start": {
"character": 0,
"line": 9
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate `new`"
},
{
"data": uri,
"edit": {
"changes": {
uri_string: [
{
"newText": "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n", "range": {
"end": {
"character": 0,
"line": 4
},
"start": {
"character": 0,
"line": 4
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate a documentation template"
}
]),
);
assert_json_eq!(expected, response.ok().unwrap());
code_action
}
pub(crate) async fn code_action_struct_existing_impl_request(
service: &mut LspService<Backend>,
uri: &Url,
) -> Request {
let params = json!({
"textDocument": {
"uri": uri
},
"range": {
"start": {
"line": 2,
"character": 7
},
"end": {
"line": 2,
"character": 7
}
},
"context": {
"diagnostics": [],
"triggerKind": 2
}
});
let code_action = build_request_with_id("textDocument/codeAction", params, 1);
let response = call_request(service, code_action.clone()).await;
let uri_string = uri.to_string();
let expected = Response::from_ok(
1.into(),
json!([
{
"data": uri,
"edit": {
"changes": {
uri_string.clone(): [
{
"newText": "\nimpl A {\n \n}\n",
"range": {
"end": {
"character": 0,
"line": 6
},
"start": {
"character": 0,
"line": 6
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate impl for `A`"
},
{
"data": uri,
"edit": {
"changes": {
uri_string.clone(): [
{
"newText": " fn new(a: u64, b: u64) -> Self { Self { a, b } }\n",
"range": {
"end": {
"character": 0,
"line": 8
},
"start": {
"character": 0,
"line": 8
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate `new`"
},
{
"data": uri,
"edit": {
"changes": {
uri_string: [
{
"newText": "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n", "range": {
"end": {
"character": 0,
"line": 2
},
"start": {
"character": 0,
"line": 2
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate a documentation template"
}
]),
);
assert_json_eq!(expected, response.ok().unwrap());
code_action
}