1 use hir::{AsAssocItem, HasVisibility, ModuleDef, Visibility};
2 use ide_db::assists::{AssistId, AssistKind};
3 use itertools::Itertools;
4 use stdx::to_lower_snake_case;
6 ast::{self, edit::IndentLevel, HasDocComments, HasName},
10 use crate::assist_context::{AssistContext, Assists};
12 // Assist: generate_documentation_template
14 // Adds a documentation template above a function definition / declaration.
17 // pub fn my_$0func(a: i32, b: i32) -> Result<(), std::io::Error> {
28 // /// use test::my_func;
30 // /// assert_eq!(my_func(a, b), );
35 // /// This function will return an error if .
36 // pub fn my_func(a: i32, b: i32) -> Result<(), std::io::Error> {
40 pub(crate) fn generate_documentation_template(
44 let name = ctx.find_node_at_offset::<ast::Name>()?;
45 let ast_func = name.syntax().parent().and_then(ast::Fn::cast)?;
46 if is_in_trait_impl(&ast_func, ctx)
47 || !is_public(&ast_func, ctx)?
48 || ast_func.doc_comments().next().is_some()
53 let parent_syntax = ast_func.syntax();
54 let text_range = parent_syntax.text_range();
55 let indent_level = IndentLevel::from_node(&parent_syntax);
58 AssistId("generate_documentation_template", AssistKind::Generate),
59 "Generate a documentation template",
62 // Introduction / short function description before the sections
63 let mut doc_lines = vec![introduction_builder(&ast_func, ctx)];
64 // Then come the sections
65 if let Some(mut lines) = examples_builder(&ast_func, ctx) {
66 doc_lines.push("".into());
67 doc_lines.append(&mut lines);
69 for section_builder in [panics_builder, errors_builder, safety_builder] {
70 if let Some(mut lines) = section_builder(&ast_func) {
71 doc_lines.push("".into());
72 doc_lines.append(&mut lines);
75 builder.insert(text_range.start(), documentation_from_lines(doc_lines, indent_level));
80 /// Builds an introduction, trying to be smart if the function is `::new()`
81 fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext) -> String {
82 || -> Option<String> {
83 let hir_func = ctx.sema.to_def(ast_func)?;
84 let container = hir_func.as_assoc_item(ctx.db())?.container(ctx.db());
85 if let hir::AssocItemContainer::Impl(implementation) = container {
86 let ret_ty = hir_func.ret_type(ctx.db());
87 let self_ty = implementation.self_ty(ctx.db());
89 let is_new = ast_func.name()?.to_string() == "new";
90 match is_new && ret_ty == self_ty {
92 Some(format!("Creates a new [`{}`].", self_type_without_lifetimes(ast_func)?))
100 .unwrap_or_else(|| ".".into())
103 /// Builds an `# Examples` section. An option is returned to be able to manage an error in the AST.
104 fn examples_builder(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<Vec<String>> {
105 let mut lines = string_vec_from(&["# Examples", "", "```"]);
106 if is_in_trait_def(ast_func, ctx) {
107 lines.push("// Example template not implemented for trait functions".into());
109 lines.append(&mut gen_ex_template(ast_func, ctx)?)
112 lines.push("```".into());
116 /// Builds an optional `# Panics` section
117 fn panics_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
118 match can_panic(ast_func) {
119 Some(true) => Some(string_vec_from(&["# Panics", "", "Panics if ."])),
124 /// Builds an optional `# Errors` section
125 fn errors_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
126 match return_type(ast_func)?.to_string().contains("Result") {
127 true => Some(string_vec_from(&["# Errors", "", "This function will return an error if ."])),
132 /// Builds an optional `# Safety` section
133 fn safety_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
134 let is_unsafe = ast_func.unsafe_token().is_some();
136 true => Some(string_vec_from(&["# Safety", "", "."])),
141 /// Generates an example template
142 fn gen_ex_template(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<Vec<String>> {
143 let mut lines = Vec::new();
144 let is_unsafe = ast_func.unsafe_token().is_some();
145 let param_list = ast_func.param_list()?;
146 let ref_mut_params = ref_mut_params(¶m_list);
147 let self_name: Option<String> = self_name(ast_func);
149 lines.push(format!("use {};", build_path(ast_func, ctx)?));
150 lines.push("".into());
151 if let Some(self_definition) = self_definition(ast_func, self_name.as_deref()) {
152 lines.push(self_definition);
154 for param_name in &ref_mut_params {
155 lines.push(format!("let mut {} = ;", param_name))
157 // Call the function, check result
158 let function_call = function_call(ast_func, ¶m_list, self_name.as_deref(), is_unsafe)?;
159 if returns_a_value(ast_func, ctx) {
160 if count_parameters(¶m_list) < 3 {
161 lines.push(format!("assert_eq!({}, );", function_call));
163 lines.push(format!("let result = {};", function_call));
164 lines.push("assert_eq!(result, );".into());
167 lines.push(format!("{};", function_call));
169 // Check the mutated values
170 if is_ref_mut_self(ast_func) == Some(true) {
171 lines.push(format!("assert_eq!({}, );", self_name?));
173 for param_name in &ref_mut_params {
174 lines.push(format!("assert_eq!({}, );", param_name));
179 /// Checks if the function is public / exported
180 fn is_public(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<bool> {
181 let hir_func = ctx.sema.to_def(ast_func)?;
183 hir_func.visibility(ctx.db()) == Visibility::Public
184 && all_parent_mods_public(&hir_func, ctx),
188 /// Checks that all parent modules of the function are public / exported
189 fn all_parent_mods_public(hir_func: &hir::Function, ctx: &AssistContext) -> bool {
190 let mut module = hir_func.module(ctx.db());
192 if let Some(parent) = module.parent(ctx.db()) {
193 match ModuleDef::from(module).visibility(ctx.db()) {
194 Visibility::Public => module = parent,
203 /// Returns the name of the current crate
204 fn crate_name(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<String> {
205 let krate = ctx.sema.scope(&ast_func.syntax()).module()?.krate();
206 Some(krate.display_name(ctx.db())?.to_string())
209 /// `None` if function without a body; some bool to guess if function can panic
210 fn can_panic(ast_func: &ast::Fn) -> Option<bool> {
211 let body = ast_func.body()?.to_string();
212 let can_panic = body.contains("panic!(")
213 // FIXME it would be better to not match `debug_assert*!` macro invocations
214 || body.contains("assert!(")
215 || body.contains(".unwrap()")
216 || body.contains(".expect(");
220 /// Helper function to get the name that should be given to `self` arguments
221 fn self_name(ast_func: &ast::Fn) -> Option<String> {
222 self_partial_type(ast_func).map(|name| to_lower_snake_case(&name))
225 /// Heper function to get the name of the type of `self`
226 fn self_type(ast_func: &ast::Fn) -> Option<ast::Type> {
227 ast_func.syntax().ancestors().find_map(ast::Impl::cast).and_then(|i| i.self_ty())
230 /// Output the real name of `Self` like `MyType<T>`, without the lifetimes.
231 fn self_type_without_lifetimes(ast_func: &ast::Fn) -> Option<String> {
232 let path_segment = match self_type(ast_func)? {
233 ast::Type::PathType(path_type) => path_type.path()?.segment()?,
236 let mut name = path_segment.name_ref()?.to_string();
237 let generics = path_segment
240 .filter(|generic| matches!(generic, ast::GenericArg::TypeArg(_)))
241 .map(|generic| generic.to_string());
242 let generics: String = generics.format(", ").to_string();
243 if !generics.is_empty() {
245 name.push_str(&generics);
251 /// Heper function to get the name of the type of `self` without generic arguments
252 fn self_partial_type(ast_func: &ast::Fn) -> Option<String> {
253 let mut self_type = self_type(ast_func)?.to_string();
254 if let Some(idx) = self_type.find(|c| ['<', ' '].contains(&c)) {
255 self_type.truncate(idx);
260 /// Helper function to determine if the function is in a trait implementation
261 fn is_in_trait_impl(ast_func: &ast::Fn, ctx: &AssistContext) -> bool {
264 .and_then(|hir_func| hir_func.as_assoc_item(ctx.db()))
265 .and_then(|assoc_item| assoc_item.containing_trait_impl(ctx.db()))
269 /// Helper function to determine if the function definition is in a trait definition
270 fn is_in_trait_def(ast_func: &ast::Fn, ctx: &AssistContext) -> bool {
273 .and_then(|hir_func| hir_func.as_assoc_item(ctx.db()))
274 .and_then(|assoc_item| assoc_item.containing_trait(ctx.db()))
278 /// Returns `None` if no `self` at all, `Some(true)` if there is `&mut self` else `Some(false)`
279 fn is_ref_mut_self(ast_func: &ast::Fn) -> Option<bool> {
280 let self_param = ast_func.param_list()?.self_param()?;
281 Some(self_param.mut_token().is_some() && self_param.amp_token().is_some())
284 /// Helper function to define an variable to be the `self` argument
285 fn self_definition(ast_func: &ast::Fn, self_name: Option<&str>) -> Option<String> {
286 let definition = match is_ref_mut_self(ast_func)? {
287 true => format!("let mut {} = ;", self_name?),
288 false => format!("let {} = ;", self_name?),
293 /// Helper function to determine if a parameter is `&mut`
294 fn is_a_ref_mut_param(param: &ast::Param) -> bool {
296 Some(ast::Type::RefType(param_ref)) => param_ref.mut_token().is_some(),
301 /// Helper function to build the list of `&mut` parameters
302 fn ref_mut_params(param_list: &ast::ParamList) -> Vec<String> {
305 .filter_map(|param| match is_a_ref_mut_param(¶m) {
306 // Maybe better filter the param name (to do this maybe extract a function from
307 // `arguments_from_params`?) in case of a `mut a: &mut T`. Anyway managing most (not
308 // all) cases might be enough, the goal is just to produce a template.
309 true => Some(param.pat()?.to_string()),
315 /// Helper function to build the comma-separated list of arguments of the function
316 fn arguments_from_params(param_list: &ast::ParamList) -> String {
317 let args_iter = param_list.params().map(|param| match param.pat() {
318 // To avoid `mut` in the function call (which would be a nonsense), `Pat` should not be
319 // written as is so its variants must be managed independently. Other variants (for
320 // instance `TuplePat`) could be managed later.
321 Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() {
322 Some(name) => match is_a_ref_mut_param(¶m) {
323 true => format!("&mut {}", name),
324 false => name.to_string(),
326 None => "_".to_string(),
328 _ => "_".to_string(),
330 args_iter.format(", ").to_string()
333 /// Helper function to build a function call. `None` if expected `self_name` was not provided
336 param_list: &ast::ParamList,
337 self_name: Option<&str>,
339 ) -> Option<String> {
340 let name = ast_func.name()?;
341 let arguments = arguments_from_params(¶m_list);
342 let function_call = if param_list.self_param().is_some() {
343 format!("{}.{}({})", self_name?, name, arguments)
344 } else if let Some(implementation) = self_partial_type(ast_func) {
345 format!("{}::{}({})", implementation, name, arguments)
347 format!("{}({})", name, arguments)
350 true => Some(format!("unsafe {{ {} }}", function_call)),
351 false => Some(function_call),
355 /// Helper function to count the parameters including `self`
356 fn count_parameters(param_list: &ast::ParamList) -> usize {
357 param_list.params().count() + if param_list.self_param().is_some() { 1 } else { 0 }
360 /// Helper function to transform lines of documentation into a Rust code documentation
361 fn documentation_from_lines(doc_lines: Vec<String>, indent_level: IndentLevel) -> String {
362 let mut result = String::new();
363 for doc_line in doc_lines {
364 result.push_str("///");
365 if !doc_line.is_empty() {
367 result.push_str(&doc_line);
370 result.push_str(&indent_level.to_string());
375 /// Helper function to transform an array of borrowed strings to an owned `Vec<String>`
376 fn string_vec_from(string_array: &[&str]) -> Vec<String> {
377 string_array.iter().map(|&s| s.to_owned()).collect()
380 /// Helper function to build the path of the module in the which is the node
381 fn build_path(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<String> {
382 let crate_name = crate_name(ast_func, ctx)?;
383 let leaf = self_partial_type(ast_func)
384 .or_else(|| ast_func.name().map(|n| n.to_string()))
385 .unwrap_or_else(|| "*".into());
386 let module_def: ModuleDef = ctx.sema.to_def(ast_func)?.module(ctx.db()).into();
387 match module_def.canonical_path(ctx.db()) {
388 Some(path) => Some(format!("{}::{}::{}", crate_name, path, leaf)),
389 None => Some(format!("{}::{}", crate_name, leaf)),
393 /// Helper function to get the return type of a function
394 fn return_type(ast_func: &ast::Fn) -> Option<ast::Type> {
395 ast_func.ret_type()?.ty()
398 /// Helper function to determine if the function returns some data
399 fn returns_a_value(ast_func: &ast::Fn, ctx: &AssistContext) -> bool {
402 .map(|hir_func| hir_func.ret_type(ctx.db()))
403 .map(|ret_ty| !ret_ty.is_unit() && !ret_ty.is_never())
409 use crate::tests::{check_assist, check_assist_not_applicable};
414 fn not_applicable_on_function_calls() {
415 check_assist_not_applicable(
416 generate_documentation_template,
419 fn calls_hello_world() {
427 fn not_applicable_in_trait_impl() {
428 check_assist_not_applicable(
429 generate_documentation_template,
433 impl MyTrait for MyStruct {
441 fn not_applicable_if_function_is_private() {
442 check_assist_not_applicable(generate_documentation_template, r#"fn priv$0ate() {}"#);
446 fn not_applicable_if_function_is_pub_crate() {
447 check_assist_not_applicable(
448 generate_documentation_template,
449 r#"pub(crate) fn pri$0vate() {}"#,
454 fn not_applicable_if_function_is_in_private_mod() {
455 check_assist_not_applicable(
456 generate_documentation_template,
459 pub fn pri$0vate() {}
465 fn not_applicable_if_function_is_in_pub_crate_mod() {
466 check_assist_not_applicable(
467 generate_documentation_template,
469 pub(crate) mod PrivateModule {
470 pub fn pr$0ivate() {}
476 fn not_applicable_if_function_is_in_non_public_mod_is_recursive() {
477 check_assist_not_applicable(
478 generate_documentation_template,
480 mod ParentPrivateModule {
481 pub mod PrivateModule {
482 pub fn pr$0ivate() {}
489 fn not_applicable_if_function_already_documented() {
490 check_assist_not_applicable(
491 generate_documentation_template,
493 /// Some documentation here
494 pub fn $0documented_function() {}
500 fn supports_noop_function() {
502 generate_documentation_template,
522 fn supports_a_parameter() {
524 generate_documentation_template,
526 pub fn no$0op_with_param(_a: i32) {}
534 /// use test::noop_with_param;
536 /// noop_with_param(_a);
538 pub fn noop_with_param(_a: i32) {}
544 fn detects_unsafe_function() {
546 generate_documentation_template,
548 pub unsafe fn no$0op_unsafe() {}
556 /// use test::noop_unsafe;
558 /// unsafe { noop_unsafe() };
564 pub unsafe fn noop_unsafe() {}
570 fn guesses_panic_macro_can_panic() {
572 generate_documentation_template,
574 pub fn panic$0s_if(a: bool) {
586 /// use test::panics_if;
594 pub fn panics_if(a: bool) {
604 fn guesses_assert_macro_can_panic() {
606 generate_documentation_template,
608 pub fn $0panics_if_not(a: bool) {
618 /// use test::panics_if_not;
620 /// panics_if_not(a);
626 pub fn panics_if_not(a: bool) {
634 fn guesses_unwrap_can_panic() {
636 generate_documentation_template,
638 pub fn $0panics_if_none(a: Option<()>) {
648 /// use test::panics_if_none;
650 /// panics_if_none(a);
656 pub fn panics_if_none(a: Option<()>) {
664 fn guesses_expect_can_panic() {
666 generate_documentation_template,
668 pub fn $0panics_if_none2(a: Option<()>) {
678 /// use test::panics_if_none2;
680 /// panics_if_none2(a);
686 pub fn panics_if_none2(a: Option<()>) {
694 fn checks_output_in_example() {
696 generate_documentation_template,
698 pub fn returns_a_value$0() -> i32 {
708 /// use test::returns_a_value;
710 /// assert_eq!(returns_a_value(), );
712 pub fn returns_a_value() -> i32 {
720 fn detects_result_output() {
722 generate_documentation_template,
724 pub fn returns_a_result$0() -> Result<i32, std::io::Error> {
734 /// use test::returns_a_result;
736 /// assert_eq!(returns_a_result(), );
741 /// This function will return an error if .
742 pub fn returns_a_result() -> Result<i32, std::io::Error> {
750 fn checks_ref_mut_in_example() {
752 generate_documentation_template,
754 pub fn modifies_a_value$0(a: &mut i32) {
764 /// use test::modifies_a_value;
767 /// modifies_a_value(&mut a);
770 pub fn modifies_a_value(a: &mut i32) {
778 fn stores_result_if_at_least_3_params() {
780 generate_documentation_template,
782 pub fn sum3$0(a: i32, b: i32, c: i32) -> i32 {
794 /// let result = sum3(a, b, c);
795 /// assert_eq!(result, );
797 pub fn sum3(a: i32, b: i32, c: i32) -> i32 {
805 fn supports_fn_in_mods() {
807 generate_documentation_template,
823 /// use test::a::b::noop;
835 fn supports_fn_in_impl() {
837 generate_documentation_template,
852 /// use test::MyStruct;
854 /// MyStruct::noop();
863 fn supports_fn_in_trait() {
865 generate_documentation_template,
868 fn fun$0ction_trait();
878 /// // Example template not implemented for trait functions
887 fn supports_unsafe_fn_in_trait() {
889 generate_documentation_template,
892 unsafe fn unsafe_funct$0ion_trait();
902 /// // Example template not implemented for trait functions
908 unsafe fn unsafe_function_trait();
915 fn supports_fn_in_trait_with_default_panicking() {
917 generate_documentation_template,
920 fn function_trait_with_$0default_panicking() {
932 /// // Example template not implemented for trait functions
938 fn function_trait_with_default_panicking() {
947 fn supports_fn_in_trait_returning_result() {
949 generate_documentation_template,
952 fn function_tr$0ait_returning_result() -> Result<(), std::io::Error>;
962 /// // Example template not implemented for trait functions
967 /// This function will return an error if .
968 fn function_trait_returning_result() -> Result<(), std::io::Error>;
977 generate_documentation_template,
979 #[derive(Debug, PartialEq)]
980 pub struct MyGenericStruct<T> {
983 impl<T> MyGenericStruct<T> {
984 pub fn new$0(x: T) -> MyGenericStruct<T> {
985 MyGenericStruct { x }
990 #[derive(Debug, PartialEq)]
991 pub struct MyGenericStruct<T> {
994 impl<T> MyGenericStruct<T> {
995 /// Creates a new [`MyGenericStruct<T>`].
1000 /// use test::MyGenericStruct;
1002 /// assert_eq!(MyGenericStruct::new(x), );
1004 pub fn new(x: T) -> MyGenericStruct<T> {
1005 MyGenericStruct { x }
1013 fn removes_one_lifetime_from_description() {
1015 generate_documentation_template,
1017 #[derive(Debug, PartialEq)]
1018 pub struct MyGenericStruct<'a, T> {
1021 impl<'a, T> MyGenericStruct<'a, T> {
1022 pub fn new$0(x: &'a T) -> Self {
1023 MyGenericStruct { x }
1028 #[derive(Debug, PartialEq)]
1029 pub struct MyGenericStruct<'a, T> {
1032 impl<'a, T> MyGenericStruct<'a, T> {
1033 /// Creates a new [`MyGenericStruct<T>`].
1038 /// use test::MyGenericStruct;
1040 /// assert_eq!(MyGenericStruct::new(x), );
1042 pub fn new(x: &'a T) -> Self {
1043 MyGenericStruct { x }
1051 fn removes_all_lifetimes_from_description() {
1053 generate_documentation_template,
1055 #[derive(Debug, PartialEq)]
1056 pub struct MyGenericStruct<'a, 'b, T> {
1060 impl<'a, 'b, T> MyGenericStruct<'a, 'b, T> {
1061 pub fn new$0(x: &'a T, y: &'b T) -> Self {
1062 MyGenericStruct { x, y }
1067 #[derive(Debug, PartialEq)]
1068 pub struct MyGenericStruct<'a, 'b, T> {
1072 impl<'a, 'b, T> MyGenericStruct<'a, 'b, T> {
1073 /// Creates a new [`MyGenericStruct<T>`].
1078 /// use test::MyGenericStruct;
1080 /// assert_eq!(MyGenericStruct::new(x, y), );
1082 pub fn new(x: &'a T, y: &'b T) -> Self {
1083 MyGenericStruct { x, y }
1091 fn removes_all_lifetimes_and_brackets_from_description() {
1093 generate_documentation_template,
1095 #[derive(Debug, PartialEq)]
1096 pub struct MyGenericStruct<'a, 'b> {
1100 impl<'a, 'b> MyGenericStruct<'a, 'b> {
1101 pub fn new$0(x: &'a usize, y: &'b usize) -> Self {
1102 MyGenericStruct { x, y }
1107 #[derive(Debug, PartialEq)]
1108 pub struct MyGenericStruct<'a, 'b> {
1112 impl<'a, 'b> MyGenericStruct<'a, 'b> {
1113 /// Creates a new [`MyGenericStruct`].
1118 /// use test::MyGenericStruct;
1120 /// assert_eq!(MyGenericStruct::new(x, y), );
1122 pub fn new(x: &'a usize, y: &'b usize) -> Self {
1123 MyGenericStruct { x, y }
1131 fn detects_new_with_self() {
1133 generate_documentation_template,
1135 #[derive(Debug, PartialEq)]
1136 pub struct MyGenericStruct2<T> {
1139 impl<T> MyGenericStruct2<T> {
1140 pub fn new$0(x: T) -> Self {
1141 MyGenericStruct2 { x }
1146 #[derive(Debug, PartialEq)]
1147 pub struct MyGenericStruct2<T> {
1150 impl<T> MyGenericStruct2<T> {
1151 /// Creates a new [`MyGenericStruct2<T>`].
1156 /// use test::MyGenericStruct2;
1158 /// assert_eq!(MyGenericStruct2::new(x), );
1160 pub fn new(x: T) -> Self {
1161 MyGenericStruct2 { x }
1169 fn supports_method_call() {
1171 generate_documentation_template,
1173 impl<T> MyGenericStruct<T> {
1174 pub fn co$0nsume(self) {}
1178 impl<T> MyGenericStruct<T> {
1184 /// use test::MyGenericStruct;
1186 /// let my_generic_struct = ;
1187 /// my_generic_struct.consume();
1189 pub fn consume(self) {}
1196 fn checks_modified_self_param() {
1198 generate_documentation_template,
1200 impl<T> MyGenericStruct<T> {
1201 pub fn modi$0fy(&mut self, new_value: T) {
1207 impl<T> MyGenericStruct<T> {
1213 /// use test::MyGenericStruct;
1215 /// let mut my_generic_struct = ;
1216 /// my_generic_struct.modify(new_value);
1217 /// assert_eq!(my_generic_struct, );
1219 pub fn modify(&mut self, new_value: T) {