1 use ide_db::assists::{AssistId, AssistKind};
2 use stdx::to_lower_snake_case;
4 ast::{self, edit::IndentLevel, HasDocComments, HasName},
8 use crate::assist_context::{AssistContext, Assists};
10 /// Assist: generate_documentation_template
12 /// Adds a documentation template above a function definition / declaration
15 /// fn my_func(a: i32, b: i32) -> Result<(), std::io::Error> {
26 /// /// use my_crate::my_func;
28 /// /// let result = my_func(a, b);
29 /// /// assert_eq!(result, );
34 /// /// This function will return an error if .
35 /// 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 = ast::Fn::cast(name.syntax().parent()?)?;
45 if is_in_trait_impl(&ast_func) {
48 // TODO disable at least examples if function not public, as the example will fail to build on
49 // `cargo test`. What is the exact criteria of `pub`ness? All parent modules must be `pub`, for
50 // `impl { fn }` both `fn` and `struct`* must be public.
52 // What about `pub(crate)`?
54 // *: Seems complex but maybe ignoring this criteria can be ignored.
56 let parent_syntax = ast_func.syntax();
57 let text_range = parent_syntax.text_range();
58 let indent_level = IndentLevel::from_node(&parent_syntax);
60 ctx.sema.scope(&parent_syntax).module()?.krate().display_name(ctx.db())?.to_string();
63 AssistId("generate_documentation_template", AssistKind::Generate),
64 "Generate a documentation template",
67 let mut doc_lines = Vec::new();
68 // Introduction / short function description before the sections
69 doc_lines.push(introduction_builder(&ast_func));
70 // Then come the sections
71 if let Some(mut lines) = examples_builder(&ast_func, krate_name) {
72 doc_lines.push("".into());
73 doc_lines.append(&mut lines);
75 for section_builder in [panics_builder, errors_builder, safety_builder] {
76 if let Some(mut lines) = section_builder(&ast_func) {
77 doc_lines.push("".into());
78 doc_lines.append(&mut lines);
81 if ast_func.doc_comments().next().is_some() {
82 doc_lines.push("--- OLD VERSION BELOW ---".into());
84 builder.insert(text_range.start(), documentation_from_lines(doc_lines, indent_level));
89 /// Builds an introduction, trying to be smart if the function is `::new()`
90 fn introduction_builder(ast_func: &ast::Fn) -> String {
91 let is_new = ast_func.name().map(|name| &name.to_string() == "new").unwrap_or(false);
93 let ret_type = return_type(ast_func).map(|ret_type| ret_type.to_string());
94 let self_type = self_type(ast_func);
95 if ret_type.as_deref() == Some("Self") || ret_type == self_type {
96 if let Some(self_type) = self_type {
97 return format!("Creates a new [`{}`].", self_type);
104 /// Builds an `# Examples` section. An option is returned to be able to manage an error in the AST.
105 fn examples_builder(ast_func: &ast::Fn, krate_name: String) -> Option<Vec<String>> {
106 let (no_panic_ex, panic_ex) = if is_in_trait_def(ast_func) {
107 let message = "// Example template not implemented for trait functions";
108 (Some(vec![message.into()]), Some(vec![message.into()]))
110 let panic_ex = match can_panic(ast_func) {
111 Some(true) => gen_panic_ex_template(ast_func, krate_name.clone()),
114 let no_panic_ex = gen_ex_template(ast_func, krate_name);
115 (no_panic_ex, panic_ex)
118 let mut lines = string_vec_from(&["# Examples", "", "```"]);
119 lines.append(&mut no_panic_ex?);
120 lines.push("```".into());
121 if let Some(mut ex) = panic_ex {
122 lines.push("".into());
123 lines.push("```should_panic".into());
124 lines.append(&mut ex);
125 lines.push("```".into());
130 /// Builds an optional `# Panics` section
131 fn panics_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
132 match can_panic(ast_func) {
133 Some(true) => Some(string_vec_from(&["# Panics", "", "Panics if ."])),
138 /// Builds an optional `# Errors` section
139 fn errors_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
140 match return_type(ast_func)?.to_string().contains("Result") {
141 true => Some(string_vec_from(&["# Errors", "", "This function will return an error if ."])),
146 /// Builds an optional `# Safety` section
147 fn safety_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
148 let is_unsafe = ast_func.unsafe_token().is_some();
150 true => Some(string_vec_from(&["# Safety", "", "."])),
155 /// Generate an example template which should not panic
156 /// `None` if the function has a `self` parameter but is not in an `impl`.
157 fn gen_ex_template(ast_func: &ast::Fn, krate_name: String) -> Option<Vec<String>> {
158 let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, krate_name)?;
159 // Call the function, check result
160 if returns_a_value(ast_func) {
161 if count_parameters(&ex_helper.param_list) < 3 {
162 lines.push(format!("assert_eq!({}, );", ex_helper.function_call));
164 lines.push(format!("let result = {};", ex_helper.function_call));
165 lines.push("assert_eq!(result, );".into());
168 lines.push(format!("{};", ex_helper.function_call));
170 // Check the mutated values
171 if is_ref_mut_self(ast_func) == Some(true) {
172 lines.push(format!("assert_eq!({}, );", ex_helper.self_name?));
174 for param_name in &ex_helper.ref_mut_params {
175 lines.push(format!("assert_eq!({}, );", param_name));
180 /// Generate an example template which should panic
181 /// `None` if the function has a `self` parameter but is not in an `impl`.
182 fn gen_panic_ex_template(ast_func: &ast::Fn, krate_name: String) -> Option<Vec<String>> {
183 let (mut lines, ex_helper) = gen_ex_start_helper(ast_func, krate_name)?;
184 match returns_a_value(ast_func) {
185 true => lines.push(format!("let _ = {}; // panics", ex_helper.function_call)),
186 false => lines.push(format!("{}; // panics", ex_helper.function_call)),
191 /// Intermediary results of the start of example generation
193 function_call: String,
194 param_list: ast::ParamList,
195 ref_mut_params: Vec<String>,
196 self_name: Option<String>,
199 /// Build the start of the example and transmit the useful intermediary results.
200 /// `None` if the function has a `self` parameter but is not in an `impl`.
201 fn gen_ex_start_helper(ast_func: &ast::Fn, krate_name: String) -> Option<(Vec<String>, ExHelper)> {
202 let mut lines = Vec::new();
203 let is_unsafe = ast_func.unsafe_token().is_some();
204 let param_list = ast_func.param_list()?;
205 let ref_mut_params = ref_mut_params(¶m_list);
206 let self_name: Option<String> = self_name(ast_func);
208 lines.push(format!("use {};", build_path(ast_func, krate_name)));
209 lines.push("".into());
210 if let Some(self_definition) = self_definition(ast_func, self_name.as_deref()) {
211 lines.push(self_definition);
213 for param_name in &ref_mut_params {
214 lines.push(format!("let mut {} = ;", param_name))
216 let function_call = function_call(ast_func, ¶m_list, self_name.as_deref(), is_unsafe)?;
217 let ex_helper = ExHelper { function_call, param_list, ref_mut_params, self_name };
218 Some((lines, ex_helper))
221 /// `None` if function without a body; some bool to guess if function can panic
222 fn can_panic(ast_func: &ast::Fn) -> Option<bool> {
223 let body = ast_func.body()?.to_string();
224 let can_panic = body.contains("panic!(")
225 || body.contains("assert!(")
226 || body.contains(".unwrap()")
227 || body.contains(".expect(");
231 /// Helper function to get the name that should be given to `self` arguments
232 fn self_name(ast_func: &ast::Fn) -> Option<String> {
233 self_partial_type(ast_func).map(|name| to_lower_snake_case(&name))
236 /// Heper function to get the name of the type of `self`
237 fn self_type(ast_func: &ast::Fn) -> Option<String> {
241 .find_map(ast::Impl::cast)
242 .and_then(|i| i.self_ty())
243 .map(|t| (t.to_string()))
246 /// Heper function to get the name of the type of `self` without generic arguments
247 fn self_partial_type(ast_func: &ast::Fn) -> Option<String> {
248 let mut self_type = self_type(ast_func)?;
249 if let Some(idx) = self_type.find(|c| ['<', ' '].contains(&c)) {
250 self_type.truncate(idx);
255 /// Helper function to determine if the function is in a trait implementation
256 fn is_in_trait_impl(ast_func: &ast::Fn) -> bool {
260 .find_map(ast::Impl::cast)
261 .and_then(|impl_| impl_.trait_())
265 /// Helper function to determine if the function definition is in a trait definition
266 fn is_in_trait_def(ast_func: &ast::Fn) -> bool {
267 ast_func.syntax().ancestors().find_map(ast::Trait::cast).is_some()
270 /// Returns `None` if no `self` at all, `Some(true)` if there is `&mut self` else `Some(false)`
271 fn is_ref_mut_self(ast_func: &ast::Fn) -> Option<bool> {
272 let self_param = ast_func.param_list()?.self_param()?;
273 Some(self_param.mut_token().is_some() && self_param.amp_token().is_some())
276 /// Helper function to define an variable to be the `self` argument
277 fn self_definition(ast_func: &ast::Fn, self_name: Option<&str>) -> Option<String> {
278 let definition = match is_ref_mut_self(ast_func)? {
279 true => format!("let mut {} = ;", self_name?),
280 false => format!("let {} = ;", self_name?),
285 /// Helper function to determine if a parameter is `&mut`
286 fn is_a_ref_mut_param(param: &ast::Param) -> bool {
288 Some(ast::Type::RefType(param_ref)) => param_ref.mut_token().is_some(),
293 /// Helper function to build the list of `&mut` parameters
294 fn ref_mut_params(param_list: &ast::ParamList) -> Vec<String> {
297 .filter_map(|param| match is_a_ref_mut_param(¶m) {
298 // Maybe better filter the param name (to do this maybe extract a function from
299 // `arguments_from_params`?) in case of a `mut a: &mut T`. Anyway managing most (not
300 // all) cases might be enough, the goal is just to produce a template.
301 true => Some(param.pat()?.to_string()),
307 /// Helper function to build the comma-separated list of arguments of the function
308 fn arguments_from_params(param_list: &ast::ParamList) -> String {
309 let args_iter = param_list.params().map(|param| match param.pat() {
310 // To avoid `mut` in the function call (which would be a nonsense), `Pat` should not be
311 // written as is so its variants must be managed independently. Other variants (for
312 // instance `TuplePat`) could be managed later.
313 Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() {
314 Some(name) => match is_a_ref_mut_param(¶m) {
315 true => format!("&mut {}", name.to_string()),
316 false => name.to_string(),
318 None => "_".to_string(),
320 _ => "_".to_string(),
322 intersperse_string(args_iter, ", ")
325 /// Helper function to build a function call. `None` if expected `self_name` was not provided
328 param_list: &ast::ParamList,
329 self_name: Option<&str>,
331 ) -> Option<String> {
332 let name = ast_func.name()?;
333 let arguments = arguments_from_params(¶m_list);
334 let function_call = if param_list.self_param().is_some() {
335 format!("{}.{}({})", self_name?, name, arguments)
336 } else if let Some(implementation) = self_partial_type(ast_func) {
337 format!("{}::{}({})", implementation, name, arguments)
339 format!("{}({})", name, arguments)
342 true => Some(format!("unsafe {{ {} }}", function_call)),
343 false => Some(function_call),
347 /// Helper function to count the parameters including `self`
348 fn count_parameters(param_list: &ast::ParamList) -> usize {
349 param_list.params().count() + if param_list.self_param().is_some() { 1 } else { 0 }
352 /// Helper function to transform lines of documentation into a Rust code documentation
353 fn documentation_from_lines(doc_lines: Vec<String>, indent_level: IndentLevel) -> String {
354 let mut result = String::new();
355 for doc_line in doc_lines {
356 result.push_str("///");
357 if !doc_line.is_empty() {
359 result.push_str(&doc_line);
362 result.push_str(&indent_level.to_string());
367 /// Helper function to transform an array of borrowed strings to an owned `Vec<String>`
368 fn string_vec_from(string_array: &[&str]) -> Vec<String> {
369 string_array.iter().map(|&s| s.to_owned()).collect()
372 /// Helper function to build the path of the module in the which is the node
373 fn build_path(ast_func: &ast::Fn, krate_name: String) -> String {
374 let mut path: Vec<String> = ast_func
377 .filter_map(|m| ast::Module::cast(m).and_then(|m| m.name()))
378 .map(|m| m.to_string())
380 path.push(krate_name);
383 self_partial_type(ast_func)
384 .or_else(|| ast_func.name().map(|n| n.to_string()))
385 .unwrap_or_else(|| "*".into()),
387 intersperse_string(path.into_iter(), "::")
390 /// Helper function to get the return type of a function
391 fn return_type(ast_func: &ast::Fn) -> Option<ast::Type> {
392 ast_func.ret_type()?.ty()
395 /// Helper function to determine if the function returns some data
396 fn returns_a_value(ast_func: &ast::Fn) -> bool {
397 match return_type(ast_func) {
398 Some(ret_type) => !["()", "!"].contains(&ret_type.to_string().as_str()),
403 /// Helper function to concatenate string with a separator between them
404 fn intersperse_string(mut iter: impl Iterator<Item = String>, separator: &str) -> String {
405 let mut result = String::new();
406 if let Some(first) = iter.next() {
407 result.push_str(&first);
410 result.push_str(separator);
411 result.push_str(&string);
418 use crate::tests::{check_assist, check_assist_not_applicable};
423 fn not_applicable_on_function_calls() {
424 check_assist_not_applicable(
425 generate_documentation_template,
428 fn calls_hello_world() {
436 fn not_applicable_in_trait_impl() {
437 check_assist_not_applicable(
438 generate_documentation_template,
442 impl MyTrait for MyStruct {
450 fn supports_noop_function() {
452 generate_documentation_template,
472 fn supports_a_parameter() {
474 generate_documentation_template,
476 fn no$0op_with_param(_a: i32) {}
484 /// use test::noop_with_param;
486 /// noop_with_param(_a);
488 fn noop_with_param(_a: i32) {}
494 fn detects_unsafe_function() {
496 generate_documentation_template,
498 unsafe fn no$0op_unsafe() {}
506 /// use test::noop_unsafe;
508 /// unsafe { noop_unsafe() };
514 unsafe fn noop_unsafe() {}
520 fn guesses_panic_macro_can_panic() {
522 generate_documentation_template,
524 fn panic$0s_if(a: bool) {
536 /// use test::panics_if;
542 /// use test::panics_if;
544 /// panics_if(a); // panics
550 fn panics_if(a: bool) {
560 fn guesses_assert_macro_can_panic() {
562 generate_documentation_template,
564 fn $0panics_if_not(a: bool) {
574 /// use test::panics_if_not;
576 /// panics_if_not(a);
580 /// use test::panics_if_not;
582 /// panics_if_not(a); // panics
588 fn panics_if_not(a: bool) {
596 fn guesses_unwrap_can_panic() {
598 generate_documentation_template,
600 fn $0panics_if_none(a: Option<()>) {
610 /// use test::panics_if_none;
612 /// panics_if_none(a);
616 /// use test::panics_if_none;
618 /// panics_if_none(a); // panics
624 fn panics_if_none(a: Option<()>) {
632 fn guesses_expect_can_panic() {
634 generate_documentation_template,
636 fn $0panics_if_none2(a: Option<()>) {
646 /// use test::panics_if_none2;
648 /// panics_if_none2(a);
652 /// use test::panics_if_none2;
654 /// panics_if_none2(a); // panics
660 fn panics_if_none2(a: Option<()>) {
668 fn checks_output_in_example() {
670 generate_documentation_template,
672 fn returns_a_value$0() -> i32 {
682 /// use test::returns_a_value;
684 /// assert_eq!(returns_a_value(), );
686 fn returns_a_value() -> i32 {
694 fn detects_result_output() {
696 generate_documentation_template,
698 fn returns_a_result$0() -> Result<i32, std::io::Error> {
708 /// use test::returns_a_result;
710 /// assert_eq!(returns_a_result(), );
715 /// This function will return an error if .
716 fn returns_a_result() -> Result<i32, std::io::Error> {
724 fn checks_ref_mut_in_example() {
726 generate_documentation_template,
728 fn modifies_a_value$0(a: &mut i32) {
738 /// use test::modifies_a_value;
741 /// modifies_a_value(&mut a);
744 fn modifies_a_value(a: &mut i32) {
752 fn stores_result_if_at_least_3_params() {
754 generate_documentation_template,
756 fn sum3$0(a: i32, b: i32, c: i32) -> i32 {
768 /// let result = sum3(a, b, c);
769 /// assert_eq!(result, );
771 fn sum3(a: i32, b: i32, c: i32) -> i32 {
779 fn supports_fn_in_mods() {
781 generate_documentation_template,
797 /// use test::a::b::noop;
809 fn supports_fn_in_impl() {
811 generate_documentation_template,
826 /// use test::MyStruct;
828 /// MyStruct::noop();
839 generate_documentation_template,
841 #[derive(Debug, PartialEq)]
842 pub struct MyGenericStruct<T> {
845 impl<T> MyGenericStruct<T> {
846 pub fn new$0(x: T) -> MyGenericStruct<T> {
847 MyGenericStruct { x }
852 #[derive(Debug, PartialEq)]
853 pub struct MyGenericStruct<T> {
856 impl<T> MyGenericStruct<T> {
857 /// Creates a new [`MyGenericStruct<T>`].
862 /// use test::MyGenericStruct;
864 /// assert_eq!(MyGenericStruct::new(x), );
866 pub fn new(x: T) -> MyGenericStruct<T> {
867 MyGenericStruct { x }
875 fn detects_new_with_self() {
877 generate_documentation_template,
879 #[derive(Debug, PartialEq)]
880 pub struct MyGenericStruct2<T> {
883 impl<T> MyGenericStruct2<T> {
884 pub fn new$0(x: T) -> Self {
885 MyGenericStruct2 { x }
890 #[derive(Debug, PartialEq)]
891 pub struct MyGenericStruct2<T> {
894 impl<T> MyGenericStruct2<T> {
895 /// Creates a new [`MyGenericStruct2<T>`].
900 /// use test::MyGenericStruct2;
902 /// assert_eq!(MyGenericStruct2::new(x), );
904 pub fn new(x: T) -> Self {
905 MyGenericStruct2 { x }
913 fn supports_method_call() {
915 generate_documentation_template,
917 impl<T> MyGenericStruct<T> {
918 pub fn co$0nsume(self) {}
922 impl<T> MyGenericStruct<T> {
928 /// use test::MyGenericStruct;
930 /// let my_generic_struct = ;
931 /// my_generic_struct.consume();
933 pub fn consume(self) {}
940 fn checks_modified_self_param() {
942 generate_documentation_template,
944 impl<T> MyGenericStruct<T> {
945 pub fn modi$0fy(&mut self, new_value: T) {
951 impl<T> MyGenericStruct<T> {
957 /// use test::MyGenericStruct;
959 /// let mut my_generic_struct = ;
960 /// my_generic_struct.modify(new_value);
961 /// assert_eq!(my_generic_struct, );
963 pub fn modify(&mut self, new_value: T) {