--- /dev/null
+use ide_db::assists::{AssistId, AssistKind};
+use stdx::to_lower_snake_case;
+use syntax::{
+ ast::{self, edit::IndentLevel, HasDocComments, HasName},
+ AstNode,
+};
+
+use crate::assist_context::{AssistContext, Assists};
+
+/// Assist: generate_documentation_template
+///
+/// Adds a documentation template above a function definition / declaration
+///
+/// ```
+/// fn my_func(a: i32, b: i32) -> Result<(), std::io::Error> {
+/// unimplemented!()
+/// }
+/// ```
+/// ->
+/// ```
+/// /// .
+/// ///
+/// /// # Examples
+/// ///
+/// /// ```rust
+/// /// use my_crate::my_func;
+/// ///
+/// /// let result = my_func(a, b);
+/// /// assert_eq!(result, );
+/// /// ```
+/// ///
+/// /// # Errors
+/// ///
+/// /// This function will return an error if .
+/// fn my_func(a: i32, b: i32) -> Result<(), std::io::Error> {
+/// unimplemented!()
+/// }
+/// ```
+pub(crate) fn generate_documentation_template(
+ acc: &mut Assists,
+ ctx: &AssistContext,
+) -> Option<()> {
+ let name = ctx.find_node_at_offset::<ast::Name>()?;
+ let ast_func = ast::Fn::cast(name.syntax().parent()?)?;
+ if is_in_trait_impl(&ast_func) {
+ return None;
+ }
+ // TODO disable at least examples if function not public, as the example will fail to build on
+ // `cargo test`. What is the exact criteria of `pub`ness? All parent modules must be `pub`, for
+ // `impl { fn }` both `fn` and `struct`* must be public.
+ //
+ // What about `pub(crate)`?
+ //
+ // *: Seems complex but maybe ignoring this criteria can be ignored.
+
+ let parent_syntax = ast_func.syntax();
+ let text_range = parent_syntax.text_range();
+ let indent_level = IndentLevel::from_node(&parent_syntax);
+ let krate_name =
+ ctx.sema.scope(&parent_syntax).module()?.krate().display_name(ctx.db())?.to_string();
+
+ acc.add(
+ AssistId("generate_documentation_template", AssistKind::Generate),
+ "Generate a documentation template",
+ text_range,
+ |builder| {
+ let mut doc_lines = Vec::new();
+ // Introduction / short function description before the sections
+ doc_lines.push(introduction_builder(&ast_func));
+ // Then come the sections
+ if let Some(mut lines) = examples_builder(&ast_func, krate_name) {
+ doc_lines.push("".into());
+ doc_lines.append(&mut lines);
+ }
+ 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);
+ }
+ }
+ if ast_func.doc_comments().next().is_some() {
+ doc_lines.push("--- OLD VERSION BELOW ---".into());
+ }
+ builder.insert(text_range.start(), documentation_from_lines(doc_lines, indent_level));
+ },
+ )
+}
+
+/// Builds an introduction, trying to be smart if the function is `::new()`
+fn introduction_builder(ast_func: &ast::Fn) -> String {
+ let is_new = ast_func.name().map(|name| &name.to_string() == "new").unwrap_or(false);
+ if is_new {
+ let ret_type = return_type(ast_func).map(|ret_type| ret_type.to_string());
+ let self_type = self_type(ast_func);
+ if ret_type.as_deref() == Some("Self") || ret_type == self_type {
+ if let Some(self_type) = self_type {
+ return format!("Creates a new [`{}`].", self_type);
+ }
+ }
+ }
+ ".".into()
+}
+
+/// Builds an `# Examples` section. An option is returned to be able to manage an error in the AST.
+fn examples_builder(ast_func: &ast::Fn, krate_name: String) -> Option<Vec<String>> {
+ let (no_panic_ex, panic_ex) = if is_in_trait_def(ast_func) {
+ let message = "// Example template not implemented for trait functions";
+ (Some(vec![message.into()]), Some(vec![message.into()]))
+ } else {
+ let panic_ex = match can_panic(ast_func) {
+ Some(true) => gen_panic_ex_template(ast_func, krate_name.clone()),
+ _ => None,
+ };
+ let no_panic_ex = gen_ex_template(ast_func, krate_name);
+ (no_panic_ex, panic_ex)
+ };
+
+ let mut lines = string_vec_from(&["# Examples", "", "```"]);
+ lines.append(&mut no_panic_ex?);
+ lines.push("```".into());
+ if let Some(mut ex) = panic_ex {
+ lines.push("".into());
+ lines.push("```should_panic".into());
+ lines.append(&mut ex);
+ lines.push("```".into());
+ }
+ Some(lines)
+}
+
+/// 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,
+ }
+}
+
+/// Generate an example template which should not panic
+/// `None` if the function has a `self` parameter but is not in an `impl`.
+fn gen_ex_template(ast_func: &ast::Fn, krate_name: String) -> Option<Vec<String>> {
+ let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, krate_name)?;
+ // Call the function, check result
+ if returns_a_value(ast_func) {
+ if count_parameters(&ex_helper.param_list) < 3 {
+ lines.push(format!("assert_eq!({}, );", ex_helper.function_call));
+ } else {
+ lines.push(format!("let result = {};", ex_helper.function_call));
+ lines.push("assert_eq!(result, );".into());
+ }
+ } else {
+ lines.push(format!("{};", ex_helper.function_call));
+ }
+ // Check the mutated values
+ if is_ref_mut_self(ast_func) == Some(true) {
+ lines.push(format!("assert_eq!({}, );", ex_helper.self_name?));
+ }
+ for param_name in &ex_helper.ref_mut_params {
+ lines.push(format!("assert_eq!({}, );", param_name));
+ }
+ Some(lines)
+}
+
+/// Generate an example template which should panic
+/// `None` if the function has a `self` parameter but is not in an `impl`.
+fn gen_panic_ex_template(ast_func: &ast::Fn, krate_name: String) -> Option<Vec<String>> {
+ let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, krate_name)?;
+ match returns_a_value(ast_func) {
+ true => lines.push(format!("let _ = {}; // panics", ex_helper.function_call)),
+ false => lines.push(format!("{}; // panics", ex_helper.function_call)),
+ }
+ Some(lines)
+}
+
+/// Intermediary results of the start of example generation
+struct ExHelper {
+ function_call: String,
+ param_list: ast::ParamList,
+ ref_mut_params: Vec<String>,
+ self_name: Option<String>,
+}
+
+/// Build the start of the example and transmit the useful intermediary results.
+/// `None` if the function has a `self` parameter but is not in an `impl`.
+fn gen_ex_start_helper(ast_func: &ast::Fn, krate_name: String) -> Option<(Vec<String>, ExHelper)> {
+ let mut lines = Vec::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: Option<String> = self_name(ast_func);
+
+ lines.push(format!("use {};", build_path(ast_func, krate_name)));
+ lines.push("".into());
+ if let Some(self_definition) = self_definition(ast_func, self_name.as_deref()) {
+ lines.push(self_definition);
+ }
+ for param_name in &ref_mut_params {
+ lines.push(format!("let mut {} = ;", param_name))
+ }
+ let function_call = function_call(ast_func, ¶m_list, self_name.as_deref(), is_unsafe)?;
+ let ex_helper = ExHelper { function_call, param_list, ref_mut_params, self_name };
+ Some((lines, ex_helper))
+}
+
+/// `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!(")
+ || 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<String> {
+ ast_func
+ .syntax()
+ .ancestors()
+ .find_map(ast::Impl::cast)
+ .and_then(|i| i.self_ty())
+ .map(|t| (t.to_string()))
+}
+
+/// 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)?;
+ 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) -> bool {
+ ast_func
+ .syntax()
+ .ancestors()
+ .find_map(ast::Impl::cast)
+ .and_then(|impl_| impl_.trait_())
+ .is_some()
+}
+
+/// Helper function to determine if the function definition is in a trait definition
+fn is_in_trait_def(ast_func: &ast::Fn) -> bool {
+ ast_func.syntax().ancestors().find_map(ast::Trait::cast).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 define an variable to be the `self` argument
+fn self_definition(ast_func: &ast::Fn, self_name: Option<&str>) -> Option<String> {
+ let definition = match is_ref_mut_self(ast_func)? {
+ true => format!("let mut {} = ;", self_name?),
+ false => format!("let {} = ;", self_name?),
+ };
+ Some(definition)
+}
+
+/// 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.to_string()),
+ false => name.to_string(),
+ },
+ None => "_".to_string(),
+ },
+ _ => "_".to_string(),
+ });
+ intersperse_string(args_iter, ", ")
+}
+
+/// 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(¶m_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, krate_name: String) -> String {
+ let mut path: Vec<String> = ast_func
+ .syntax()
+ .ancestors()
+ .filter_map(|m| ast::Module::cast(m).and_then(|m| m.name()))
+ .map(|m| m.to_string())
+ .collect();
+ path.push(krate_name);
+ path.reverse();
+ path.push(
+ self_partial_type(ast_func)
+ .or_else(|| ast_func.name().map(|n| n.to_string()))
+ .unwrap_or_else(|| "*".into()),
+ );
+ intersperse_string(path.into_iter(), "::")
+}
+
+/// 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) -> bool {
+ match return_type(ast_func) {
+ Some(ret_type) => !["()", "!"].contains(&ret_type.to_string().as_str()),
+ None => false,
+ }
+}
+
+/// Helper function to concatenate string with a separator between them
+fn intersperse_string(mut iter: impl Iterator<Item = String>, separator: &str) -> String {
+ let mut result = String::new();
+ if let Some(first) = iter.next() {
+ result.push_str(&first);
+ }
+ for string in iter {
+ result.push_str(separator);
+ result.push_str(&string);
+ }
+ result
+}
+
+#[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 supports_noop_function() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+fn no$0op() {}
+"#,
+ r#"
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::noop;
+///
+/// noop();
+/// ```
+fn noop() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_a_parameter() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+fn no$0op_with_param(_a: i32) {}
+"#,
+ r#"
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::noop_with_param;
+///
+/// noop_with_param(_a);
+/// ```
+fn noop_with_param(_a: i32) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn detects_unsafe_function() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+unsafe fn no$0op_unsafe() {}
+"#,
+ r#"
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::noop_unsafe;
+///
+/// unsafe { noop_unsafe() };
+/// ```
+///
+/// # Safety
+///
+/// .
+unsafe fn noop_unsafe() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn guesses_panic_macro_can_panic() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+fn panic$0s_if(a: bool) {
+ if a {
+ panic!();
+ }
+}
+"#,
+ r#"
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::panics_if;
+///
+/// panics_if(a);
+/// ```
+///
+/// ```should_panic
+/// use test::panics_if;
+///
+/// panics_if(a); // panics
+/// ```
+///
+/// # Panics
+///
+/// Panics if .
+fn panics_if(a: bool) {
+ if a {
+ panic!();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn guesses_assert_macro_can_panic() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+fn $0panics_if_not(a: bool) {
+ assert!(a == true);
+}
+"#,
+ r#"
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::panics_if_not;
+///
+/// panics_if_not(a);
+/// ```
+///
+/// ```should_panic
+/// use test::panics_if_not;
+///
+/// panics_if_not(a); // panics
+/// ```
+///
+/// # Panics
+///
+/// Panics if .
+fn panics_if_not(a: bool) {
+ assert!(a == true);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn guesses_unwrap_can_panic() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+fn $0panics_if_none(a: Option<()>) {
+ a.unwrap();
+}
+"#,
+ r#"
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::panics_if_none;
+///
+/// panics_if_none(a);
+/// ```
+///
+/// ```should_panic
+/// use test::panics_if_none;
+///
+/// panics_if_none(a); // panics
+/// ```
+///
+/// # Panics
+///
+/// Panics if .
+fn panics_if_none(a: Option<()>) {
+ a.unwrap();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn guesses_expect_can_panic() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+fn $0panics_if_none2(a: Option<()>) {
+ a.expect("Bouh!");
+}
+"#,
+ r#"
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::panics_if_none2;
+///
+/// panics_if_none2(a);
+/// ```
+///
+/// ```should_panic
+/// use test::panics_if_none2;
+///
+/// panics_if_none2(a); // panics
+/// ```
+///
+/// # Panics
+///
+/// Panics if .
+fn panics_if_none2(a: Option<()>) {
+ a.expect("Bouh!");
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn checks_output_in_example() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+fn returns_a_value$0() -> i32 {
+ 0
+}
+"#,
+ r#"
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::returns_a_value;
+///
+/// assert_eq!(returns_a_value(), );
+/// ```
+fn returns_a_value() -> i32 {
+ 0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn detects_result_output() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+fn returns_a_result$0() -> Result<i32, std::io::Error> {
+ Ok(0)
+}
+"#,
+ r#"
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::returns_a_result;
+///
+/// assert_eq!(returns_a_result(), );
+/// ```
+///
+/// # Errors
+///
+/// This function will return an error if .
+fn returns_a_result() -> Result<i32, std::io::Error> {
+ Ok(0)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn checks_ref_mut_in_example() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+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, );
+/// ```
+fn modifies_a_value(a: &mut i32) {
+ *a = 0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn stores_result_if_at_least_3_params() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+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, );
+/// ```
+fn sum3(a: i32, b: i32, c: i32) -> i32 {
+ a + b + c
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_fn_in_mods() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+mod a {
+ mod b {
+ fn no$0op() {}
+ }
+}
+"#,
+ r#"
+mod a {
+ mod b {
+ /// .
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use test::a::b::noop;
+ ///
+ /// noop();
+ /// ```
+ fn noop() {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_fn_in_impl() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+struct MyStruct;
+impl MyStruct {
+ fn no$0op() {}
+}
+"#,
+ r#"
+struct MyStruct;
+impl MyStruct {
+ /// .
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use test::MyStruct;
+ ///
+ /// MyStruct::noop();
+ /// ```
+ fn noop() {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn detects_new() {
+ 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>`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use test::MyGenericStruct;
+ ///
+ /// assert_eq!(MyGenericStruct::new(x), );
+ /// ```
+ pub fn new(x: T) -> MyGenericStruct<T> {
+ MyGenericStruct { x }
+ }
+}
+"#,
+ );
+ }
+
+ #[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>`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use test::MyGenericStruct2;
+ ///
+ /// assert_eq!(MyGenericStruct2::new(x), );
+ /// ```
+ pub fn new(x: T) -> Self {
+ MyGenericStruct2 { x }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_method_call() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+impl<T> MyGenericStruct<T> {
+ pub fn co$0nsume(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_documentation_template,
+ r#"
+impl<T> MyGenericStruct<T> {
+ pub fn modi$0fy(&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;
+ }
+}
+"#,
+ );
+ }
+}