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},
"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());
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 {
/// 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)
}
}
}
-/// 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()?;
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, ¶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))
+ if returns_a_value(ast_func, ctx) {
+ if count_parameters(¶m_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
}
/// 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);
}
// 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()),
+ 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
}
/// 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)]
/// panics_if(a);
/// ```
///
-/// ```should_panic
-/// use test::panics_if;
-///
-/// panics_if(a); // panics
-/// ```
-///
/// # Panics
///
/// Panics if .
/// panics_if_not(a);
/// ```
///
-/// ```should_panic
-/// use test::panics_if_not;
-///
-/// panics_if_not(a); // panics
-/// ```
-///
/// # Panics
///
/// Panics if .
/// panics_if_none(a);
/// ```
///
-/// ```should_panic
-/// use test::panics_if_none;
-///
-/// panics_if_none(a); // panics
-/// ```
-///
/// # Panics
///
/// Panics if .
/// panics_if_none2(a);
/// ```
///
-/// ```should_panic
-/// use test::panics_if_none2;
-///
-/// panics_if_none2(a); // panics
-/// ```
-///
/// # Panics
///
/// Panics if .
/// // Example template not implemented for trait functions
/// ```
///
- /// ```should_panic
- /// // Example template not implemented for trait functions
- /// ```
- ///
/// # Panics
///
/// Panics if .
);
}
+ #[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(