]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_assists/src/handlers/generate_documentation_template.rs
Merge #11481
[rust.git] / crates / ide_assists / src / handlers / generate_documentation_template.rs
index 5205a8ac7edb753edbbb7b0c9dac424eef6f31f8..bb7c8b10102cb07d0b700f5578d19782e7254fac 100644 (file)
@@ -1,5 +1,6 @@
 use hir::{AsAssocItem, HasVisibility, ModuleDef, Visibility};
 use ide_db::assists::{AssistId, AssistKind};
+use itertools::Itertools;
 use stdx::to_lower_snake_case;
 use syntax::{
     ast::{self, edit::IndentLevel, HasDocComments, HasName},
@@ -58,9 +59,8 @@ pub(crate) fn generate_documentation_template(
         "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, ctx));
+            let mut doc_lines = vec![introduction_builder(&ast_func, ctx)];
             // Then come the sections
             if let Some(mut lines) = examples_builder(&ast_func, ctx) {
                 doc_lines.push("".into());
@@ -88,7 +88,9 @@ fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext) -> String {
 
             let is_new = ast_func.name()?.to_string() == "new";
             match is_new && ret_ty == self_ty {
-                true => Some(format!("Creates a new [`{}`].", self_type(ast_func)?)),
+                true => {
+                    Some(format!("Creates a new [`{}`].", self_type_without_lifetimes(ast_func)?))
+                }
                 false => None,
             }
         } else {
@@ -100,31 +102,14 @@ fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext) -> String {
 
 /// 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, ctx: &AssistContext) -> Option<Vec<String>> {
-    let (no_panic_ex, panic_ex) = if is_in_trait_def(ast_func, ctx) {
-        let message = "// Example template not implemented for trait functions";
-        let panic_ex = match can_panic(ast_func) {
-            Some(true) => Some(vec![message.into()]),
-            _ => None,
-        };
-        (Some(vec![message.into()]), panic_ex)
+    let mut lines = string_vec_from(&["# Examples", "", "```"]);
+    if is_in_trait_def(ast_func, ctx) {
+        lines.push("// Example template not implemented for trait functions".into());
     } else {
-        let panic_ex = match can_panic(ast_func) {
-            Some(true) => gen_panic_ex_template(ast_func, ctx),
-            _ => None,
-        };
-        let no_panic_ex = gen_ex_template(ast_func, ctx);
-        (no_panic_ex, panic_ex)
+        lines.append(&mut gen_ex_template(ast_func, ctx)?)
     };
 
-    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)
 }
 
@@ -153,53 +138,8 @@ fn safety_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
     }
 }
 
-/// Generate an example template which should not panic
-/// `None` if the function has a `self` parameter but is not in an `impl`.
+/// Generates an example template
 fn gen_ex_template(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<Vec<String>> {
-    let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, ctx)?;
-    // 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, ctx: &AssistContext) -> Option<Vec<String>> {
-    let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, ctx)?;
-    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>,
-}
-
-/// Builds 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, ctx: &AssistContext) -> 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()?;
@@ -214,9 +154,26 @@ fn gen_ex_start_helper(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<(Vec<S
     for param_name in &ref_mut_params {
         lines.push(format!("let mut {} = ;", param_name))
     }
+    // Call the function, check result
     let function_call = function_call(ast_func, &param_list, self_name.as_deref(), is_unsafe)?;
-    let ex_helper = ExHelper { function_call, param_list, ref_mut_params, self_name };
-    Some((lines, ex_helper))
+    if returns_a_value(ast_func, ctx) {
+        if count_parameters(&param_list) < 3 {
+            lines.push(format!("assert_eq!({}, );", function_call));
+        } else {
+            lines.push(format!("let result = {};", function_call));
+            lines.push("assert_eq!(result, );".into());
+        }
+    } else {
+        lines.push(format!("{};", function_call));
+    }
+    // Check the mutated values
+    if is_ref_mut_self(ast_func) == Some(true) {
+        lines.push(format!("assert_eq!({}, );", self_name?));
+    }
+    for param_name in &ref_mut_params {
+        lines.push(format!("assert_eq!({}, );", param_name));
+    }
+    Some(lines)
 }
 
 /// Checks if the function is public / exported
@@ -266,18 +223,34 @@ fn self_name(ast_func: &ast::Fn) -> Option<String> {
 }
 
 /// 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()))
+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()?
+        .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)?;
+    let mut self_type = self_type(ast_func)?.to_string();
     if let Some(idx) = self_type.find(|c| ['<', ' '].contains(&c)) {
         self_type.truncate(idx);
     }
