1 use hir::{AsAssocItem, HasVisibility, ModuleDef, Visibility};
2 use ide_db::assists::{AssistId, AssistKind};
3 use stdx::to_lower_snake_case;
5 ast::{self, edit::IndentLevel, HasDocComments, HasName},
9 use crate::assist_context::{AssistContext, Assists};
11 // Assist: generate_documentation_template
13 // Adds a documentation template above a function definition / declaration.
16 // pub fn my_$0func(a: i32, b: i32) -> Result<(), std::io::Error> {
27 // /// use test::my_func;
29 // /// assert_eq!(my_func(a, b), );
34 // /// This function will return an error if .
35 // pub fn my_func(a: i32, b: i32) -> Result<(), std::io::Error> {
39 pub(crate) fn generate_documentation_template(
43 let name = ctx.find_node_at_offset::<ast::Name>()?;
44 let ast_func = name.syntax().parent().and_then(ast::Fn::cast)?;
45 if is_in_trait_impl(&ast_func, ctx)
46 || !is_public(&ast_func, ctx)?
47 || ast_func.doc_comments().next().is_some()
52 let parent_syntax = ast_func.syntax();
53 let text_range = parent_syntax.text_range();
54 let indent_level = IndentLevel::from_node(&parent_syntax);
57 AssistId("generate_documentation_template", AssistKind::Generate),
58 "Generate a documentation template",
61 let mut doc_lines = Vec::new();
62 // Introduction / short function description before the sections
63 doc_lines.push(introduction_builder(&ast_func));
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) -> String {
82 let is_new = ast_func.name().map(|name| &name.to_string() == "new").unwrap_or(false);
84 let ret_type = return_type(ast_func).map(|ret_type| ret_type.to_string());
85 let self_type = self_type(ast_func);
86 if ret_type.as_deref() == Some("Self") || ret_type == self_type {
87 if let Some(self_type) = self_type {
88 return format!("Creates a new [`{}`].", self_type);
95 /// Builds an `# Examples` section. An option is returned to be able to manage an error in the AST.
96 fn examples_builder(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<Vec<String>> {
97 let (no_panic_ex, panic_ex) = if is_in_trait_def(ast_func, ctx) {
98 let message = "// Example template not implemented for trait functions";
99 let panic_ex = match can_panic(ast_func) {
100 Some(true) => Some(vec![message.into()]),
103 (Some(vec![message.into()]), panic_ex)
105 let panic_ex = match can_panic(ast_func) {
106 Some(true) => gen_panic_ex_template(ast_func, ctx),
109 let no_panic_ex = gen_ex_template(ast_func, ctx);
110 (no_panic_ex, panic_ex)
113 let mut lines = string_vec_from(&["# Examples", "", "```"]);
114 lines.append(&mut no_panic_ex?);
115 lines.push("```".into());
116 if let Some(mut ex) = panic_ex {
117 lines.push("".into());
118 lines.push("```should_panic".into());
119 lines.append(&mut ex);
120 lines.push("```".into());
125 /// Builds an optional `# Panics` section
126 fn panics_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
127 match can_panic(ast_func) {
128 Some(true) => Some(string_vec_from(&["# Panics", "", "Panics if ."])),
133 /// Builds an optional `# Errors` section
134 fn errors_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
135 match return_type(ast_func)?.to_string().contains("Result") {
136 true => Some(string_vec_from(&["# Errors", "", "This function will return an error if ."])),
141 /// Builds an optional `# Safety` section
142 fn safety_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
143 let is_unsafe = ast_func.unsafe_token().is_some();
145 true => Some(string_vec_from(&["# Safety", "", "."])),
150 /// Generate an example template which should not panic
151 /// `None` if the function has a `self` parameter but is not in an `impl`.
152 fn gen_ex_template(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<Vec<String>> {
153 let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, ctx)?;
154 // Call the function, check result
155 if returns_a_value(ast_func) {
156 if count_parameters(&ex_helper.param_list) < 3 {
157 lines.push(format!("assert_eq!({}, );", ex_helper.function_call));
159 lines.push(format!("let result = {};", ex_helper.function_call));
160 lines.push("assert_eq!(result, );".into());
163 lines.push(format!("{};", ex_helper.function_call));
165 // Check the mutated values
166 if is_ref_mut_self(ast_func) == Some(true) {
167 lines.push(format!("assert_eq!({}, );", ex_helper.self_name?));
169 for param_name in &ex_helper.ref_mut_params {
170 lines.push(format!("assert_eq!({}, );", param_name));
175 /// Generate an example template which should panic
176 /// `None` if the function has a `self` parameter but is not in an `impl`.
177 fn gen_panic_ex_template(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<Vec<String>> {
178 let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, ctx)?;
179 match returns_a_value(ast_func) {
180 true => lines.push(format!("let _ = {}; // panics", ex_helper.function_call)),
181 false => lines.push(format!("{}; // panics", ex_helper.function_call)),
186 /// Intermediary results of the start of example generation
188 function_call: String,
189 param_list: ast::ParamList,
190 ref_mut_params: Vec<String>,
191 self_name: Option<String>,
194 /// Builds the start of the example and transmit the useful intermediary results.
195 /// `None` if the function has a `self` parameter but is not in an `impl`.
196 fn gen_ex_start_helper(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<(Vec<String>, ExHelper)> {
197 let mut lines = Vec::new();
198 let is_unsafe = ast_func.unsafe_token().is_some();
199 let param_list = ast_func.param_list()?;
200 let ref_mut_params = ref_mut_params(¶m_list);
201 let self_name: Option<String> = self_name(ast_func);
203 lines.push(format!("use {};", build_path(ast_func, ctx)?));
204 lines.push("".into());
205 if let Some(self_definition) = self_definition(ast_func, self_name.as_deref()) {
206 lines.push(self_definition);
208 for param_name in &ref_mut_params {
209 lines.push(format!("let mut {} = ;", param_name))
211 let function_call = function_call(ast_func, ¶m_list, self_name.as_deref(), is_unsafe)?;
212 let ex_helper = ExHelper { function_call, param_list, ref_mut_params, self_name };
213 Some((lines, ex_helper))
216 /// Checks if the function is public / exported
217 fn is_public(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<bool> {
218 let hir_func = ctx.sema.to_def(ast_func)?;
220 hir_func.visibility(ctx.db()) == Visibility::Public
221 && all_parent_mods_public(&hir_func, ctx),
225 /// Checks that all parent modules of the function are public / exported
226 fn all_parent_mods_public(hir_func: &hir::Function, ctx: &AssistContext) -> bool {
227 let mut module = hir_func.module(ctx.db());
229 if let Some(parent) = module.parent(ctx.db()) {
230 match ModuleDef::from(module).visibility(ctx.db()) {
231 Visibility::Public => module = parent,
240 /// Returns the name of the current crate
241 fn crate_name(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<String> {
242 let krate = ctx.sema.scope(&ast_func.syntax()).module()?.krate();
243 Some(krate.display_name(ctx.db())?.to_string())
246 /// `None` if function without a body; some bool to guess if function can panic
247 fn can_panic(ast_func: &ast::Fn) -> Option<bool> {
248 let body = ast_func.body()?.to_string();
249 let can_panic = body.contains("panic!(")
250 // FIXME it would be better to not match `debug_assert*!` macro invocations
251 || body.contains("assert!(")
252 || body.contains(".unwrap()")
253 || body.contains(".expect(");
257 /// Helper function to get the name that should be given to `self` arguments
258 fn self_name(ast_func: &ast::Fn) -> Option<String> {
259 self_partial_type(ast_func).map(|name| to_lower_snake_case(&name))
262 /// Heper function to get the name of the type of `self`
263 fn self_type(ast_func: &ast::Fn) -> Option<String> {
267 .find_map(ast::Impl::cast)
268 .and_then(|i| i.self_ty())
269 .map(|t| (t.to_string()))
272 /// Heper function to get the name of the type of `self` without generic arguments
273 fn self_partial_type(ast_func: &ast::Fn) -> Option<String> {
274 let mut self_type = self_type(ast_func)?;
275 if let Some(idx) = self_type.find(|c| ['<', ' '].contains(&c)) {
276 self_type.truncate(idx);
281 /// Helper function to determine if the function is in a trait implementation
282 fn is_in_trait_impl(ast_func: &ast::Fn, ctx: &AssistContext) -> bool {
285 .and_then(|hir_func| hir_func.as_assoc_item(ctx.db()))
286 .and_then(|assoc_item| assoc_item.containing_trait_impl(ctx.db()))
290 /// Helper function to determine if the function definition is in a trait definition
291 fn is_in_trait_def(ast_func: &ast::Fn, ctx: &AssistContext) -> bool {
294 .and_then(|hir_func| hir_func.as_assoc_item(ctx.db()))
295 .and_then(|assoc_item| assoc_item.containing_trait(ctx.db()))
299 /// Returns `None` if no `self` at all, `Some(true)` if there is `&mut self` else `Some(false)`
300 fn is_ref_mut_self(ast_func: &ast::Fn) -> Option<bool> {
301 let self_param = ast_func.param_list()?.self_param()?;
302 Some(self_param.mut_token().is_some() && self_param.amp_token().is_some())
305 /// Helper function to define an variable to be the `self` argument
306 fn self_definition(ast_func: &ast::Fn, self_name: Option<&str>) -> Option<String> {
307 let definition = match is_ref_mut_self(ast_func)? {
308 true => format!("let mut {} = ;", self_name?),
309 false => format!("let {} = ;", self_name?),
314 /// Helper function to determine if a parameter is `&mut`
315 fn is_a_ref_mut_param(param: &ast::Param) -> bool {
317 Some(ast::Type::RefType(param_ref)) => param_ref.mut_token().is_some(),
322 /// Helper function to build the list of `&mut` parameters
323 fn ref_mut_params(param_list: &ast::ParamList) -> Vec<String> {
326 .filter_map(|param| match is_a_ref_mut_param(¶m) {
327 // Maybe better filter the param name (to do this maybe extract a function from
328 // `arguments_from_params`?) in case of a `mut a: &mut T`. Anyway managing most (not
329 // all) cases might be enough, the goal is just to produce a template.
330 true => Some(param.pat()?.to_string()),
336 /// Helper function to build the comma-separated list of arguments of the function
337 fn arguments_from_params(param_list: &ast::ParamList) -> String {
338 let args_iter = param_list.params().map(|param| match param.pat() {
339 // To avoid `mut` in the function call (which would be a nonsense), `Pat` should not be
340 // written as is so its variants must be managed independently. Other variants (for
341 // instance `TuplePat`) could be managed later.
342 Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() {
343 Some(name) => match is_a_ref_mut_param(¶m) {
344 true => format!("&mut {}", name.to_string()),
345 false => name.to_string(),
347 None => "_".to_string(),
349 _ => "_".to_string(),
351 intersperse_string(args_iter, ", ")
354 /// Helper function to build a function call. `None` if expected `self_name` was not provided
357 param_list: &ast::ParamList,
358 self_name: Option<&str>,
360 ) -> Option<String> {
361 let name = ast_func.name()?;
362 let arguments = arguments_from_params(¶m_list);
363 let function_call = if param_list.self_param().is_some() {
364 format!("{}.{}({})", self_name?, name, arguments)
365 } else if let Some(implementation) = self_partial_type(ast_func) {
366 format!("{}::{}({})", implementation, name, arguments)
368 format!("{}({})", name, arguments)
371 true => Some(format!("unsafe {{ {} }}", function_call)),
372 false => Some(function_call),
376 /// Helper function to count the parameters including `self`
377 fn count_parameters(param_list: &ast::ParamList) -> usize {
378 param_list.params().count() + if param_list.self_param().is_some() { 1 } else { 0 }
381 /// Helper function to transform lines of documentation into a Rust code documentation
382 fn documentation_from_lines(doc_lines: Vec<String>, indent_level: IndentLevel) -> String {
383 let mut result = String::new();
384 for doc_line in doc_lines {
385 result.push_str("///");
386 if !doc_line.is_empty() {
388 result.push_str(&doc_line);
391 result.push_str(&indent_level.to_string());
396 /// Helper function to transform an array of borrowed strings to an owned `Vec<String>`
397 fn string_vec_from(string_array: &[&str]) -> Vec<String> {
398 string_array.iter().map(|&s| s.to_owned()).collect()
401 /// Helper function to build the path of the module in the which is the node
402 fn build_path(ast_func: &ast::Fn, ctx: &AssistContext) -> Option<String> {
403 let crate_name = crate_name(ast_func, ctx)?;
404 let leaf = self_partial_type(ast_func)
405 .or_else(|| ast_func.name().map(|n| n.to_string()))
406 .unwrap_or_else(|| "*".into());
407 let module_def: ModuleDef = ctx.sema.to_def(ast_func)?.module(ctx.db()).into();
408 match module_def.canonical_path(ctx.db()) {
409 Some(path) => Some(format!("{}::{}::{}", crate_name, path, leaf)),
410 None => Some(format!("{}::{}", crate_name, leaf)),
414 /// Helper function to get the return type of a function
415 fn return_type(ast_func: &ast::Fn) -> Option<ast::Type> {
416 ast_func.ret_type()?.ty()
419 /// Helper function to determine if the function returns some data
420 fn returns_a_value(ast_func: &ast::Fn) -> bool {
421 match return_type(ast_func) {
422 Some(ret_type) => !["()", "!"].contains(&ret_type.to_string().as_str()),
427 /// Helper function to concatenate string with a separator between them
428 fn intersperse_string(mut iter: impl Iterator<Item = String>, separator: &str) -> String {
429 let mut result = String::new();
430 if let Some(first) = iter.next() {
431 result.push_str(&first);
434 result.push_str(separator);
435 result.push_str(&string);
442 use crate::tests::{check_assist, check_assist_not_applicable};
447 fn not_applicable_on_function_calls() {
448 check_assist_not_applicable(
449 generate_documentation_template,
452 fn calls_hello_world() {
460 fn not_applicable_in_trait_impl() {
461 check_assist_not_applicable(
462 generate_documentation_template,
466 impl MyTrait for MyStruct {
474 fn not_applicable_if_function_is_private() {
475 check_assist_not_applicable(generate_documentation_template, r#"fn priv$0ate() {}"#);
479 fn not_applicable_if_function_is_pub_crate() {
480 check_assist_not_applicable(
481 generate_documentation_template,
482 r#"pub(crate) fn pri$0vate() {}"#,
487 fn not_applicable_if_function_is_in_private_mod() {
488 check_assist_not_applicable(
489 generate_documentation_template,
492 pub fn pri$0vate() {}
498 fn not_applicable_if_function_is_in_pub_crate_mod() {
499 check_assist_not_applicable(
500 generate_documentation_template,
502 pub(crate) mod PrivateModule {
503 pub fn pr$0ivate() {}
509 fn not_applicable_if_function_is_in_non_public_mod_is_recursive() {
510 check_assist_not_applicable(
511 generate_documentation_template,
513 mod ParentPrivateModule {
514 pub mod PrivateModule {
515 pub fn pr$0ivate() {}
522 fn not_applicable_if_function_already_documented() {
523 check_assist_not_applicable(
524 generate_documentation_template,
526 /// Some documentation here
527 pub fn $0documented_function() {}
533 fn supports_noop_function() {
535 generate_documentation_template,
555 fn supports_a_parameter() {
557 generate_documentation_template,
559 pub fn no$0op_with_param(_a: i32) {}
567 /// use test::noop_with_param;
569 /// noop_with_param(_a);
571 pub fn noop_with_param(_a: i32) {}
577 fn detects_unsafe_function() {
579 generate_documentation_template,
581 pub unsafe fn no$0op_unsafe() {}
589 /// use test::noop_unsafe;
591 /// unsafe { noop_unsafe() };
597 pub unsafe fn noop_unsafe() {}
603 fn guesses_panic_macro_can_panic() {
605 generate_documentation_template,
607 pub fn panic$0s_if(a: bool) {
619 /// use test::panics_if;
625 /// use test::panics_if;
627 /// panics_if(a); // panics
633 pub fn panics_if(a: bool) {
643 fn guesses_assert_macro_can_panic() {
645 generate_documentation_template,
647 pub fn $0panics_if_not(a: bool) {
657 /// use test::panics_if_not;
659 /// panics_if_not(a);
663 /// use test::panics_if_not;
665 /// panics_if_not(a); // panics
671 pub fn panics_if_not(a: bool) {
679 fn guesses_unwrap_can_panic() {
681 generate_documentation_template,
683 pub fn $0panics_if_none(a: Option<()>) {
693 /// use test::panics_if_none;
695 /// panics_if_none(a);
699 /// use test::panics_if_none;
701 /// panics_if_none(a); // panics
707 pub fn panics_if_none(a: Option<()>) {
715 fn guesses_expect_can_panic() {
717 generate_documentation_template,
719 pub fn $0panics_if_none2(a: Option<()>) {
729 /// use test::panics_if_none2;
731 /// panics_if_none2(a);
735 /// use test::panics_if_none2;
737 /// panics_if_none2(a); // panics
743 pub fn panics_if_none2(a: Option<()>) {
751 fn checks_output_in_example() {
753 generate_documentation_template,
755 pub fn returns_a_value$0() -> i32 {
765 /// use test::returns_a_value;
767 /// assert_eq!(returns_a_value(), );
769 pub fn returns_a_value() -> i32 {
777 fn detects_result_output() {
779 generate_documentation_template,
781 pub fn returns_a_result$0() -> Result<i32, std::io::Error> {
791 /// use test::returns_a_result;
793 /// assert_eq!(returns_a_result(), );
798 /// This function will return an error if .
799 pub fn returns_a_result() -> Result<i32, std::io::Error> {
807 fn checks_ref_mut_in_example() {
809 generate_documentation_template,
811 pub fn modifies_a_value$0(a: &mut i32) {
821 /// use test::modifies_a_value;
824 /// modifies_a_value(&mut a);
827 pub fn modifies_a_value(a: &mut i32) {
835 fn stores_result_if_at_least_3_params() {
837 generate_documentation_template,
839 pub fn sum3$0(a: i32, b: i32, c: i32) -> i32 {
851 /// let result = sum3(a, b, c);
852 /// assert_eq!(result, );
854 pub fn sum3(a: i32, b: i32, c: i32) -> i32 {
862 fn supports_fn_in_mods() {
864 generate_documentation_template,
880 /// use test::a::b::noop;
892 fn supports_fn_in_impl() {
894 generate_documentation_template,
909 /// use test::MyStruct;
911 /// MyStruct::noop();
920 fn supports_fn_in_trait() {
922 generate_documentation_template,
925 fn fun$0ction_trait();
935 /// // Example template not implemented for trait functions
944 fn supports_unsafe_fn_in_trait() {
946 generate_documentation_template,
949 unsafe fn unsafe_funct$0ion_trait();
959 /// // Example template not implemented for trait functions
965 unsafe fn unsafe_function_trait();
972 fn supports_fn_in_trait_with_default_panicking() {
974 generate_documentation_template,
977 fn function_trait_with_$0default_panicking() {
989 /// // Example template not implemented for trait functions
993 /// // Example template not implemented for trait functions
999 fn function_trait_with_default_panicking() {
1008 fn supports_fn_in_trait_returning_result() {
1010 generate_documentation_template,
1013 fn function_tr$0ait_returning_result() -> Result<(), std::io::Error>;
1023 /// // Example template not implemented for trait functions
1028 /// This function will return an error if .
1029 fn function_trait_returning_result() -> Result<(), std::io::Error>;
1038 generate_documentation_template,
1040 #[derive(Debug, PartialEq)]
1041 pub struct MyGenericStruct<T> {
1044 impl<T> MyGenericStruct<T> {
1045 pub fn new$0(x: T) -> MyGenericStruct<T> {
1046 MyGenericStruct { x }
1051 #[derive(Debug, PartialEq)]
1052 pub struct MyGenericStruct<T> {
1055 impl<T> MyGenericStruct<T> {
1056 /// Creates a new [`MyGenericStruct<T>`].
1061 /// use test::MyGenericStruct;
1063 /// assert_eq!(MyGenericStruct::new(x), );
1065 pub fn new(x: T) -> MyGenericStruct<T> {
1066 MyGenericStruct { x }
1074 fn detects_new_with_self() {
1076 generate_documentation_template,
1078 #[derive(Debug, PartialEq)]
1079 pub struct MyGenericStruct2<T> {
1082 impl<T> MyGenericStruct2<T> {
1083 pub fn new$0(x: T) -> Self {
1084 MyGenericStruct2 { x }
1089 #[derive(Debug, PartialEq)]
1090 pub struct MyGenericStruct2<T> {
1093 impl<T> MyGenericStruct2<T> {
1094 /// Creates a new [`MyGenericStruct2<T>`].
1099 /// use test::MyGenericStruct2;
1101 /// assert_eq!(MyGenericStruct2::new(x), );
1103 pub fn new(x: T) -> Self {
1104 MyGenericStruct2 { x }
1112 fn supports_method_call() {
1114 generate_documentation_template,
1116 impl<T> MyGenericStruct<T> {
1117 pub fn co$0nsume(self) {}
1121 impl<T> MyGenericStruct<T> {
1127 /// use test::MyGenericStruct;
1129 /// let my_generic_struct = ;
1130 /// my_generic_struct.consume();
1132 pub fn consume(self) {}
1139 fn checks_modified_self_param() {
1141 generate_documentation_template,
1143 impl<T> MyGenericStruct<T> {
1144 pub fn modi$0fy(&mut self, new_value: T) {
1150 impl<T> MyGenericStruct<T> {
1156 /// use test::MyGenericStruct;
1158 /// let mut my_generic_struct = ;
1159 /// my_generic_struct.modify(new_value);
1160 /// assert_eq!(my_generic_struct, );
1162 pub fn modify(&mut self, new_value: T) {