Skip to content

Commit

Permalink
Add code actions to generate doc comments (#4642)
Browse files Browse the repository at this point in the history
## Description

Closes #4571

Following the directory structure for code actions that was there
before, I've added new modules for:
- function decls
- enum variants
- storage fields
- configurable constants

Following FuelLabs/sway-standards#5, the doc
comment template is the same for all of these except for functions. For
functions, the template includes the argument names & types, return
value, and a generated example of usage which is just meant to be a
starting point for the comment author.

For the basic template that most of these use, I created
`BasicDocCommentCodeAction`. I also refactored some of the "generate
impl" code actions that were there before into a trait called
`GenerateImplCodeAction` to keep their common functionality separate
from the "generate doc comment" code actions.

I updated the existing code action tests for enums & structs, and added
a new one for functions. I didn't think it was necessary to test enum
variants, storage fields, etc. since the template and underlying code is
the same.

### Example generating docs for a function

![Jun-07-2023
17-01-17](https://github.com/FuelLabs/sway/assets/47993817/b645afd9-a474-450d-bb12-e69ed2ccaacf)

## 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.
- [ ] 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.

Co-authored-by: Joshua Batty <joshpbatty@gmail.com>
  • Loading branch information
sdankel and JoshuaBatty committed Jun 12, 2023
1 parent 2d23078 commit 733d9b5
Show file tree
Hide file tree
Showing 23 changed files with 732 additions and 125 deletions.
6 changes: 6 additions & 0 deletions sway-core/src/language/ty/declaration/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ impl TyEnumDecl {
}
}

impl Spanned for TyEnumVariant {
fn span(&self) -> Span {
self.span.clone()
}
}

#[derive(Debug, Clone)]
pub struct TyEnumVariant {
pub name: Ident,
Expand Down
6 changes: 6 additions & 0 deletions sway-core/src/language/ty/declaration/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ impl TyStorageDecl {
}
}

impl Spanned for TyStorageField {
fn span(&self) -> Span {
self.span.clone()
}
}

#[derive(Clone, Debug)]
pub struct TyStorageField {
pub name: Ident,
Expand Down
6 changes: 6 additions & 0 deletions sway-core/src/language/ty/declaration/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ impl TyStructDecl {
}
}

impl Spanned for TyStructField {
fn span(&self) -> Span {
self.span.clone()
}
}

#[derive(Debug, Clone)]
pub struct TyStructField {
pub name: Ident,
Expand Down
15 changes: 11 additions & 4 deletions sway-lsp/src/capabilities/code_actions/abi_decl/abi_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use sway_core::{
language::ty::{self, TyAbiDecl, TyFunctionParameter, TyTraitFn},
Engines,
};
use tower_lsp::lsp_types::Url;
use tower_lsp::lsp_types::{Range, Url};

use crate::capabilities::code_actions::{
CodeAction, CodeActionContext, CODE_ACTION_IMPL_TITLE, CONTRACT,
common::generate_impl::{GenerateImplCodeAction, CONTRACT},
CodeAction, CodeActionContext, CODE_ACTION_IMPL_TITLE,
};

pub(crate) struct AbiImplCodeAction<'a> {
Expand All @@ -14,6 +15,12 @@ pub(crate) struct AbiImplCodeAction<'a> {
uri: &'a Url,
}

impl<'a> GenerateImplCodeAction<'a, TyAbiDecl> for AbiImplCodeAction<'a> {
fn decl_name(&self) -> String {
self.decl.name.to_string()
}
}