@@ -347,14 +320,14 @@ fn arguments_from_params(param_list: &ast::ParamList) -> String {
         // instance `TuplePat`) could be managed later.
         Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() {
             Some(name) => match is_a_ref_mut_param(&param) {
-                true => format!("&mut {}", name.to_string()),
+                true => format!("&mut {}", name),
                 false => name.to_string(),
             },
             None => "_".to_string(),
         },
         _ => "_".to_string(),
     });
-    intersperse_string(args_iter, ", ")
+    args_iter.format(", ").to_string()
 }
 
 /// Helper function to build a function call. `None` if expected `self_name` was not provided
@@ -423,24 +396,12 @@ fn return_type(ast_func: &ast::Fn) -> Option<ast::Type> {
 }
 
 /// 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
+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)]
@@ -627,12 +588,6 @@ pub fn panic$0s_if(a: bool) {
 /// panics_if(a);
 /// ```
 ///
-/// ```should_panic
-/// use test::panics_if;
-///
-/// panics_if(a); // panics
-/// ```
-///
 /// # Panics
 ///
 /// Panics if .
@@ -665,12 +620,6 @@ pub fn $0panics_if_not(a: bool) {
 /// panics_if_not(a);
 /// ```
 ///
-/// ```should_panic
-/// use test::panics_if_not;
-///
-/// panics_if_not(a); // panics
-/// ```
-///
 /// # Panics
 ///
 /// Panics if .
@@ -701,12 +650,6 @@ pub fn $0panics_if_none(a: Option<()>) {
 /// panics_if_none(a);
 /// ```
 ///
-/// ```should_panic
-/// use test::panics_if_none;
-///
-/// panics_if_none(a); // panics
-/// ```
-///
 /// # Panics
 ///
 /// Panics if .
@@ -737,12 +680,6 @@ pub fn $0panics_if_none2(a: Option<()>) {
 /// panics_if_none2(a);
 /// ```
 ///
-/// ```should_panic
-/// use test::panics_if_none2;
-///
-/// panics_if_none2(a); // panics
-/// ```
-///
 /// # Panics
 ///
 /// Panics if .
@@ -995,10 +932,6 @@ pub trait MyTrait {
     /// // Example template not implemented for trait functions
     /// ```
     ///
-    /// ```should_panic
-    /// // Example template not implemented for trait functions
-    /// ```
-    ///
     /// # Panics
     ///
     /// Panics if .
@@ -1076,6 +1009,124 @@ pub fn new(x: T) -> MyGenericStruct<T> {
         );
     }
 
+    #[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>`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use test::MyGenericStruct;
+    ///
+    /// assert_eq!(MyGenericStruct::new(x), );
+    /// ```
+    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>`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use test::MyGenericStruct;
+    ///
+    /// assert_eq!(MyGenericStruct::new(x, y), );
+    /// ```
+    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`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use test::MyGenericStruct;
+    ///
+    /// assert_eq!(MyGenericStruct::new(x, y), );
+    /// ```
+    pub fn new(x: &'a usize, y: &'b usize) -> Self {
+        MyGenericStruct { x, y }
+    }
+}
+"#,
+        );
+    }
+
     #[test]
     fn detects_new_with_self() {
         check_assist(