diff options
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs')
-rw-r--r-- | src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs | 1328 |
1 files changed, 1328 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs new file mode 100644 index 000000000..c91141f8e --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs @@ -0,0 +1,1328 @@ +use hir::{AsAssocItem, HasVisibility, ModuleDef, Visibility}; +use ide_db::assists::{AssistId, AssistKind}; +use itertools::Itertools; +use stdx::{format_to, to_lower_snake_case}; +use syntax::{ + algo::skip_whitespace_token, + ast::{self, edit::IndentLevel, HasDocComments, HasName}, + match_ast, AstNode, AstToken, +}; + +use crate::assist_context::{AssistContext, Assists}; + +// Assist: generate_documentation_template +// +// Adds a documentation template above a function definition / declaration. +// +// ``` +// pub struct S; +// impl S { +// pub unsafe fn set_len$0(&mut self, len: usize) -> Result<(), std::io::Error> { +// /* ... */ +// } +// } +// ``` +// -> +// ``` +// pub struct S; +// impl S { +// /// Sets the length of this [`S`]. +// /// +// /// # Errors +// /// +// /// This function will return an error if . +// /// +// /// # Safety +// /// +// /// . +// pub unsafe fn set_len(&mut self, len: usize) -> Result<(), std::io::Error> { +// /* ... */ +// } +// } +// ``` +pub(crate) fn generate_documentation_template( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let name = ctx.find_node_at_offset::<ast::Name>()?; + let ast_func = name.syntax().parent().and_then(ast::Fn::cast)?; + if is_in_trait_impl(&ast_func, ctx) || ast_func.doc_comments().next().is_some() { + return None; + } + + let parent_syntax = ast_func.syntax(); + let text_range = parent_syntax.text_range(); + let indent_level = IndentLevel::from_node(parent_syntax); + + acc.add( + AssistId("generate_documentation_template", AssistKind::Generate), + "Generate a documentation template", + text_range, + |builder| { + // Introduction / short function description before the sections + let mut doc_lines = vec![introduction_builder(&ast_func, ctx).unwrap_or(".".into())]; + // Then come the sections + for section_builder in [panics_builder, errors_builder, safety_builder] { + if let Some(mut lines) = section_builder(&ast_func) { + doc_lines.push("".into()); + doc_lines.append(&mut lines); + } + } + builder.insert(text_range.start(), documentation_from_lines(doc_lines, indent_level)); + }, + ) +} + +// Assist: generate_doc_example +// +// Generates a rustdoc example when editing an item's documentation. +// +// ``` +// /// Adds two numbers.$0 +// pub fn add(a: i32, b: i32) -> i32 { a + b } +// ``` +// -> +// ``` +// /// Adds two numbers. +// /// +// /// # Examples +// /// +// /// ``` +// /// use test::add; +// /// +// /// assert_eq!(add(a, b), ); +// /// ``` +// pub fn add(a: i32, b: i32) -> i32 { a + b } +// ``` +pub(crate) fn generate_doc_example(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let tok: ast::Comment = ctx.find_token_at_offset()?; + let node = tok.syntax().parent()?; + let last_doc_token = + ast::AnyHasDocComments::cast(node.clone())?.doc_comments().last()?.syntax().clone(); + let next_token = skip_whitespace_token(last_doc_token.next_token()?, syntax::Direction::Next)?; + + let example = match_ast! { + match node { + ast::Fn(it) => make_example_for_fn(&it, ctx)?, + _ => return None, + } + }; + + let mut lines = string_vec_from(&["", "# Examples", "", "```"]); + lines.extend(example.lines().map(String::from)); + lines.push("```".into()); + let indent_level = IndentLevel::from_node(&node); + + acc.add( + AssistId("generate_doc_example", AssistKind::Generate), + "Generate a documentation example", + node.text_range(), + |builder| { + builder.insert( + next_token.text_range().start(), + documentation_from_lines(lines, indent_level), + ); + }, + ) +} + +fn make_example_for_fn(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> { + if !is_public(ast_func, ctx)? { + // Doctests for private items can't actually name the item, so they're pretty useless. + return None; + } + + if is_in_trait_def(ast_func, ctx) { + // This is not yet implemented. + return None; + } + + let mut example = String::new(); + + let is_unsafe = ast_func.unsafe_token().is_some(); + let param_list = ast_func.param_list()?; + let ref_mut_params = ref_mut_params(¶m_list); + let self_name = self_name(ast_func); + + format_to!(example, "use {};\n\n", build_path(ast_func, ctx)?); + if let Some(self_name) = &self_name { + if let Some(mtbl) = is_ref_mut_self(ast_func) { + let mtbl = if mtbl == true { " mut" } else { "" }; + format_to!(example, "let{} {} = ;\n", mtbl, self_name); + } + } + for param_name in &ref_mut_params { + format_to!(example, "let mut {} = ;\n", param_name); + } + // Call the function, check result + let function_call = function_call(ast_func, ¶m_list, self_name.as_deref(), is_unsafe)?; + if returns_a_value(ast_func, ctx) { + if count_parameters(¶m_list) < 3 { + format_to!(example, "assert_eq!({}, );\n", function_call); + } else { + format_to!(example, "let result = {};\n", function_call); + example.push_str("assert_eq!(result, );\n"); + } + } else { + format_to!(example, "{};\n", function_call); + } + // Check the mutated values + if is_ref_mut_self(ast_func) == Some(true) { + format_to!(example, "assert_eq!({}, );", self_name?); + } + for param_name in &ref_mut_params { + format_to!(example, "assert_eq!({}, );", param_name); + } + Some(example) +} + +fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> { + let hir_func = ctx.sema.to_def(ast_func)?; + let container = hir_func.as_assoc_item(ctx.db())?.container(ctx.db()); + if let hir::AssocItemContainer::Impl(imp) = container { + let ret_ty = hir_func.ret_type(ctx.db()); + let self_ty = imp.self_ty(ctx.db()); + let name = ast_func.name()?.to_string(); + let linkable_self_ty = self_type_without_lifetimes(ast_func); + let linkable_self_ty = linkable_self_ty.as_deref(); + + let intro_for_new = || { + let is_new = name == "new"; + if is_new && ret_ty == self_ty { + Some(format!("Creates a new [`{}`].", linkable_self_ty?)) + } else { + None + } + }; + + let intro_for_getter = || match ( + hir_func.self_param(ctx.sema.db), + &*hir_func.params_without_self(ctx.sema.db), + ) { + (Some(self_param), []) if self_param.access(ctx.sema.db) != hir::Access::Owned => { + if name.starts_with("as_") || name.starts_with("to_") || name == "get" { + return None; + } + let mut what = name.trim_end_matches("_mut").replace('_', " "); + if what == "len" { + what = "length".into() + } + let reference = if ret_ty.is_mutable_reference() { + " a mutable reference to" + } else if ret_ty.is_reference() { + " a reference to" + } else { + "" + }; + Some(format!("Returns{reference} the {what} of this [`{}`].", linkable_self_ty?)) + } + _ => None, + }; + + let intro_for_setter = || { + if !name.starts_with("set_") { + return None; + } + + let mut what = name.trim_start_matches("set_").replace('_', " "); + if what == "len" { + what = "length".into() + }; + Some(format!("Sets the {what} of this [`{}`].", linkable_self_ty?)) + }; + + if let Some(intro) = intro_for_new() { + return Some(intro); + } + if let Some(intro) = intro_for_getter() { + return Some(intro); + } + if let Some(intro) = intro_for_setter() { + return Some(intro); + } + } + None +} + +/// Builds an optional `# Panics` section +fn panics_builder(ast_func: &ast::Fn) -> Option<Vec<String>> { + match can_panic(ast_func) { + Some(true) => Some(string_vec_from(&["# Panics", "", "Panics if ."])), + _ => None, + } +} + +/// Builds an optional `# Errors` section +fn errors_builder(ast_func: &ast::Fn) -> Option<Vec<String>> { + match return_type(ast_func)?.to_string().contains("Result") { + true => Some(string_vec_from(&["# Errors", "", "This function will return an error if ."])), + false => None, + } +} + +/// Builds an optional `# Safety` section +fn safety_builder(ast_func: &ast::Fn) -> Option<Vec<String>> { + let is_unsafe = ast_func.unsafe_token().is_some(); + match is_unsafe { + true => Some(string_vec_from(&["# Safety", "", "."])), + false => None, + } +} + +/// Checks if the function is public / exported +fn is_public(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<bool> { + let hir_func = ctx.sema.to_def(ast_func)?; + Some( + hir_func.visibility(ctx.db()) == Visibility::Public + && all_parent_mods_public(&hir_func, ctx), + ) +} + +/// Checks that all parent modules of the function are public / exported +fn all_parent_mods_public(hir_func: &hir::Function, ctx: &AssistContext<'_>) -> bool { + let mut module = hir_func.module(ctx.db()); + loop { + if let Some(parent) = module.parent(ctx.db()) { + match ModuleDef::from(module).visibility(ctx.db()) { + Visibility::Public => module = parent, + _ => break false, + } + } else { + break true; + } + } +} + +/// Returns the name of the current crate +fn crate_name(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> { + let krate = ctx.sema.scope(ast_func.syntax())?.krate(); + Some(krate.display_name(ctx.db())?.to_string()) +} + +/// `None` if function without a body; some bool to guess if function can panic +fn can_panic(ast_func: &ast::Fn) -> Option<bool> { + let body = ast_func.body()?.to_string(); + let can_panic = body.contains("panic!(") + // FIXME it would be better to not match `debug_assert*!` macro invocations + || body.contains("assert!(") + || body.contains(".unwrap()") + || body.contains(".expect("); + Some(can_panic) +} + +/// Helper function to get the name that should be given to `self` arguments +fn self_name(ast_func: &ast::Fn) -> Option<String> { + self_partial_type(ast_func).map(|name| to_lower_snake_case(&name)) +} + +/// Heper function to get the name of the type of `self` +fn self_type(ast_func: &ast::Fn) -> Option<ast::Type> { + ast_func.syntax().ancestors().find_map(ast::Impl::cast).and_then(|i| i.self_ty()) +} + +/// Output the real name of `Self` like `MyType<T>`, without the lifetimes. +fn self_type_without_lifetimes(ast_func: &ast::Fn) -> Option<String> { + let path_segment = match self_type(ast_func)? { + ast::Type::PathType(path_type) => path_type.path()?.segment()?, + _ => return None, + }; + let mut name = path_segment.name_ref()?.to_string(); + let generics = path_segment.generic_arg_list().into_iter().flat_map(|list| { + list.generic_args() + .filter(|generic| matches!(generic, ast::GenericArg::TypeArg(_))) + .map(|generic| generic.to_string()) + }); + let generics: String = generics.format(", ").to_string(); + if !generics.is_empty() { + name.push('<'); + name.push_str(&generics); + name.push('>'); + } + Some(name) +} + +/// Heper function to get the name of the type of `self` without generic arguments +fn self_partial_type(ast_func: &ast::Fn) -> Option<String> { + let mut self_type = self_type(ast_func)?.to_string(); + if let Some(idx) = self_type.find(|c| ['<', ' '].contains(&c)) { + self_type.truncate(idx); + } + Some(self_type) +} + +/// Helper function to determine if the function is in a trait implementation +fn is_in_trait_impl(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> bool { + ctx.sema + .to_def(ast_func) + .and_then(|hir_func| hir_func.as_assoc_item(ctx.db())) + .and_then(|assoc_item| assoc_item.containing_trait_impl(ctx.db())) + .is_some() +} + +/// Helper function to determine if the function definition is in a trait definition +fn is_in_trait_def(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> bool { + ctx.sema + .to_def(ast_func) + .and_then(|hir_func| hir_func.as_assoc_item(ctx.db())) + .and_then(|assoc_item| assoc_item.containing_trait(ctx.db())) + .is_some() +} + +/// Returns `None` if no `self` at all, `Some(true)` if there is `&mut self` else `Some(false)` +fn is_ref_mut_self(ast_func: &ast::Fn) -> Option<bool> { + let self_param = ast_func.param_list()?.self_param()?; + Some(self_param.mut_token().is_some() && self_param.amp_token().is_some()) +} + +/// Helper function to determine if a parameter is `&mut` +fn is_a_ref_mut_param(param: &ast::Param) -> bool { + match param.ty() { + Some(ast::Type::RefType(param_ref)) => param_ref.mut_token().is_some(), + _ => false, + } +} + +/// Helper function to build the list of `&mut` parameters +fn ref_mut_params(param_list: &ast::ParamList) -> Vec<String> { + param_list + .params() + .filter_map(|param| match is_a_ref_mut_param(¶m) { + // Maybe better filter the param name (to do this maybe extract a function from + // `arguments_from_params`?) in case of a `mut a: &mut T`. Anyway managing most (not + // all) cases might be enough, the goal is just to produce a template. + true => Some(param.pat()?.to_string()), + false => None, + }) + .collect() +} + +/// Helper function to build the comma-separated list of arguments of the function +fn arguments_from_params(param_list: &ast::ParamList) -> String { + let args_iter = param_list.params().map(|param| match param.pat() { + // To avoid `mut` in the function call (which would be a nonsense), `Pat` should not be + // written as is so its variants must be managed independently. Other variants (for + // instance `TuplePat`) could be managed later. + Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() { + Some(name) => match is_a_ref_mut_param(¶m) { + true => format!("&mut {}", name), + false => name.to_string(), + }, + None => "_".to_string(), + }, + _ => "_".to_string(), + }); + args_iter.format(", ").to_string() +} + +/// Helper function to build a function call. `None` if expected `self_name` was not provided +fn function_call( + ast_func: &ast::Fn, + param_list: &ast::ParamList, + self_name: Option<&str>, + is_unsafe: bool, +) -> Option<String> { + let name = ast_func.name()?; + let arguments = arguments_from_params(param_list); + let function_call = if param_list.self_param().is_some() { + format!("{}.{}({})", self_name?, name, arguments) + } else if let Some(implementation) = self_partial_type(ast_func) { + format!("{}::{}({})", implementation, name, arguments) + } else { + format!("{}({})", name, arguments) + }; + match is_unsafe { + true => Some(format!("unsafe {{ {} }}", function_call)), + false => Some(function_call), + } +} + +/// Helper function to count the parameters including `self` +fn count_parameters(param_list: &ast::ParamList) -> usize { + param_list.params().count() + if param_list.self_param().is_some() { 1 } else { 0 } +} + +/// Helper function to transform lines of documentation into a Rust code documentation +fn documentation_from_lines(doc_lines: Vec<String>, indent_level: IndentLevel) -> String { + let mut result = String::new(); + for doc_line in doc_lines { + result.push_str("///"); + if !doc_line.is_empty() { + result.push(' '); + result.push_str(&doc_line); + } + result.push('\n'); + result.push_str(&indent_level.to_string()); + } + result +} + +/// Helper function to transform an array of borrowed strings to an owned `Vec<String>` +fn string_vec_from(string_array: &[&str]) -> Vec<String> { + string_array.iter().map(|&s| s.to_owned()).collect() +} + +/// Helper function to build the path of the module in the which is the node +fn build_path(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> { + let crate_name = crate_name(ast_func, ctx)?; + let leaf = self_partial_type(ast_func) + .or_else(|| ast_func.name().map(|n| n.to_string())) + .unwrap_or_else(|| "*".into()); + let module_def: ModuleDef = ctx.sema.to_def(ast_func)?.module(ctx.db()).into(); + match module_def.canonical_path(ctx.db()) { + Some(path) => Some(format!("{}::{}::{}", crate_name, path, leaf)), + None => Some(format!("{}::{}", crate_name, leaf)), + } +} + +/// Helper function to get the return type of a function +fn return_type(ast_func: &ast::Fn) -> Option<ast::Type> { + ast_func.ret_type()?.ty() +} + +/// Helper function to determine if the function returns some data +fn returns_a_value(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> bool { + ctx.sema + .to_def(ast_func) + .map(|hir_func| hir_func.ret_type(ctx.db())) + .map(|ret_ty| !ret_ty.is_unit() && !ret_ty.is_never()) + .unwrap_or(false) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn not_applicable_on_function_calls() { + check_assist_not_applicable( + generate_documentation_template, + r#" +fn hello_world() {} +fn calls_hello_world() { + hello_world$0(); +} +"#, + ) + } + + #[test] + fn not_applicable_in_trait_impl() { + check_assist_not_applicable( + generate_documentation_template, + r#" +trait MyTrait {} +struct MyStruct; +impl MyTrait for MyStruct { + fn hello_world$0(); +} +"#, + ) + } + + #[test] + fn not_applicable_if_function_already_documented() { + check_assist_not_applicable( + generate_documentation_template, + r#" +/// Some documentation here +pub fn $0documented_function() {} +"#, + ); + } + + #[test] + fn supports_noop_function() { + check_assist( + generate_documentation_template, + r#" +pub fn no$0op() {} +"#, + r#" +/// . +pub fn noop() {} +"#, + ); + } + + #[test] + fn is_applicable_if_function_is_private() { + check_assist( + generate_documentation_template, + r#" +fn priv$0ate() {} +"#, + r#" +/// . +fn private() {} +"#, + ); + } + + #[test] + fn no_doc_example_for_private_fn() { + check_assist_not_applicable( + generate_doc_example, + r#" +///$0 +fn private() {} +"#, + ); + } + + #[test] + fn supports_a_parameter() { + check_assist( + generate_doc_example, + r#" +/// $0. +pub fn noop_with_param(_a: i32) {} +"#, + r#" +/// . +/// +/// # Examples +/// +/// ``` +/// use test::noop_with_param; +/// +/// noop_with_param(_a); +/// ``` +pub fn noop_with_param(_a: i32) {} +"#, + ); + } + + #[test] + fn detects_unsafe_function() { + check_assist( + generate_documentation_template, + r#" +pub unsafe fn no$0op_unsafe() {} +"#, + r#" +/// . +/// +/// # Safety +/// +/// . +pub unsafe fn noop_unsafe() {} +"#, + ); + check_assist( + generate_doc_example, + r#" +/// . +/// +/// # Safety$0 +/// +/// . +pub unsafe fn noop_unsafe() {} +"#, + r#" +/// . +/// +/// # Safety +/// +/// . +/// +/// # Examples +/// +/// ``` +/// use test::noop_unsafe; +/// +/// unsafe { noop_unsafe() }; +/// ``` +pub unsafe fn noop_unsafe() {} +"#, + ); + } + + #[test] + fn guesses_panic_macro_can_panic() { + check_assist( + generate_documentation_template, + r#" +pub fn panic$0s_if(a: bool) { + if a { + panic!(); + } +} +"#, + r#" +/// . +/// +/// # Panics +/// +/// Panics if . +pub fn panics_if(a: bool) { + if a { + panic!(); + } +} +"#, + ); + } + + #[test] + fn guesses_assert_macro_can_panic() { + check_assist( + generate_documentation_template, + r#" +pub fn $0panics_if_not(a: bool) { + assert!(a == true); +} +"#, + r#" +/// . +/// +/// # Panics +/// +/// Panics if . +pub fn panics_if_not(a: bool) { + assert!(a == true); +} +"#, + ); + } + + #[test] + fn guesses_unwrap_can_panic() { + check_assist( + generate_documentation_template, + r#" +pub fn $0panics_if_none(a: Option<()>) { + a.unwrap(); +} +"#, + r#" +/// . +/// +/// # Panics +/// +/// Panics if . +pub fn panics_if_none(a: Option<()>) { + a.unwrap(); +} +"#, + ); + } + + #[test] + fn guesses_expect_can_panic() { + check_assist( + generate_documentation_template, + r#" +pub fn $0panics_if_none2(a: Option<()>) { + a.expect("Bouh!"); +} +"#, + r#" +/// . +/// +/// # Panics +/// +/// Panics if . +pub fn panics_if_none2(a: Option<()>) { + a.expect("Bouh!"); +} +"#, + ); + } + + #[test] + fn checks_output_in_example() { + check_assist( + generate_doc_example, + r#" +///$0 +pub fn returns_a_value$0() -> i32 { + 0 +} +"#, + r#" +/// +/// +/// # Examples +/// +/// ``` +/// use test::returns_a_value; +/// +/// assert_eq!(returns_a_value(), ); +/// ``` +pub fn returns_a_value() -> i32 { + 0 +} +"#, + ); + } + + #[test] + fn detects_result_output() { + check_assist( + generate_documentation_template, + r#" +pub fn returns_a_result$0() -> Result<i32, std::io::Error> { + Ok(0) +} +"#, + r#" +/// . +/// +/// # Errors +/// +/// This function will return an error if . +pub fn returns_a_result() -> Result<i32, std::io::Error> { + Ok(0) +} +"#, + ); + } + + #[test] + fn checks_ref_mut_in_example() { + check_assist( + generate_doc_example, + r#" +///$0 +pub fn modifies_a_value$0(a: &mut i32) { + *a = 0; +} +"#, + r#" +/// +/// +/// # Examples +/// +/// ``` +/// use test::modifies_a_value; +/// +/// let mut a = ; +/// modifies_a_value(&mut a); +/// assert_eq!(a, ); +/// ``` +pub fn modifies_a_value(a: &mut i32) { + *a = 0; +} +"#, + ); + } + + #[test] + fn stores_result_if_at_least_3_params() { + check_assist( + generate_doc_example, + r#" +///$0 +pub fn sum3$0(a: i32, b: i32, c: i32) -> i32 { + a + b + c +} +"#, + r#" +/// +/// +/// # Examples +/// +/// ``` +/// use test::sum3; +/// +/// let result = sum3(a, b, c); +/// assert_eq!(result, ); +/// ``` +pub fn sum3(a: i32, b: i32, c: i32) -> i32 { + a + b + c +} +"#, + ); + } + + #[test] + fn supports_fn_in_mods() { + check_assist( + generate_doc_example, + r#" +pub mod a { + pub mod b { + ///$0 + pub fn noop() {} + } +} +"#, + r#" +pub mod a { + pub mod b { + /// + /// + /// # Examples + /// + /// ``` + /// use test::a::b::noop; + /// + /// noop(); + /// ``` + pub fn noop() {} + } +} +"#, + ); + } + + #[test] + fn supports_fn_in_impl() { + check_assist( + generate_doc_example, + r#" +pub struct MyStruct; +impl MyStruct { + ///$0 + pub fn noop() {} +} +"#, + r#" +pub struct MyStruct; +impl MyStruct { + /// + /// + /// # Examples + /// + /// ``` + /// use test::MyStruct; + /// + /// MyStruct::noop(); + /// ``` + pub fn noop() {} +} +"#, + ); + } + + #[test] + fn supports_unsafe_fn_in_trait() { + check_assist( + generate_documentation_template, + r#" +pub trait MyTrait { + unsafe fn unsafe_funct$0ion_trait(); +} +"#, + r#" +pub trait MyTrait { + /// . + /// + /// # Safety + /// + /// . + unsafe fn unsafe_function_trait(); +} +"#, + ); + } + + #[test] + fn supports_fn_in_trait_with_default_panicking() { + check_assist( + generate_documentation_template, + r#" +pub trait MyTrait { + fn function_trait_with_$0default_panicking() { + panic!() + } +} +"#, + r#" +pub trait MyTrait { + /// . + /// + /// # Panics + /// + /// Panics if . + fn function_trait_with_default_panicking() { + panic!() + } +} +"#, + ); + } + + #[test] + fn supports_fn_in_trait_returning_result() { + check_assist( + generate_documentation_template, + r#" +pub trait MyTrait { + fn function_tr$0ait_returning_result() -> Result<(), std::io::Error>; +} +"#, + r#" +pub trait MyTrait { + /// . + /// + /// # Errors + /// + /// This function will return an error if . + fn function_trait_returning_result() -> Result<(), std::io::Error>; +} +"#, + ); + } + + #[test] + fn detects_new() { + check_assist( + generate_documentation_template, + r#" +pub struct String(u8); +impl String { + pub fn new$0(x: u8) -> String { + String(x) + } +} +"#, + r#" +pub struct String(u8); +impl String { + /// Creates a new [`String`]. + pub fn new(x: u8) -> String { + String(x) + } +} +"#, + ); + check_assist( + generate_documentation_template, + r#" +#[derive(Debug, PartialEq)] +pub struct MyGenericStruct<T> { + pub x: T, +} +impl<T> MyGenericStruct<T> { + pub fn new$0(x: T) -> MyGenericStruct<T> { + MyGenericStruct { x } + } +} +"#, + r#" +#[derive(Debug, PartialEq)] +pub struct MyGenericStruct<T> { + pub x: T, +} +impl<T> MyGenericStruct<T> { + /// Creates a new [`MyGenericStruct<T>`]. + pub fn new(x: T) -> MyGenericStruct<T> { + MyGenericStruct { x } + } +} +"#, + ); + } + + #[test] + fn removes_one_lifetime_from_description() { + check_assist( + generate_documentation_template, + r#" +#[derive(Debug, PartialEq)] +pub struct MyGenericStruct<'a, T> { + pub x: &'a T, +} +impl<'a, T> MyGenericStruct<'a, T> { + pub fn new$0(x: &'a T) -> Self { + MyGenericStruct { x } + } +} +"#, + r#" +#[derive(Debug, PartialEq)] +pub struct MyGenericStruct<'a, T> { + pub x: &'a T, +} +impl<'a, T> MyGenericStruct<'a, T> { + /// Creates a new [`MyGenericStruct<T>`]. + pub fn new(x: &'a T) -> Self { + MyGenericStruct { x } + } +} +"#, + ); + } + + #[test] + fn removes_all_lifetimes_from_description() { + check_assist( + generate_documentation_template, + r#" +#[derive(Debug, PartialEq)] +pub struct MyGenericStruct<'a, 'b, T> { + pub x: &'a T, + pub y: &'b T, +} +impl<'a, 'b, T> MyGenericStruct<'a, 'b, T> { + pub fn new$0(x: &'a T, y: &'b T) -> Self { + MyGenericStruct { x, y } + } +} +"#, + r#" +#[derive(Debug, PartialEq)] +pub struct MyGenericStruct<'a, 'b, T> { + pub x: &'a T, + pub y: &'b T, +} +impl<'a, 'b, T> MyGenericStruct<'a, 'b, T> { + /// Creates a new [`MyGenericStruct<T>`]. + pub fn new(x: &'a T, y: &'b T) -> Self { + MyGenericStruct { x, y } + } +} +"#, + ); + } + + #[test] + fn removes_all_lifetimes_and_brackets_from_description() { + check_assist( + generate_documentation_template, + r#" +#[derive(Debug, PartialEq)] +pub struct MyGenericStruct<'a, 'b> { + pub x: &'a usize, + pub y: &'b usize, +} +impl<'a, 'b> MyGenericStruct<'a, 'b> { + pub fn new$0(x: &'a usize, y: &'b usize) -> Self { + MyGenericStruct { x, y } + } +} +"#, + r#" +#[derive(Debug, PartialEq)] +pub struct MyGenericStruct<'a, 'b> { + pub x: &'a usize, + pub y: &'b usize, +} +impl<'a, 'b> MyGenericStruct<'a, 'b> { + /// Creates a new [`MyGenericStruct`]. + pub fn new(x: &'a usize, y: &'b usize) -> Self { + MyGenericStruct { x, y } + } +} +"#, + ); + } + + #[test] + fn detects_new_with_self() { + check_assist( + generate_documentation_template, + r#" +#[derive(Debug, PartialEq)] +pub struct MyGenericStruct2<T> { + pub x: T, +} +impl<T> MyGenericStruct2<T> { + pub fn new$0(x: T) -> Self { + MyGenericStruct2 { x } + } +} +"#, + r#" +#[derive(Debug, PartialEq)] +pub struct MyGenericStruct2<T> { + pub x: T, +} +impl<T> MyGenericStruct2<T> { + /// Creates a new [`MyGenericStruct2<T>`]. + pub fn new(x: T) -> Self { + MyGenericStruct2 { x } + } +} +"#, + ); + } + + #[test] + fn supports_method_call() { + check_assist( + generate_doc_example, + r#" +impl<T> MyGenericStruct<T> { + ///$0 + pub fn consume(self) {} +} +"#, + r#" +impl<T> MyGenericStruct<T> { + /// + /// + /// # Examples + /// + /// ``` + /// use test::MyGenericStruct; + /// + /// let my_generic_struct = ; + /// my_generic_struct.consume(); + /// ``` + pub fn consume(self) {} +} +"#, + ); + } + + #[test] + fn checks_modified_self_param() { + check_assist( + generate_doc_example, + r#" +impl<T> MyGenericStruct<T> { + ///$0 + pub fn modify(&mut self, new_value: T) { + self.x = new_value; + } +} +"#, + r#" +impl<T> MyGenericStruct<T> { + /// + /// + /// # Examples + /// + /// ``` + /// use test::MyGenericStruct; + /// + /// let mut my_generic_struct = ; + /// my_generic_struct.modify(new_value); + /// assert_eq!(my_generic_struct, ); + /// ``` + pub fn modify(&mut self, new_value: T) { + self.x = new_value; + } +} +"#, + ); + } + + #[test] + fn generates_intro_for_getters() { + check_assist( + generate_documentation_template, + r#" +pub struct S; +impl S { + pub fn speed$0(&self) -> f32 { 0.0 } +} +"#, + r#" +pub struct S; +impl S { + /// Returns the speed of this [`S`]. + pub fn speed(&self) -> f32 { 0.0 } +} +"#, + ); + check_assist( + generate_documentation_template, + r#" +pub struct S; +impl S { + pub fn data$0(&self) -> &[u8] { &[] } +} +"#, + r#" +pub struct S; +impl S { + /// Returns a reference to the data of this [`S`]. + pub fn data(&self) -> &[u8] { &[] } +} +"#, + ); + check_assist( + generate_documentation_template, + r#" +pub struct S; +impl S { + pub fn data$0(&mut self) -> &mut [u8] { &mut [] } +} +"#, + r#" +pub struct S; +impl S { + /// Returns a mutable reference to the data of this [`S`]. + pub fn data(&mut self) -> &mut [u8] { &mut [] } +} +"#, + ); + check_assist( + generate_documentation_template, + r#" +pub struct S; +impl S { + pub fn data_mut$0(&mut self) -> &mut [u8] { &mut [] } +} +"#, + r#" +pub struct S; +impl S { + /// Returns a mutable reference to the data of this [`S`]. + pub fn data_mut(&mut self) -> &mut [u8] { &mut [] } +} +"#, + ); + } + + #[test] + fn no_getter_intro_for_prefixed_methods() { + check_assist( + generate_documentation_template, + r#" +pub struct S; +impl S { + pub fn as_bytes$0(&self) -> &[u8] { &[] } +} +"#, + r#" +pub struct S; +impl S { + /// . + pub fn as_bytes(&self) -> &[u8] { &[] } +} +"#, + ); + } + + #[test] + fn generates_intro_for_setters() { + check_assist( + generate_documentation_template, + r#" +pub struct S; +impl S { + pub fn set_data$0(&mut self, data: Vec<u8>) {} +} +"#, + r#" +pub struct S; +impl S { + /// Sets the data of this [`S`]. + pub fn set_data(&mut self, data: Vec<u8>) {} +} +"#, + ); + check_assist( + generate_documentation_template, + r#" +pub struct S; +impl S { + pub fn set_domain_name$0(&mut self, name: String) {} +} +"#, + r#" +pub struct S; +impl S { + /// Sets the domain name of this [`S`]. + pub fn set_domain_name(&mut self, name: String) {} +} +"#, + ); + } +} |