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 let mut doc_lines = Vec::new();
63 // Introduction / short function description before the sections
64 doc_lines.push(introduction_builder(&ast_func, ctx));
65 // Then come the sections
66 if let Some(mut lines) = examples_builder(&ast_func, ctx) {
67 doc_lines.push("".into());
68 doc_lines.append(&mut lines);
70 for section_builder in [panics_builder, errors_builder, safety_builder] {
71 if let Some(mut lines) = section_builder(&ast_func) {
72 doc_lines.push("".into());
73 doc_lines.append(&mut lines);
76 builder.insert(text_range.start(), documentation_from_lines(doc_lines, indent_level));
81 /// Builds an introduction, trying to be smart if the function is `::new()`
82 fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext) -> String {
83 || -> Option<String> {
84 let hir_func = ctx.sema.to_def(ast_func)?;
85 let container = hir_func.as_assoc_item(ctx.db())?.container(ctx.db());
86 if let hir::AssocItemContainer::Impl(implementation) = container {
87 let ret_ty = hir_func.ret_type(ctx.db());
88 let self_ty = implementation.self_ty(ctx.db());
90 let is_new = ast_func.name()?.to_string() == "new";
91 match is_new && ret_ty == self_ty {
92 true => Some(format!("Creates a new [`{}`].", self_type(ast_func)?)),
99 .unwrap_or_else(|| ".".into())
102 /// Builds an `# Examples` section. An option is returned to be able to manage an error in the AST.
103 fn examples_builder(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<Vec<String>> {
104 let (no_panic_ex, panic_ex) = if is_in_trait_def(ast_func, ctx) {
105 let message = "// Example template not implemented for trait functions";
106 let panic_ex = match can_panic(ast_func) {
107 Some(true) => Some(vec![message.into()]),
110 (Some(vec![message.into()]), panic_ex)
112 let panic_ex = match can_panic(ast_func) {
113 Some(true) => gen_panic_ex_template(ast_func, ctx),
116 let no_panic_ex = gen_ex_template(ast_func, ctx);
117 (no_panic_ex, panic_ex)
120 let mut lines = string_vec_from(&["# Examples", "", "```"]);
121 lines.append(&mut no_panic_ex?);
122 lines.push("```".into());
123 if let Some(mut ex) = panic_ex {
124 lines.push("".into());
125 lines.push("```should_panic".into());
126 lines.append(&mut ex);
127 lines.push("```".into());
132 /// Builds an optional `# Panics` section
133 fn panics_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
134 match can_panic(ast_func) {
135 Some(true) => Some(string_vec_from(&["# Panics", "", "Panics if ."])),
140 /// Builds an optional `# Errors` section
141 fn errors_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
142 match return_type(ast_func)?.to_string().contains("Result") {
143 true => Some(string_vec_from(&["# Errors", "", "This function will return an error if ."])),
148 /// Builds an optional `# Safety` section
149 fn safety_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
150 let is_unsafe = ast_func.unsafe_token().is_some();
152 true => Some(string_vec_from(&["# Safety", "", "."])),
157 /// Generate an example template which should not panic
158 /// `None` if the function has a `self` parameter but is not in an `impl`.
159 fn gen_ex_template(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<Vec<String>> {
160 let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, ctx)?;
161 // Call the function, check result
162 if returns_a_value(ast_func, ctx) {
163 if count_parameters(&ex_helper.param_list) < 3 {
164 lines.push(format!("assert_eq!({}, );", ex_helper.function_call));
166 lines.push(format!("let result = {};", ex_helper.function_call));
167 lines.push("assert_eq!(result, );".into());
170 lines.push(format!("{};", ex_helper.function_call));
172 // Check the mutated values
173 if is_ref_mut_self(ast_func) == Some(true) {
174 lines.push(format!("assert_eq!({}, );", ex_helper.self_name?));
176 for param_name in &ex_helper.ref_mut_params {
177 lines.push(format!("assert_eq!({}, );", param_name));
182 /// Generate an example template which should panic
183 /// `None` if the function has a `self` parameter but is not in an `impl`.
184 fn gen_panic_ex_template(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<Vec<String>> {
185 let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, ctx)?;
186 match returns_a_value(ast_func, ctx) {
187 true => lines.push(format!("let _ = {}; // panics", ex_helper.function_call)),
188 false => lines.push(format!("{}; // panics", ex_helper.function_call)),
193 /// Intermediary results of the start of example generation
195 function_call: String,
196 param_list: ast::ParamList,
197 ref_mut_params: Vec<String>,
198 self_name: Option<String>,
201 /// Builds the start of the example and transmit the useful intermediary results.
202 /// `None` if the function has a `self` parameter but is not in an `impl`.
203 fn gen_ex_start_helper(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<(Vec<String>, ExHelper)> {
204 let mut lines = Vec::new();
205 let is_unsafe = ast_func.unsafe_token().is_some();
206 let param_list = ast_func.param_list()?;
207 let ref_mut_params = ref_mut_params(¶m_list);
208 let self_name: Option<String> = self_name(ast_func);
210 lines.push(format!("use {};", build_path(ast_func, ctx)?));
211 lines.push("".into());
212 if let Some(self_definition) = self_definition(ast_func, self_name.as_deref()) {
213 lines.push(self_definition);
215 for param_name in &ref_mut_params {
216 lines.push(format!("let mut {} = ;", param_name))
218 let function_call = function_call(ast_func, ¶m_list, self_name.as_deref(), is_unsafe)?;
219 let ex_helper = ExHelper { function_call, param_list, ref_mut_params, self_name };
220 Some((lines, ex_helper))
223 /// Checks if the function is public / exported
224 fn is_public(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<bool> {
225 let hir_func = ctx.sema.to_def(ast_func)?;
227 hir_func.visibility(ctx.db()) == Visibility::Public
228 && all_parent_mods_public(&hir_func, ctx),
232 /// Checks that all parent modules of the function are public / exported
233 fn all_parent_mods_public(hir_func: &hir::Function, ctx: &AssistContext) -> bool {
234 let mut module = hir_func.module(ctx.db());
236 if let Some(parent) = module.parent(ctx.db()) {
237 match ModuleDef::from(module).visibility(ctx.db()) {
238 Visibility::Public => module = parent,
247 /// Returns the name of the current crate
248 fn crate_name(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<String> {
249 let krate = ctx.sema.scope(&ast_func.syntax()).module()?.krate();
250 Some(krate.display_name(ctx.db())?.to_string())
253 /// `None` if function without a body; some bool to guess if function can panic
254 fn can_panic(ast_func: &ast::Fn) -> Option<bool> {
255 let body = ast_func.body()?.to_string();
256 let can_panic = body.contains("panic!(")
257 // FIXME it would be better to not match `debug_assert*!` macro invocations
258 || body.contains("assert!(")
259 || body.contains(".unwrap()")
260 || body.contains(".expect(");
264 /// Helper function to get the name that should be given to `self` arguments
265 fn self_name(ast_func: &ast::Fn) -> Option<String> {
266 self_partial_type(ast_func).map(|name| to_lower_snake_case(&name))
269 /// Heper function to get the name of the type of `self`
270 fn self_type(ast_func: &ast::Fn) -> Option<String> {
274 .find_map(ast::Impl::cast)
275 .and_then(|i| i.self_ty())
276 .map(|t| (t.to_string()))
279 /// Heper function to get the name of the type of `self` without generic arguments
280 fn self_partial_type(ast_func: &ast::Fn) -> Option<String> {
281 let mut self_type = self_type(ast_func)?;
282 if let Some(idx) = self_type.find(|c| ['<', ' '].contains(&c)) {
283 self_type.truncate(idx);
288 /// Helper function to determine if the function is in a trait implementation
289 fn is_in_trait_impl(ast_func: &ast::Fn, ctx: &AssistContext) -> bool {
292 .and_then(|hir_func| hir_func.as_assoc_item(ctx.db()))
293 .and_then(|assoc_item| assoc_item.containing_trait_impl(ctx.db()))
297 /// Helper function to determine if the function definition is in a trait definition
298 fn is_in_trait_def(ast_func: &ast::Fn, ctx: &AssistContext) -> bool {
301 .and_then(|hir_func| hir_func.as_assoc_item(ctx.db()))
302 .and_then(|assoc_item| assoc_item.containing_trait(ctx.db()))
306 /// Returns `None` if no `self` at all, `Some(true)` if there is `&mut self` else `Some(false)`
307 fn is_ref_mut_self(ast_func: &ast::Fn) -> Option<bool> {
308 let self_param = ast_func.param_list()?.self_param()?;
309 Some(self_param.mut_token().is_some() && self_param.amp_token().is_some())
312 /// Helper function to define an variable to be the `self` argument
313 fn self_definition(ast_func: &ast::Fn, self_name: Option<&str>) -> Option<String> {
314 let definition = match is_ref_mut_self(ast_func)? {
315 true => format!("let mut {} = ;", self_name?),
316 false => format!("let {} = ;", self_name?),
321 /// Helper function to determine if a parameter is `&mut`
322 fn is_a_ref_mut_param(param: &ast::Param) -> bool {
324 Some(ast::Type::RefType(param_ref)) => param_ref.mut_token().is_some(),
329 /// Helper function to build the list of `&mut` parameters
330 fn ref_mut_params(param_list: &ast::ParamList) -> Vec<String> {
333 .filter_map(|param| match is_a_ref_mut_param(¶m) {
334 // Maybe better filter the param name (to do this maybe extract a function from
335 // `arguments_from_params`?) in case of a `mut a: &mut T`. Anyway managing most (not
336 // all) cases might be enough, the goal is just to produce a template.
337 true => Some(param.pat()?.to_string()),
343 /// Helper function to build the comma-separated list of arguments of the function
344 fn arguments_from_params(param_list: &ast::ParamList) -> String {
345 let args_iter = param_list.params().map(|param| match param.pat() {
346 // To avoid `mut` in the function call (which would be a nonsense), `Pat` should not be
347 // written as is so its variants must be managed independently. Other variants (for
348 // instance `TuplePat`) could be managed later.
349 Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() {
350 Some(name) => match is_a_ref_mut_param(¶m) {
351 true => format!("&mut {}", name.to_string()),
352 false => name.to_string(),
354 None => "_".to_string(),
356 _ => "_".to_string(),
358 Itertools::intersperse(args_iter, ", ".to_string()).collect()
361 /// Helper function to build a function call. `None` if expected `self_name` was not provided
364 param_list: &ast::ParamList,
365 self_name: Option<&str>,
367 ) -> Option<String> {
368 let name = ast_func.name()?;
369 let arguments = arguments_from_params(¶m_list);
370 let function_call = if param_list.self_param().is_some() {
371 format!("{}.{}({})", self_name?, name, arguments)
372 } else if let Some(implementation) = self_partial_type(ast_func) {
373 format!("{}::{}({})", implementation, name, arguments)
375 format!("{}({})", name, arguments)
378 true => Some(format!("unsafe {{ {} }}", function_call)),
379 false => Some(function_call),
383 /// Helper function to count the parameters including `self`
384 fn count_parameters(param_list: &ast::ParamList) -> usize {
385 param_list.params().count() + if param_list.self_param().is_some() { 1 } else { 0 }
388 /// Helper function to transform lines of documentation into a Rust code documentation
389 fn documentation_from_lines(doc_lines: Vec<String>, indent_level: IndentLevel) -> String {
390 let mut result = String::new();
391 for doc_line in doc_lines {
392 result.push_str("///");
393 if !doc_line.is_empty() {
395 result.push_str(&doc_line);
398 result.push_str(&indent_level.to_string());
403 /// Helper function to transform an array of borrowed strings to an owned `Vec<String>`
404 fn string_vec_from(string_array: &[&str]) -> Vec<String> {
405 string_array.iter().map(|&s| s.to_owned()).collect()
408 /// Helper function to build the path of the module in the which is the node
409 fn build_path(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<String> {
410 let crate_name = crate_name(ast_func, ctx)?;
411 let leaf = self_partial_type(ast_func)
412 .or_else(|| ast_func.name().map(|n| n.to_string()))
413 .unwrap_or_else(|| "*".into());
414 let module_def: ModuleDef = ctx.sema.to_def(ast_func)?.module(ctx.db()).into();
415 match module_def.canonical_path(ctx.db()) {
416 Some(path) => Some(format!("{}::{}::{}", crate_name, path, leaf)),
417 None => Some(format!("{}::{}", crate_name, leaf)),
421 /// Helper function to get the return type of a function
422 fn return_type(ast_func: &ast::Fn) -> Option<ast::Type> {
423 ast_func.ret_type()?.ty()
426 /// Helper function to determine if the function returns some data
427 fn returns_a_value(ast_func: &ast::Fn, ctx: &AssistContext) -> bool {
430 .map(|hir_func| hir_func.ret_type(ctx.db()))
431 .map(|ret_ty| !ret_ty.is_unit() && !ret_ty.is_never())
437 use crate::tests::{check_assist, check_assist_not_applicable};
442 fn not_applicable_on_function_calls() {
443 check_assist_not_applicable(
444 generate_documentation_template,
447 fn calls_hello_world() {
455 fn not_applicable_in_trait_impl() {
456 check_assist_not_applicable(
457 generate_documentation_template,
461 impl MyTrait for MyStruct {
469 fn not_applicable_if_function_is_private() {
470 check_assist_not_applicable(generate_documentation_template, r#"fn priv$0ate() {}"#);
474 fn not_applicable_if_function_is_pub_crate() {
475 check_assist_not_applicable(
476 generate_documentation_template,
477 r#"pub(crate) fn pri$0vate() {}"#,
482 fn not_applicable_if_function_is_in_private_mod() {
483 check_assist_not_applicable(
484 generate_documentation_template,
487 pub fn pri$0vate() {}
493 fn not_applicable_if_function_is_in_pub_crate_mod() {
494 check_assist_not_applicable(
495 generate_documentation_template,
497 pub(crate) mod PrivateModule {
498 pub fn pr$0ivate() {}
504 fn not_applicable_if_function_is_in_non_public_mod_is_recursive() {
505 check_assist_not_applicable(
506 generate_documentation_template,
508 mod ParentPrivateModule {
509 pub mod PrivateModule {
510 pub fn pr$0ivate() {}
517 fn not_applicable_if_function_already_documented() {
518 check_assist_not_applicable(
519 generate_documentation_template,
521 /// Some documentation here
522 pub fn $0documented_function() {}
528 fn supports_noop_function() {
530 generate_documentation_template,
550 fn supports_a_parameter() {
552 generate_documentation_template,
554 pub fn no$0op_with_param(_a: i32) {}
562 /// use test::noop_with_param;
564 /// noop_with_param(_a);
566 pub fn noop_with_param(_a: i32) {}
572 fn detects_unsafe_function() {
574 generate_documentation_template,
576 pub unsafe fn no$0op_unsafe() {}
584 /// use test::noop_unsafe;
586 /// unsafe { noop_unsafe() };
592 pub unsafe fn noop_unsafe() {}
598 fn guesses_panic_macro_can_panic() {
600 generate_documentation_template,
602 pub fn panic$0s_if(a: bool) {
614 /// use test::panics_if;
620 /// use test::panics_if;
622 /// panics_if(a); // panics
628 pub fn panics_if(a: bool) {
638 fn guesses_assert_macro_can_panic() {
640 generate_documentation_template,
642 pub fn $0panics_if_not(a: bool) {
652 /// use test::panics_if_not;
654 /// panics_if_not(a);
658 /// use test::panics_if_not;
660 /// panics_if_not(a); // panics
666 pub fn panics_if_not(a: bool) {
674 fn guesses_unwrap_can_panic() {
676 generate_documentation_template,
678 pub fn $0panics_if_none(a: Option<()>) {
688 /// use test::panics_if_none;
690 /// panics_if_none(a);
694 /// use test::panics_if_none;
696 /// panics_if_none(a); // panics
702 pub fn panics_if_none(a: Option<()>) {
710 fn guesses_expect_can_panic() {
712 generate_documentation_template,
714 pub fn $0panics_if_none2(a: Option<()>) {
724 /// use test::panics_if_none2;
726 /// panics_if_none2(a);
730 /// use test::panics_if_none2;
732 /// panics_if_none2(a); // panics
738 pub fn panics_if_none2(a: Option<()>) {
746 fn checks_output_in_example() {
748 generate_documentation_template,
750 pub fn returns_a_value$0() -> i32 {
760 /// use test::returns_a_value;
762 /// assert_eq!(returns_a_value(), );
764 pub fn returns_a_value() -> i32 {
772 fn detects_result_output() {
774 generate_documentation_template,
776 pub fn returns_a_result$0() -> Result<i32, std::io::Error> {
786 /// use test::returns_a_result;
788 /// assert_eq!(returns_a_result(), );
793 /// This function will return an error if .
794 pub fn returns_a_result() -> Result<i32, std::io::Error> {
802 fn checks_ref_mut_in_example() {
804 generate_documentation_template,
806 pub fn modifies_a_value$0(a: &mut i32) {
816 /// use test::modifies_a_value;
819 /// modifies_a_value(&mut a);
822 pub fn modifies_a_value(a: &mut i32) {
830 fn stores_result_if_at_least_3_params() {
832 generate_documentation_template,
834 pub fn sum3$0(a: i32, b: i32, c: i32) -> i32 {
846 /// let result = sum3(a, b, c);
847 /// assert_eq!(result, );
849 pub fn sum3(a: i32, b: i32, c: i32) -> i32 {
857 fn supports_fn_in_mods() {
859 generate_documentation_template,
875 /// use test::a::b::noop;
887 fn supports_fn_in_impl() {
889 generate_documentation_template,
904 /// use test::MyStruct;
906 /// MyStruct::noop();
915 fn supports_fn_in_trait() {
917 generate_documentation_template,
920 fn fun$0ction_trait();
930 /// // Example template not implemented for trait functions
939 fn supports_unsafe_fn_in_trait() {
941 generate_documentation_template,
944 unsafe fn unsafe_funct$0ion_trait();
954 /// // Example template not implemented for trait functions
960 unsafe fn unsafe_function_trait();
967 fn supports_fn_in_trait_with_default_panicking() {
969 generate_documentation_template,
972 fn function_trait_with_$0default_panicking() {
984 /// // Example template not implemented for trait functions
988 /// // Example template not implemented for trait functions
994 fn function_trait_with_default_panicking() {
1003 fn supports_fn_in_trait_returning_result() {
1005 generate_documentation_template,
1008 fn function_tr$0ait_returning_result() -> Result<(), std::io::Error>;
1018 /// // Example template not implemented for trait functions
1023 /// This function will return an error if .
1024 fn function_trait_returning_result() -> Result<(), std::io::Error>;
1033 generate_documentation_template,
1035 #[derive(Debug, PartialEq)]
1036 pub struct MyGenericStruct<T> {
1039 impl<T> MyGenericStruct<T> {
1040 pub fn new$0(x: T) -> MyGenericStruct<T> {
1041 MyGenericStruct { x }
1046 #[derive(Debug, PartialEq)]
1047 pub struct MyGenericStruct<T> {
1050 impl<T> MyGenericStruct<T> {
1051 /// Creates a new [`MyGenericStruct<T>`].
1056 /// use test::MyGenericStruct;
1058 /// assert_eq!(MyGenericStruct::new(x), );
1060 pub fn new(x: T) -> MyGenericStruct<T> {
1061 MyGenericStruct { x }
1069 fn detects_new_with_self() {
1071 generate_documentation_template,
1073 #[derive(Debug, PartialEq)]
1074 pub struct MyGenericStruct2<T> {
1077 impl<T> MyGenericStruct2<T> {
1078 pub fn new$0(x: T) -> Self {
1079 MyGenericStruct2 { x }
1084 #[derive(Debug, PartialEq)]
1085 pub struct MyGenericStruct2<T> {
1088 impl<T> MyGenericStruct2<T> {
1089 /// Creates a new [`MyGenericStruct2<T>`].
1094 /// use test::MyGenericStruct2;
1096 /// assert_eq!(MyGenericStruct2::new(x), );
1098 pub fn new(x: T) -> Self {
1099 MyGenericStruct2 { x }
1107 fn supports_method_call() {
1109 generate_documentation_template,
1111 impl<T> MyGenericStruct<T> {
1112 pub fn co$0nsume(self) {}
1116 impl<T> MyGenericStruct<T> {
1122 /// use test::MyGenericStruct;
1124 /// let my_generic_struct = ;
1125 /// my_generic_struct.consume();
1127 pub fn consume(self) {}
1134 fn checks_modified_self_param() {
1136 generate_documentation_template,
1138 impl<T> MyGenericStruct<T> {
1139 pub fn modi$0fy(&mut self, new_value: T) {
1145 impl<T> MyGenericStruct<T> {
1151 /// use test::MyGenericStruct;
1153 /// let mut my_generic_struct = ;
1154 /// my_generic_struct.modify(new_value);
1155 /// assert_eq!(my_generic_struct, );
1157 pub fn modify(&mut self, new_value: T) {