impl<'a> CodeAction<'a, TyAbiDecl> for AbiImplCodeAction<'a> {
fn new(ctx: CodeActionContext<'a>, decl: &'a TyAbiDecl) -> Self {
Self {
Expand All @@ -35,8 +42,8 @@ impl<'a> CodeAction<'a, TyAbiDecl> for AbiImplCodeAction<'a> {
format!("{} `{}`", CODE_ACTION_IMPL_TITLE, self.decl_name())
}

fn decl_name(&self) -> String {
self.decl.name.to_string()
fn range(&self) -> Range {
self.range_after()
}

fn decl(&self) -> &TyAbiDecl {
Expand Down
89 changes: 89 additions & 0 deletions sway-lsp/src/capabilities/code_actions/common/generate_doc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use sway_core::{Engines, TypeId};
use sway_types::Spanned;
use tower_lsp::lsp_types::{Range, Url};

use crate::capabilities::code_actions::{CodeAction, CodeActionContext, CODE_ACTION_DOC_TITLE};

pub(crate) trait GenerateDocCodeAction<'a, T: Spanned>: CodeAction<'a, T> {
/// Returns a placeholder description as a vector of strings.
fn description_section(&self) -> Vec<String> {
vec!["Add a brief description.".to_string()]
}

/// Returns a placeholder information section as a vector of strings.
fn info_section(&self) -> Vec<String> {
vec![
String::new(),
"### Additional Information".to_string(),
String::new(),
"Provide information beyond the core purpose or functionality.".to_string(),
]
}

fn default_template(&self) -> String {
let lines: Vec<String> = vec![self.description_section(), self.info_section()]
.into_iter()
.flatten()
.collect();
self.format_lines(lines)
}

/// Formats a vector of lines into a doc comment [String].
fn format_lines(&self, lines: Vec<String>) -> String {
lines
.iter()
.map(|line| format!("{}/// {}\n", self.indentation(), line))
.collect()
}

/// Formats a list item with a name and type into a [String].
fn formatted_list_item(
&self,
engines: &'a Engines,
name: Option<String>,
type_id: TypeId,
) -> String {
let name_string = match name {
Some(name) => format!("`{}`: ", name),
None => String::new(),
};
let type_string = match engines.te().get(type_id).is_unit() {
true => "()".to_string(),
false => format!("[{}]", engines.help_out(type_id)),
};
format!("* {name_string}{type_string} - Add description here",)
}
}

pub struct BasicDocCommentCodeAction<'a, T: Spanned> {
decl: &'a T,
uri: &'a Url,
}

impl<'a, T: Spanned> GenerateDocCodeAction<'a, T> for BasicDocCommentCodeAction<'a, T> {}

impl<'a, T: Spanned> CodeAction<'a, T> for BasicDocCommentCodeAction<'a, T> {
fn new(ctx: CodeActionContext<'a>, decl: &'a T) -> Self {
Self { decl, uri: ctx.uri }
}

fn new_text(&self) -> String {
self.default_template()
}

fn range(&self) -> Range {
self.range_before()
}

fn title(&self) -> String {
CODE_ACTION_DOC_TITLE.to_string()
}

fn decl(&self) -> &T {
self.decl
}

fn uri(&self) -> &Url {
self.uri
}
}
103 changes: 103 additions & 0 deletions sway-lsp/src/capabilities/code_actions/common/generate_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use sway_core::{
transform::{AttributeKind, AttributesMap},
TypeParameter,
};
use sway_types::Spanned;

use crate::capabilities::code_actions::CodeAction;

pub(crate) const CONTRACT: &str = "Contract";
pub(crate) const TAB: &str = " ";

pub(crate) trait GenerateImplCodeAction<'a, T: Spanned>: CodeAction<'a, T> {
/// Returns a [String] holding the name of the declaration.
fn decl_name(&self) -> String;

/// Returns an optional [String] of the type parameters for the given [TypeParameter] vector.
fn type_param_string(&self, type_params: &Vec<TypeParameter>) -> Option<String> {
if type_params.is_empty() {
None
} else {
Some(
type_params
.iter()
.map(|param| param.name_ident.to_string())
.collect::<Vec<_>>()
.join(", "),
)
}
}

/// Returns a [String] of a generated impl with the optional `for <for_name>` signature.
/// Can be used for both ABI and Struct impls.
fn impl_string(
&self,
type_params: Option<String>,
body: String,
for_name: Option<String>,
) -> String {
let for_string = match for_name {
Some(name) => format!(" for {name}"),
None => "".to_string(),
};
let type_param_string = match type_params {
Some(params) => format!("<{params}>"),
None => "".to_string(),
};
format!(
"\nimpl{} {}{}{} {{{}}}\n",
type_param_string,
self.decl_name(),
type_param_string,
for_string,
body
)
}

/// Returns a [String] of a an attribute map, optionally excluding comments.
fn attribute_string(&self, attr_map: &AttributesMap, include_comments: bool) -> String {
let attr_string = attr_map
.iter()
.map(|(kind, attrs)| {
attrs
.iter()
.filter_map(|attr| match kind {
AttributeKind::DocComment { .. } => {
if include_comments {
return Some(format!("{}{}", TAB, attr.span.as_str()));
}
None
}
_ => Some(format!("{}{}", TAB, attr.span.as_str())),
})
.collect::<Vec<String>>()
.join("\n")
})
.collect::<Vec<String>>()
.join("\n");
let attribute_padding = match attr_string.len() > 1 {
true => "\n",
false => "",
};
format!("{attr_string}{attribute_padding}")
}

/// Returns a [String] of a generated function signature.
fn fn_signature_string(
&self,
fn_name: String,
params_string: String,
attr_map: &AttributesMap,
return_type_string: String,
body: Option<String>,
) -> String {
let attribute_string = self.attribute_string(attr_map, false);
let body_string = match body {
Some(body) => format!(" {body} "),
None => String::new(),
};
format!(
"{attribute_string}{TAB}fn {fn_name}({params_string}){return_type_string} {{{body_string}}}",
)
}
}
2 changes: 2 additions & 0 deletions sway-lsp/src/capabilities/code_actions/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod generate_doc;
pub mod generate_impl;
12 changes: 12 additions & 0 deletions sway-lsp/src/capabilities/code_actions/constant_decl/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::capabilities::code_actions::{CodeAction, CodeActionContext};
use sway_core::language::ty;
use tower_lsp::lsp_types::CodeActionOrCommand;

use super::common::generate_doc::BasicDocCommentCodeAction;

pub(crate) fn code_actions(
decl: &ty::TyConstantDecl,
ctx: CodeActionContext,
) -> Option<Vec<CodeActionOrCommand>> {
Some(vec![BasicDocCommentCodeAction::new(ctx, decl).code_action()])
}
39 changes: 39 additions & 0 deletions sway-lsp/src/capabilities/code_actions/enum_decl/doc_comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use crate::capabilities::code_actions::{
common::generate_doc::GenerateDocCodeAction, CodeAction, CodeActionContext,
CODE_ACTION_DOC_TITLE,
};
use sway_core::language::ty::TyEnumDecl;
use tower_lsp::lsp_types::{Range, Url};

pub(crate) struct DocCommentCodeAction<'a> {
decl: &'a TyEnumDecl,
uri: &'a Url,
}

