1 use ide_db::assists::{AssistId, AssistKind};
2 use stdx::to_lower_snake_case;
4 ast::{self, edit::IndentLevel, HasDocComments, HasName, HasVisibility},
8 use crate::assist_context::{AssistContext, Assists};
10 // Assist: generate_documentation_template
12 // Adds a documentation template above a function definition / declaration.
15 // pub fn my_$0func(a: i32, b: i32) -> Result<(), std::io::Error> {
26 // /// use test::my_func;
28 // /// assert_eq!(my_func(a, b), );
33 // /// This function will return an error if .
34 // pub fn my_func(a: i32, b: i32) -> Result<(), std::io::Error> {
38 pub(crate) fn generate_documentation_template(
42 let name = ctx.find_node_at_offset::<ast::Name>()?;
43 let ast_func = name.syntax().parent().and_then(ast::Fn::cast)?;
44 if is_in_trait_impl(&ast_func)
45 || !is_public(&ast_func)
46 || ast_func.doc_comments().next().is_some()
51 let parent_syntax = ast_func.syntax();
52 let text_range = parent_syntax.text_range();
53 let indent_level = IndentLevel::from_node(&parent_syntax);
55 ctx.sema.scope(&parent_syntax).module()?.krate().display_name(ctx.db())?.to_string();
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));
65 // Then come the sections
66 if let Some(mut lines) = examples_builder(&ast_func, krate_name) {
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) -> String {
83 let is_new = ast_func.name().map(|name| &name.to_string() == "new").unwrap_or(false);
85 let ret_type = return_type(ast_func).map(|ret_type| ret_type.to_string());
86 let self_type = self_type(ast_func);
87 if ret_type.as_deref() == Some("Self") || ret_type == self_type {
88 if let Some(self_type) = self_type {
89 return format!("Creates a new [`{}`].", self_type);
96 /// Builds an `# Examples` section. An option is returned to be able to manage an error in the AST.
97 fn examples_builder(ast_func: &ast::Fn, krate_name: String) -> Option<Vec<String>> {
98 let (no_panic_ex, panic_ex) = if is_in_trait_def(ast_func) {
99 let message = "// Example template not implemented for trait functions";
100 (Some(vec![message.into()]), Some(vec![message.into()]))
102 let panic_ex = match can_panic(ast_func) {
103 Some(true) => gen_panic_ex_template(ast_func, krate_name.clone()),
106 let no_panic_ex = gen_ex_template(ast_func, krate_name);
107 (no_panic_ex, panic_ex)
110 let mut lines = string_vec_from(&["# Examples", "", "```"]);
111 lines.append(&mut no_panic_ex?);
112 lines.push("```".into());
113 if let Some(mut ex) = panic_ex {
114 lines.push("".into());
115 lines.push("```should_panic".into());
116 lines.append(&mut ex);
117 lines.push("```".into());
122 /// Builds an optional `# Panics` section
123 fn panics_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
124 match can_panic(ast_func) {
125 Some(true) => Some(string_vec_from(&["# Panics", "", "Panics if ."])),
130 /// Builds an optional `# Errors` section
131 fn errors_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
132 match return_type(ast_func)?.to_string().contains("Result") {
133 true => Some(string_vec_from(&["# Errors", "", "This function will return an error if ."])),
138 /// Builds an optional `# Safety` section
139 fn safety_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
140 let is_unsafe = ast_func.unsafe_token().is_some();
142 true => Some(string_vec_from(&["# Safety", "", "."])),
147 /// Generate an example template which should not panic
148 /// `None` if the function has a `self` parameter but is not in an `impl`.
149 fn gen_ex_template(ast_func: &ast::Fn, krate_name: String) -> Option<Vec<String>> {
150 let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, krate_name)?;
151 // Call the function, check result
152 if returns_a_value(ast_func) {
153 if count_parameters(&ex_helper.param_list) < 3 {
154 lines.push(format!("assert_eq!({}, );", ex_helper.function_call));
156 lines.push(format!("let result = {};", ex_helper.function_call));
157 lines.push("assert_eq!(result, );".into());
160 lines.push(format!("{};", ex_helper.function_call));
162 // Check the mutated values
163 if is_ref_mut_self(ast_func) == Some(true) {
164 lines.push(format!("assert_eq!({}, );", ex_helper.self_name?));
166 for param_name in &ex_helper.ref_mut_params {
167 lines.push(format!("assert_eq!({}, );", param_name));
172 /// Generate an example template which should panic
173 /// `None` if the function has a `self` parameter but is not in an `impl`.
174 fn gen_panic_ex_template(ast_func: &ast::Fn, krate_name: String) -> Option<Vec<String>> {
175 let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, krate_name)?;
176 match returns_a_value(ast_func) {
177 true => lines.push(format!("let _ = {}; // panics", ex_helper.function_call)),
178 false => lines.push(format!("{}; // panics", ex_helper.function_call)),
183 /// Intermediary results of the start of example generation
185 function_call: String,
186 param_list: ast::ParamList,
187 ref_mut_params: Vec<String>,
188 self_name: Option<String>,
191 /// Build the start of the example and transmit the useful intermediary results.
192 /// `None` if the function has a `self` parameter but is not in an `impl`.
193 fn gen_ex_start_helper(ast_func: &ast::Fn, krate_name: String) -> Option<(Vec<String>, ExHelper)> {
194 let mut lines = Vec::new();
195 let is_unsafe = ast_func.unsafe_token().is_some();
196 let param_list = ast_func.param_list()?;
197 let ref_mut_params = ref_mut_params(¶m_list);
198 let self_name: Option<String> = self_name(ast_func);
200 lines.push(format!("use {};", build_path(ast_func, krate_name)));
201 lines.push("".into());
202 if let Some(self_definition) = self_definition(ast_func, self_name.as_deref()) {
203 lines.push(self_definition);
205 for param_name in &ref_mut_params {
206 lines.push(format!("let mut {} = ;", param_name))
208 let function_call = function_call(ast_func, ¶m_list, self_name.as_deref(), is_unsafe)?;
209 let ex_helper = ExHelper { function_call, param_list, ref_mut_params, self_name };
210 Some((lines, ex_helper))
213 /// Check if the function and all its parent modules are exactly `pub`
214 fn is_public(ast_func: &ast::Fn) -> bool {
219 .filter_map(ast::Module::cast)
220 .all(|module| has_pub(&module))
223 /// Check if visibility is exactly `pub` (not `pub(crate)` for instance)
224 fn has_pub<T: HasVisibility>(item: &T) -> bool {
225 item.visibility().map(|v| v.path().is_none()).unwrap_or(false)
228 /// `None` if function without a body; some bool to guess if function can panic
229 fn can_panic(ast_func: &ast::Fn) -> Option<bool> {
230 let body = ast_func.body()?.to_string();
231 let can_panic = body.contains("panic!(")
232 || body.contains("assert!(")
233 || body.contains(".unwrap()")
234 || body.contains(".expect(");
238 /// Helper function to get the name that should be given to `self` arguments
239 fn self_name(ast_func: &ast::Fn) -> Option<String> {
240 self_partial_type(ast_func).map(|name| to_lower_snake_case(&name))
243 /// Heper function to get the name of the type of `self`
244 fn self_type(ast_func: &ast::Fn) -> Option<String> {
248 .find_map(ast::Impl::cast)
249 .and_then(|i| i.self_ty())
250 .map(|t| (t.to_string()))
253 /// Heper function to get the name of the type of `self` without generic arguments
254 fn self_partial_type(ast_func: &ast::Fn) -> Option<String> {
255 let mut self_type = self_type(ast_func)?;
256 if let Some(idx) = self_type.find(|c| ['<', ' '].contains(&c)) {
257 self_type.truncate(idx);
262 /// Helper function to determine if the function is in a trait implementation
263 fn is_in_trait_impl(ast_func: &ast::Fn) -> bool {
267 .find_map(ast::Impl::cast)
268 .and_then(|impl_| impl_.trait_())
272 /// Helper function to determine if the function definition is in a trait definition
273 fn is_in_trait_def(ast_func: &ast::Fn) -> bool {
274 ast_func.syntax().ancestors().find_map(ast::Trait::cast).is_some()
277 /// Returns `None` if no `self` at all, `Some(true)` if there is `&mut self` else `Some(false)`
278 fn is_ref_mut_self(ast_func: &ast::Fn) -> Option<bool> {
279 let self_param = ast_func.param_list()?.self_param()?;
280 Some(self_param.mut_token().is_some() && self_param.amp_token().is_some())
283 /// Helper function to define an variable to be the `self` argument
284 fn self_definition(ast_func: &ast::Fn, self_name: Option<&str>) -> Option<String> {
285 let definition = match is_ref_mut_self(ast_func)? {
286 true => format!("let mut {} = ;", self_name?),
287 false => format!("let {} = ;", self_name?),
292 /// Helper function to determine if a parameter is `&mut`
293 fn is_a_ref_mut_param(param: &ast::Param) -> bool {
295 Some(ast::Type::RefType(param_ref)) => param_ref.mut_token().is_some(),
300 /// Helper function to build the list of `&mut` parameters
301 fn ref_mut_params(param_list: &ast::ParamList) -> Vec<String> {
304 .filter_map(|param| match is_a_ref_mut_param(¶m) {
305 // Maybe better filter the param name (to do this maybe extract a function from
306 // `arguments_from_params`?) in case of a `mut a: &mut T`. Anyway managing most (not
307 // all) cases might be enough, the goal is just to produce a template.
308 true => Some(param.pat()?.to_string()),
314 /// Helper function to build the comma-separated list of arguments of the function
315 fn arguments_from_params(param_list: &ast::ParamList) -> String {
316 let args_iter = param_list.params().map(|param| match param.pat() {
317 // To avoid `mut` in the function call (which would be a nonsense), `Pat` should not be
318 // written as is so its variants must be managed independently. Other variants (for
319 // instance `TuplePat`) could be managed later.
320 Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() {
321 Some(name) => match is_a_ref_mut_param(¶m) {
322 true => format!("&mut {}", name.to_string()),
323 false => name.to_string(),
325 None => "_".to_string(),
327 _ => "_".to_string(),
329 intersperse_string(args_iter, ", ")
332 /// Helper function to build a function call. `None` if expected `self_name` was not provided
335 param_list: &ast::ParamList,
336 self_name: Option<&str>,
338 ) -> Option<String> {
339 let name = ast_func.name()?;
340 let arguments = arguments_from_params(¶m_list);
341 let function_call = if param_list.self_param().is_some() {
342 format!("{}.{}({})", self_name?, name, arguments)
343 } else if let Some(implementation) = self_partial_type(ast_func) {
344 format!("{}::{}({})", implementation, name, arguments)
346 format!("{}({})", name, arguments)
349 true => Some(format!("unsafe {{ {} }}", function_call)),
350 false => Some(function_call),
354 /// Helper function to count the parameters including `self`
355 fn count_parameters(param_list: &ast::ParamList) -> usize {
356 param_list.params().count() + if param_list.self_param().is_some() { 1 } else { 0 }
359 /// Helper function to transform lines of documentation into a Rust code documentation
360 fn documentation_from_lines(doc_lines: Vec<String>, indent_level: IndentLevel) -> String {
361 let mut result = String::new();
362 for doc_line in doc_lines {
363 result.push_str("///");
364 if !doc_line.is_empty() {
366 result.push_str(&doc_line);
369 result.push_str(&indent_level.to_string());
374 /// Helper function to transform an array of borrowed strings to an owned `Vec<String>`
375 fn string_vec_from(string_array: &[&str]) -> Vec<String> {
376 string_array.iter().map(|&s| s.to_owned()).collect()
379 /// Helper function to build the path of the module in the which is the node
380 fn build_path(ast_func: &ast::Fn, krate_name: String) -> String {
381 let mut path: Vec<String> = ast_func
384 .filter_map(|m| ast::Module::cast(m).and_then(|m| m.name()))
385 .map(|m| m.to_string())
387 path.push(krate_name);
390 self_partial_type(ast_func)
391 .or_else(|| ast_func.name().map(|n| n.to_string()))
392 .unwrap_or_else(|| "*".into()),
394 intersperse_string(path.into_iter(), "::")
397 /// Helper function to get the return type of a function
398 fn return_type(ast_func: &ast::Fn) -> Option<ast::Type> {
399 ast_func.ret_type()?.ty()
402 /// Helper function to determine if the function returns some data
403 fn returns_a_value(ast_func: &ast::Fn) -> bool {
404 match return_type(ast_func) {
405 Some(ret_type) => !["()", "!"].contains(&ret_type.to_string().as_str()),
410 /// Helper function to concatenate string with a separator between them
411 fn intersperse_string(mut iter: impl Iterator<Item = String>, separator: &str) -> String {
412 let mut result = String::new();
413 if let Some(first) = iter.next() {
414 result.push_str(&first);
417 result.push_str(separator);
418 result.push_str(&string);
425 use crate::tests::{check_assist, check_assist_not_applicable};
430 fn not_applicable_on_function_calls() {
431 check_assist_not_applicable(
432 generate_documentation_template,
435 fn calls_hello_world() {
443 fn not_applicable_in_trait_impl() {
444 check_assist_not_applicable(
445 generate_documentation_template,
449 impl MyTrait for MyStruct {
457 fn not_applicable_if_function_is_private() {
458 check_assist_not_applicable(generate_documentation_template, r#"fn priv$0ate() {}"#);
462 fn not_applicable_if_function_is_pub_crate() {
463 check_assist_not_applicable(
464 generate_documentation_template,
465 r#"pub(crate) fn pri$0vate() {}"#,
470 fn not_applicable_if_function_is_in_private_mod() {
471 check_assist_not_applicable(
472 generate_documentation_template,
475 pub fn pri$0vate() {}
481 fn not_applicable_if_function_is_in_pub_crate_mod() {
482 check_assist_not_applicable(
483 generate_documentation_template,
485 pub(crate) mod PrivateModule {
486 pub fn pr$0ivate() {}
492 fn not_applicable_if_function_is_in_non_public_mod_is_recursive() {
493 check_assist_not_applicable(
494 generate_documentation_template,
496 mod ParentPrivateModule {
497 pub mod PrivateModule {
498 pub fn pr$0ivate() {}
505 fn not_applicable_if_function_already_documented() {
506 check_assist_not_applicable(
507 generate_documentation_template,
509 /// Some documentation here
510 pub fn $0documented_function() {}
516 fn supports_noop_function() {
518 generate_documentation_template,
538 fn supports_a_parameter() {
540 generate_documentation_template,
542 pub fn no$0op_with_param(_a: i32) {}
550 /// use test::noop_with_param;
552 /// noop_with_param(_a);
554 pub fn noop_with_param(_a: i32) {}
560 fn detects_unsafe_function() {
562 generate_documentation_template,
564 pub unsafe fn no$0op_unsafe() {}
572 /// use test::noop_unsafe;
574 /// unsafe { noop_unsafe() };
580 pub unsafe fn noop_unsafe() {}
586 fn guesses_panic_macro_can_panic() {
588 generate_documentation_template,
590 pub fn panic$0s_if(a: bool) {
602 /// use test::panics_if;
608 /// use test::panics_if;
610 /// panics_if(a); // panics
616 pub fn panics_if(a: bool) {
626 fn guesses_assert_macro_can_panic() {
628 generate_documentation_template,
630 pub fn $0panics_if_not(a: bool) {
640 /// use test::panics_if_not;
642 /// panics_if_not(a);
646 /// use test::panics_if_not;
648 /// panics_if_not(a); // panics
654 pub fn panics_if_not(a: bool) {
662 fn guesses_unwrap_can_panic() {
664 generate_documentation_template,
666 pub fn $0panics_if_none(a: Option<()>) {
676 /// use test::panics_if_none;
678 /// panics_if_none(a);
682 /// use test::panics_if_none;
684 /// panics_if_none(a); // panics
690 pub fn panics_if_none(a: Option<()>) {
698 fn guesses_expect_can_panic() {
700 generate_documentation_template,
702 pub fn $0panics_if_none2(a: Option<()>) {
712 /// use test::panics_if_none2;
714 /// panics_if_none2(a);
718 /// use test::panics_if_none2;
720 /// panics_if_none2(a); // panics
726 pub fn panics_if_none2(a: Option<()>) {
734 fn checks_output_in_example() {
736 generate_documentation_template,
738 pub fn returns_a_value$0() -> i32 {
748 /// use test::returns_a_value;
750 /// assert_eq!(returns_a_value(), );
752 pub fn returns_a_value() -> i32 {
760 fn detects_result_output() {
762 generate_documentation_template,
764 pub fn returns_a_result$0() -> Result<i32, std::io::Error> {
774 /// use test::returns_a_result;
776 /// assert_eq!(returns_a_result(), );
781 /// This function will return an error if .
782 pub fn returns_a_result() -> Result<i32, std::io::Error> {
790 fn checks_ref_mut_in_example() {
792 generate_documentation_template,
794 pub fn modifies_a_value$0(a: &mut i32) {
804 /// use test::modifies_a_value;
807 /// modifies_a_value(&mut a);
810 pub fn modifies_a_value(a: &mut i32) {
818 fn stores_result_if_at_least_3_params() {
820 generate_documentation_template,
822 pub fn sum3$0(a: i32, b: i32, c: i32) -> i32 {
834 /// let result = sum3(a, b, c);
835 /// assert_eq!(result, );
837 pub fn sum3(a: i32, b: i32, c: i32) -> i32 {
845 fn supports_fn_in_mods() {
847 generate_documentation_template,
863 /// use test::a::b::noop;
875 fn supports_fn_in_impl() {
877 generate_documentation_template,
892 /// use test::MyStruct;
894 /// MyStruct::noop();
905 generate_documentation_template,
907 #[derive(Debug, PartialEq)]
908 pub struct MyGenericStruct<T> {
911 impl<T> MyGenericStruct<T> {
912 pub fn new$0(x: T) -> MyGenericStruct<T> {
913 MyGenericStruct { x }
918 #[derive(Debug, PartialEq)]
919 pub struct MyGenericStruct<T> {
922 impl<T> MyGenericStruct<T> {
923 /// Creates a new [`MyGenericStruct<T>`].
928 /// use test::MyGenericStruct;
930 /// assert_eq!(MyGenericStruct::new(x), );
932 pub fn new(x: T) -> MyGenericStruct<T> {
933 MyGenericStruct { x }
941 fn detects_new_with_self() {
943 generate_documentation_template,
945 #[derive(Debug, PartialEq)]
946 pub struct MyGenericStruct2<T> {
949 impl<T> MyGenericStruct2<T> {
950 pub fn new$0(x: T) -> Self {
951 MyGenericStruct2 { x }
956 #[derive(Debug, PartialEq)]
957 pub struct MyGenericStruct2<T> {
960 impl<T> MyGenericStruct2<T> {
961 /// Creates a new [`MyGenericStruct2<T>`].
966 /// use test::MyGenericStruct2;
968 /// assert_eq!(MyGenericStruct2::new(x), );
970 pub fn new(x: T) -> Self {
971 MyGenericStruct2 { x }
979 fn supports_method_call() {
981 generate_documentation_template,
983 impl<T> MyGenericStruct<T> {
984 pub fn co$0nsume(self) {}
988 impl<T> MyGenericStruct<T> {
994 /// use test::MyGenericStruct;
996 /// let my_generic_struct = ;
997 /// my_generic_struct.consume();
999 pub fn consume(self) {}
1006 fn checks_modified_self_param() {
1008 generate_documentation_template,
1010 impl<T> MyGenericStruct<T> {
1011 pub fn modi$0fy(&mut self, new_value: T) {
1017 impl<T> MyGenericStruct<T> {
1023 /// use test::MyGenericStruct;
1025 /// let mut my_generic_struct = ;
1026 /// my_generic_struct.modify(new_value);
1027 /// assert_eq!(my_generic_struct, );
1029 pub fn modify(&mut self, new_value: T) {