impl<'a> GenerateDocCodeAction<'a, TyEnumDecl> for DocCommentCodeAction<'a> {}

impl<'a> CodeAction<'a, TyEnumDecl> for DocCommentCodeAction<'a> {
fn new(ctx: CodeActionContext<'a>, decl: &'a TyEnumDecl) -> Self {
Self { decl, uri: ctx.uri }
}

fn new_text(&self) -> String {
self.default_template()
}

fn range(&self) -> Range {
self.range_before()
}

fn title(&self) -> String {
CODE_ACTION_DOC_TITLE.to_string()
}

fn decl(&self) -> &TyEnumDecl {
self.decl
}

fn uri(&self) -> &Url {
self.uri
}
}
47 changes: 47 additions & 0 deletions sway-lsp/src/capabilities/code_actions/enum_decl/enum_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::capabilities::code_actions::{
common::generate_impl::{GenerateImplCodeAction, TAB},
CodeAction, CodeActionContext, CODE_ACTION_IMPL_TITLE,
};
use sway_core::language::ty::TyEnumDecl;
use tower_lsp::lsp_types::{Range, Url};

pub(crate) struct EnumImplCodeAction<'a> {
decl: &'a TyEnumDecl,
uri: &'a Url,
}

impl<'a> GenerateImplCodeAction<'a, TyEnumDecl> for EnumImplCodeAction<'a> {
fn decl_name(&self) -> String {
self.decl.call_path.suffix.to_string()
}
}

impl<'a> CodeAction<'a, TyEnumDecl> for EnumImplCodeAction<'a> {
fn new(ctx: CodeActionContext<'a>, decl: &'a TyEnumDecl) -> Self {
Self { decl, uri: ctx.uri }
}

fn new_text(&self) -> String {
self.impl_string(
self.type_param_string(&self.decl.type_parameters),
format!("\n{TAB}\n"),
None,
)
}

fn title(&self) -> String {
format!("{} `{}`", CODE_ACTION_IMPL_TITLE, self.decl_name())
}

fn range(&self) -> Range {
self.range_after()
}

fn decl(&self) -> &TyEnumDecl {
self.decl
}

fn uri(&self) -> &Url {
self.uri
}
}
20 changes: 20 additions & 0 deletions sway-lsp/src/capabilities/code_actions/enum_decl/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
pub(crate) mod doc_comment;
pub(crate) mod enum_impl;

use self::enum_impl::EnumImplCodeAction;
use crate::capabilities::code_actions::{CodeAction, CodeActionContext};
use sway_core::{decl_engine::id::DeclId, language::ty};
use tower_lsp::lsp_types::CodeActionOrCommand;

use super::common::generate_doc::BasicDocCommentCodeAction;

pub(crate) fn code_actions(
decl_id: &DeclId<ty::TyEnumDecl>,
ctx: CodeActionContext,
) -> Option<Vec<CodeActionOrCommand>> {
let decl = ctx.engines.de().get_enum(decl_id);
Some(vec![
EnumImplCodeAction::new(ctx.clone(), &decl).code_action(),
BasicDocCommentCodeAction::new(ctx, &decl).code_action(),
])
}
Loading

0 comments on commit 733d9b5

Please sign in to comment.