ra_ap_ide_assists/handlers/
generate_documentation_template.rs

1use hir::{AsAssocItem, HasVisibility, ModuleDef, Visibility};
2use ide_db::assists::AssistId;
3use itertools::Itertools;
4use stdx::{format_to, to_lower_snake_case};
5use syntax::{
6    AstNode, AstToken, Edition,
7    algo::skip_whitespace_token,
8    ast::{self, HasDocComments, HasGenericArgs, HasName, edit::IndentLevel},
9    match_ast,
10};
11
12use crate::assist_context::{AssistContext, Assists};
13
14// Assist: generate_documentation_template
15//
16// Adds a documentation template above a function definition / declaration.
17//
18// ```
19// pub struct S;
20// impl S {
21//     pub unsafe fn set_len$0(&mut self, len: usize) -> Result<(), std::io::Error> {
22//         /* ... */
23//     }
24// }
25// ```
26// ->
27// ```
28// pub struct S;
29// impl S {
30//     /// Sets the length of this [`S`].
31//     ///
32//     /// # Errors
33//     ///
34//     /// This function will return an error if .
35//     ///
36//     /// # Safety
37//     ///
38//     /// .
39//     pub unsafe fn set_len(&mut self, len: usize) -> Result<(), std::io::Error> {
40//         /* ... */
41//     }
42// }
43// ```
44pub(crate) fn generate_documentation_template(
45    acc: &mut Assists,
46    ctx: &AssistContext<'_>,
47) -> Option<()> {
48    let name = ctx.find_node_at_offset::<ast::Name>()?;
49    let ast_func = name.syntax().parent().and_then(ast::Fn::cast)?;
50    if is_in_trait_impl(&ast_func, ctx) || ast_func.doc_comments().next().is_some() {
51        return None;
52    }
53
54    let parent_syntax = ast_func.syntax();
55    let text_range = parent_syntax.text_range();
56    let indent_level = IndentLevel::from_node(parent_syntax);
57
58    acc.add(
59        AssistId::generate("generate_documentation_template"),
60        "Generate a documentation template",
61        text_range,
62        |builder| {
63            // Introduction / short function description before the sections
64            let mut doc_lines = vec![introduction_builder(&ast_func, ctx).unwrap_or(".".into())];
65            // Then come the sections
66            for section_builder in [panics_builder, errors_builder, safety_builder] {
67                if let Some(mut lines) = section_builder(&ast_func) {
68                    doc_lines.push("".into());
69                    doc_lines.append(&mut lines);
70                }
71            }
72            builder.insert(text_range.start(), documentation_from_lines(doc_lines, indent_level));
73        },
74    )
75}
76
77// Assist: generate_doc_example
78//
79// Generates a rustdoc example when editing an item's documentation.
80//
81// ```
82// /// Adds two numbers.$0
83// pub fn add(a: i32, b: i32) -> i32 { a + b }
84// ```
85// ->
86// ```
87// /// Adds two numbers.
88// ///
89// /// # Examples
90// ///
91// /// ```
92// /// use ra_test_fixture::add;
93// ///
94// /// assert_eq!(add(a, b), );
95// /// ```
96// pub fn add(a: i32, b: i32) -> i32 { a + b }
97// ```
98pub(crate) fn generate_doc_example(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
99    let tok: ast::Comment = ctx.find_token_at_offset()?;
100    let node = tok.syntax().parent()?;
101    let last_doc_token =
102        ast::AnyHasDocComments::cast(node.clone())?.doc_comments().last()?.syntax().clone();
103    let next_token = skip_whitespace_token(last_doc_token.next_token()?, syntax::Direction::Next)?;
104
105    let example = match_ast! {
106        match node {
107            ast::Fn(it) => make_example_for_fn(&it, ctx)?,
108            _ => return None,
109        }
110    };
111
112    let mut lines = string_vec_from(&["", "# Examples", "", "```"]);
113    lines.extend(example.lines().map(String::from));
114    lines.push("```".into());
115    let indent_level = IndentLevel::from_node(&node);
116
117    acc.add(
118        AssistId::generate("generate_doc_example"),
119        "Generate a documentation example",
120        node.text_range(),
121        |builder| {
122            builder.insert(
123                next_token.text_range().start(),
124                documentation_from_lines(lines, indent_level),
125            );
126        },
127    )
128}
129
130fn make_example_for_fn(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> {
131    if !is_public(ast_func, ctx)? {
132        // Doctests for private items can't actually name the item, so they're pretty useless.
133        return None;
134    }
135
136    if is_in_trait_def(ast_func, ctx) {
137        // This is not yet implemented.
138        return None;
139    }
140
141    let mut example = String::new();
142
143    let edition = ctx.sema.scope(ast_func.syntax())?.krate().edition(ctx.db());
144    let use_path = build_path(ast_func, ctx, edition)?;
145    let is_unsafe = ast_func.unsafe_token().is_some();
146    let param_list = ast_func.param_list()?;
147    let ref_mut_params = ref_mut_params(&param_list);
148    let self_name = self_name(ast_func);
149
150    format_to!(example, "use {use_path};\n\n");
151    if let Some(self_name) = &self_name
152        && let Some(mut_) = is_ref_mut_self(ast_func)
153    {
154        let mut_ = if mut_ { "mut " } else { "" };
155        format_to!(example, "let {mut_}{self_name} = ;\n");
156    }
157    for param_name in &ref_mut_params {
158        format_to!(example, "let mut {param_name} = ;\n");
159    }
160    // Call the function, check result
161    let function_call = function_call(ast_func, &param_list, self_name.as_deref(), is_unsafe)?;
162    if returns_a_value(ast_func, ctx) {
163        if count_parameters(&param_list) < 3 {
164            format_to!(example, "assert_eq!({function_call}, );\n");
165        } else {
166            format_to!(example, "let result = {function_call};\n");
167            example.push_str("assert_eq!(result, );\n");
168        }
169    } else {
170        format_to!(example, "{function_call};\n");
171    }
172    // Check the mutated values
173    if let Some(self_name) = &self_name
174        && is_ref_mut_self(ast_func) == Some(true)
175    {
176        format_to!(example, "assert_eq!({self_name}, );");
177    }
178    for param_name in &ref_mut_params {
179        format_to!(example, "assert_eq!({param_name}, );");
180    }
181
182    Some(example)
183}
184
185fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> {
186    let hir_func = ctx.sema.to_def(ast_func)?;
187    let container = hir_func.as_assoc_item(ctx.db())?.container(ctx.db());
188    if let hir::AssocItemContainer::Impl(imp) = container {
189        let ret_ty = hir_func.ret_type(ctx.db());
190        let self_ty = imp.self_ty(ctx.db());
191        let name = ast_func.name()?.to_string();
192        let linkable_self_ty = self_type_without_lifetimes(ast_func);
193        let linkable_self_ty = linkable_self_ty.as_deref();
194
195        let intro_for_new = || {
196            let is_new = name == "new";
197            if is_new && ret_ty == self_ty {
198                let self_ty = linkable_self_ty?;
199                Some(format!("Creates a new [`{self_ty}`]."))
200            } else {
201                None
202            }
203        };
204
205        let intro_for_getter = || match (
206            hir_func.self_param(ctx.sema.db),
207            &*hir_func.params_without_self(ctx.sema.db),
208        ) {
209            (Some(self_param), []) if self_param.access(ctx.sema.db) != hir::Access::Owned => {
210                if name.starts_with("as_") || name.starts_with("to_") || name == "get" {
211                    return None;
212                }
213                let mut what = name.trim_end_matches("_mut").replace('_', " ");
214                if what == "len" {
215                    what = "length".into()
216                }
217                let reference = if ret_ty.is_mutable_reference() {
218                    " a mutable reference to"
219                } else if ret_ty.is_reference() {
220                    " a reference to"
221                } else {
222                    ""
223                };
224
225                let self_ty = linkable_self_ty?;
226                Some(format!("Returns{reference} the {what} of this [`{self_ty}`]."))
227            }
228            _ => None,
229        };
230
231        let intro_for_setter = || {
232            if !name.starts_with("set_") {
233                return None;
234            }
235
236            let mut what = name.trim_start_matches("set_").replace('_', " ");
237            if what == "len" {
238                what = "length".into()
239            };
240
241            let self_ty = linkable_self_ty?;
242            Some(format!("Sets the {what} of this [`{self_ty}`]."))
243        };
244
245        if let Some(intro) = intro_for_new() {
246            return Some(intro);
247        }
248        if let Some(intro) = intro_for_getter() {
249            return Some(intro);
250        }
251        if let Some(intro) = intro_for_setter() {
252            return Some(intro);
253        }
254    }
255    None
256}
257
258/// Builds an optional `# Panics` section
259fn panics_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
260    match can_panic(ast_func) {
261        Some(true) => Some(string_vec_from(&["# Panics", "", "Panics if ."])),
262        _ => None,
263    }
264}
265
266/// Builds an optional `# Errors` section
267fn errors_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
268    match return_type(ast_func)?.to_string().contains("Result") {
269        true => Some(string_vec_from(&["# Errors", "", "This function will return an error if ."])),
270        false => None,
271    }
272}
273
274/// Builds an optional `# Safety` section
275fn safety_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
276    let is_unsafe = ast_func.unsafe_token().is_some();
277    match is_unsafe {
278        true => Some(string_vec_from(&["# Safety", "", "."])),
279        false => None,
280    }
281}
282
283/// Checks if the function is public / exported
284fn is_public(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<bool> {
285    let hir_func = ctx.sema.to_def(ast_func)?;
286    Some(
287        hir_func.visibility(ctx.db()) == Visibility::Public
288            && all_parent_mods_public(&hir_func, ctx),
289    )
290}
291
292/// Checks that all parent modules of the function are public / exported
293fn all_parent_mods_public(hir_func: &hir::Function, ctx: &AssistContext<'_>) -> bool {
294    let mut module = hir_func.module(ctx.db());
295    loop {
296        if let Some(parent) = module.parent(ctx.db()) {
297            match ModuleDef::from(module).visibility(ctx.db()) {
298                Visibility::Public => module = parent,
299                _ => break false,
300            }
301        } else {
302            break true;
303        }
304    }
305}
306
307/// Returns the name of the current crate
308fn crate_name(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> {
309    let krate = ctx.sema.scope(ast_func.syntax())?.krate();
310    Some(krate.display_name(ctx.db())?.to_string())
311}
312
313/// `None` if function without a body; some bool to guess if function can panic
314fn can_panic(ast_func: &ast::Fn) -> Option<bool> {
315    let body = ast_func.body()?.to_string();
316    let mut iter = body.chars();
317    let assert_postfix = |s| {
318        ["!(", "_eq!(", "_ne!(", "_matches!("].iter().any(|postfix| str::starts_with(s, postfix))
319    };
320
321    while !iter.as_str().is_empty() {
322        let s = iter.as_str();
323        iter.next();
324        if s.strip_prefix("debug_assert").is_some_and(assert_postfix) {
325            iter.nth(10);
326            continue;
327        }
328        if s.strip_prefix("assert").is_some_and(assert_postfix)
329            || s.starts_with("panic!(")
330            || s.starts_with(".unwrap()")
331            || s.starts_with(".expect(")
332        {
333            return Some(true);
334        }
335    }
336
337    Some(false)
338}
339
340/// Helper function to get the name that should be given to `self` arguments
341fn self_name(ast_func: &ast::Fn) -> Option<String> {
342    self_partial_type(ast_func).map(|name| to_lower_snake_case(&name))
343}
344
345/// Helper function to get the name of the type of `self`
346fn self_type(ast_func: &ast::Fn) -> Option<ast::Type> {
347    ast_func.syntax().ancestors().find_map(ast::Impl::cast).and_then(|i| i.self_ty())
348}
349
350/// Output the real name of `Self` like `MyType<T>`, without the lifetimes.
351fn self_type_without_lifetimes(ast_func: &ast::Fn) -> Option<String> {
352    let path_segment = match self_type(ast_func)? {
353        ast::Type::PathType(path_type) => path_type.path()?.segment()?,
354        _ => return None,
355    };
356    let mut name = path_segment.name_ref()?.to_string();
357    let generics = path_segment.generic_arg_list().into_iter().flat_map(|list| {
358        list.generic_args()
359            .filter(|generic| matches!(generic, ast::GenericArg::TypeArg(_)))
360            .map(|generic| generic.to_string())
361    });
362    let generics: String = generics.format(", ").to_string();
363    if !generics.is_empty() {
364        name.push('<');
365        name.push_str(&generics);
366        name.push('>');
367    }
368    Some(name)
369}
370
371/// Helper function to get the name of the type of `self` without generic arguments
372fn self_partial_type(ast_func: &ast::Fn) -> Option<String> {
373    let mut self_type = self_type(ast_func)?.to_string();
374    if let Some(idx) = self_type.find(|c| ['<', ' '].contains(&c)) {
375        self_type.truncate(idx);
376    }
377    Some(self_type)
378}
379
380/// Helper function to determine if the function is in a trait implementation
381fn is_in_trait_impl(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> bool {
382    ctx.sema
383        .to_def(ast_func)
384        .and_then(|hir_func| hir_func.as_assoc_item(ctx.db()))
385        .and_then(|assoc_item| assoc_item.implemented_trait(ctx.db()))
386        .is_some()
387}
388
389/// Helper function to determine if the function definition is in a trait definition
390fn is_in_trait_def(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> bool {
391    ctx.sema
392        .to_def(ast_func)
393        .and_then(|hir_func| hir_func.as_assoc_item(ctx.db()))
394        .and_then(|assoc_item| assoc_item.container_trait(ctx.db()))
395        .is_some()
396}
397
398/// Returns `None` if no `self` at all, `Some(true)` if there is `&mut self` else `Some(false)`
399fn is_ref_mut_self(ast_func: &ast::Fn) -> Option<bool> {
400    let self_param = ast_func.param_list()?.self_param()?;
401    Some(self_param.mut_token().is_some() && self_param.amp_token().is_some())
402}
403
404/// Helper function to determine if a parameter is `&mut`
405fn is_a_ref_mut_param(param: &ast::Param) -> bool {
406    match param.ty() {
407        Some(ast::Type::RefType(param_ref)) => param_ref.mut_token().is_some(),
408        _ => false,
409    }
410}
411
412/// Helper function to build the list of `&mut` parameters
413fn ref_mut_params(param_list: &ast::ParamList) -> Vec<String> {
414    param_list
415        .params()
416        .filter_map(|param| match is_a_ref_mut_param(&param) {
417            // Maybe better filter the param name (to do this maybe extract a function from
418            // `arguments_from_params`?) in case of a `mut a: &mut T`. Anyway managing most (not
419            // all) cases might be enough, the goal is just to produce a template.
420            true => Some(param.pat()?.to_string()),
421            false => None,
422        })
423        .collect()
424}
425
426/// Helper function to build the comma-separated list of arguments of the function
427fn arguments_from_params(param_list: &ast::ParamList) -> String {
428    let args_iter = param_list.params().map(|param| match param.pat() {
429        // To avoid `mut` in the function call (which would be a nonsense), `Pat` should not be
430        // written as is so its variants must be managed independently. Other variants (for
431        // instance `TuplePat`) could be managed later.
432        Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() {
433            Some(name) => match is_a_ref_mut_param(&param) {
434                true => format!("&mut {name}"),
435                false => name.to_string(),
436            },
437            None => "_".to_owned(),
438        },
439        _ => "_".to_owned(),
440    });
441    args_iter.format(", ").to_string()
442}
443
444/// Helper function to build a function call. `None` if expected `self_name` was not provided
445fn function_call(
446    ast_func: &ast::Fn,
447    param_list: &ast::ParamList,
448    self_name: Option<&str>,
449    is_unsafe: bool,
450) -> Option<String> {
451    let name = ast_func.name()?;
452    let arguments = arguments_from_params(param_list);
453    let function_call = if param_list.self_param().is_some() {
454        let self_ = self_name?;
455        format!("{self_}.{name}({arguments})")
456    } else if let Some(implementation) = self_partial_type(ast_func) {
457        format!("{implementation}::{name}({arguments})")
458    } else {
459        format!("{name}({arguments})")
460    };
461    match is_unsafe {
462        true => Some(format!("unsafe {{ {function_call} }}")),
463        false => Some(function_call),
464    }
465}
466
467/// Helper function to count the parameters including `self`
468fn count_parameters(param_list: &ast::ParamList) -> usize {
469    param_list.params().count() + if param_list.self_param().is_some() { 1 } else { 0 }
470}
471
472/// Helper function to transform lines of documentation into a Rust code documentation
473fn documentation_from_lines(doc_lines: Vec<String>, indent_level: IndentLevel) -> String {
474    let mut result = String::new();
475    for doc_line in doc_lines {
476        result.push_str("///");
477        if !doc_line.is_empty() {
478            result.push(' ');
479            result.push_str(&doc_line);
480        }
481        result.push('\n');
482        result.push_str(&indent_level.to_string());
483    }
484    result
485}
486
487/// Helper function to transform an array of borrowed strings to an owned `Vec<String>`
488fn string_vec_from(string_array: &[&str]) -> Vec<String> {
489    string_array.iter().map(|&s| s.to_owned()).collect()
490}
491
492/// Helper function to build the path of the module in the which is the node
493fn build_path(ast_func: &ast::Fn, ctx: &AssistContext<'_>, edition: Edition) -> Option<String> {
494    let crate_name = crate_name(ast_func, ctx)?;
495    let leaf = self_partial_type(ast_func)
496        .or_else(|| ast_func.name().map(|n| n.to_string()))
497        .unwrap_or_else(|| "*".into());
498    let module_def: ModuleDef = ctx.sema.to_def(ast_func)?.module(ctx.db()).into();
499    match module_def.canonical_path(ctx.db(), edition) {
500        Some(path) => Some(format!("{crate_name}::{path}::{leaf}")),
501        None => Some(format!("{crate_name}::{leaf}")),
502    }
503}
504
505/// Helper function to get the return type of a function
506fn return_type(ast_func: &ast::Fn) -> Option<ast::Type> {
507    ast_func.ret_type()?.ty()
508}
509
510/// Helper function to determine if the function returns some data
511fn returns_a_value(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> bool {
512    ctx.sema
513        .to_def(ast_func)
514        .map(|hir_func| hir_func.ret_type(ctx.db()))
515        .map(|ret_ty| !ret_ty.is_unit() && !ret_ty.is_never())
516        .unwrap_or(false)
517}
518
519#[cfg(test)]
520mod tests {
521    use crate::tests::{check_assist, check_assist_not_applicable};
522
523    use super::*;
524
525    #[test]
526    fn not_applicable_on_function_calls() {
527        check_assist_not_applicable(
528            generate_documentation_template,
529            r#"
530fn hello_world() {}
531fn calls_hello_world() {
532    hello_world$0();
533}
534"#,
535        )
536    }
537
538    #[test]
539    fn not_applicable_in_trait_impl() {
540        check_assist_not_applicable(
541            generate_documentation_template,
542            r#"
543trait MyTrait {}
544struct MyStruct;
545impl MyTrait for MyStruct {
546    fn hello_world$0();
547}
548"#,
549        )
550    }
551
552    #[test]
553    fn not_applicable_if_function_already_documented() {
554        check_assist_not_applicable(
555            generate_documentation_template,
556            r#"
557/// Some documentation here
558pub fn $0documented_function() {}
559"#,
560        );
561    }
562
563    #[test]
564    fn supports_noop_function() {
565        check_assist(
566            generate_documentation_template,
567            r#"
568pub fn no$0op() {}
569"#,
570            r#"
571/// .
572pub fn noop() {}
573"#,
574        );
575    }
576
577    #[test]
578    fn is_applicable_if_function_is_private() {
579        check_assist(
580            generate_documentation_template,
581            r#"
582fn priv$0ate() {}
583"#,
584            r#"
585/// .
586fn private() {}
587"#,
588        );
589    }
590
591    #[test]
592    fn no_doc_example_for_private_fn() {
593        check_assist_not_applicable(
594            generate_doc_example,
595            r#"
596///$0
597fn private() {}
598"#,
599        );
600    }
601
602    #[test]
603    fn supports_a_parameter() {
604        check_assist(
605            generate_doc_example,
606            r#"
607/// $0.
608pub fn noop_with_param(_a: i32) {}
609"#,
610            r#"
611/// .
612///
613/// # Examples
614///
615/// ```
616/// use ra_test_fixture::noop_with_param;
617///
618/// noop_with_param(_a);
619/// ```
620pub fn noop_with_param(_a: i32) {}
621"#,
622        );
623    }
624
625    #[test]
626    fn detects_unsafe_function() {
627        check_assist(
628            generate_documentation_template,
629            r#"
630pub unsafe fn no$0op_unsafe() {}
631"#,
632            r#"
633/// .
634///
635/// # Safety
636///
637/// .
638pub unsafe fn noop_unsafe() {}
639"#,
640        );
641        check_assist(
642            generate_doc_example,
643            r#"
644/// .
645///
646/// # Safety$0
647///
648/// .
649pub unsafe fn noop_unsafe() {}
650"#,
651            r#"
652/// .
653///
654/// # Safety
655///
656/// .
657///
658/// # Examples
659///
660/// ```
661/// use ra_test_fixture::noop_unsafe;
662///
663/// unsafe { noop_unsafe() };
664/// ```
665pub unsafe fn noop_unsafe() {}
666"#,
667        );
668    }
669
670    #[test]
671    fn guesses_panic_macro_can_panic() {
672        check_assist(
673            generate_documentation_template,
674            r#"
675pub fn panic$0s_if(a: bool) {
676    if a {
677        panic!();
678    }
679}
680"#,
681            r#"
682/// .
683///
684/// # Panics
685///
686/// Panics if .
687pub fn panics_if(a: bool) {
688    if a {
689        panic!();
690    }
691}
692"#,
693        );
694    }
695
696    #[test]
697    fn guesses_debug_assert_macro_cannot_panic() {
698        check_assist(
699            generate_documentation_template,
700            r#"
701pub fn $0debug_panics_if_not(a: bool) {
702    debug_assert!(a == true);
703}
704"#,
705            r#"
706/// .
707pub fn debug_panics_if_not(a: bool) {
708    debug_assert!(a == true);
709}
710"#,
711        );
712    }
713
714    #[test]
715    fn guesses_assert_macro_can_panic() {
716        check_assist(
717            generate_documentation_template,
718            r#"
719pub fn $0panics_if_not(a: bool) {
720    assert!(a == true);
721}
722"#,
723            r#"
724/// .
725///
726/// # Panics
727///
728/// Panics if .
729pub fn panics_if_not(a: bool) {
730    assert!(a == true);
731}
732"#,
733        );
734    }
735
736    #[test]
737    fn guesses_assert_eq_macro_can_panic() {
738        check_assist(
739            generate_documentation_template,
740            r#"
741pub fn $0panics_if_not(a: bool) {
742    assert_eq!(a, true);
743}
744"#,
745            r#"
746/// .
747///
748/// # Panics
749///
750/// Panics if .
751pub fn panics_if_not(a: bool) {
752    assert_eq!(a, true);
753}
754"#,
755        );
756    }
757
758    #[test]
759    fn guesses_unwrap_can_panic() {
760        check_assist(
761            generate_documentation_template,
762            r#"
763pub fn $0panics_if_none(a: Option<()>) {
764    a.unwrap();
765}
766"#,
767            r#"
768/// .
769///
770/// # Panics
771///
772/// Panics if .
773pub fn panics_if_none(a: Option<()>) {
774    a.unwrap();
775}
776"#,
777        );
778    }
779
780    #[test]
781    fn guesses_expect_can_panic() {
782        check_assist(
783            generate_documentation_template,
784            r#"
785pub fn $0panics_if_none2(a: Option<()>) {
786    a.expect("Bouh!");
787}
788"#,
789            r#"
790/// .
791///
792/// # Panics
793///
794/// Panics if .
795pub fn panics_if_none2(a: Option<()>) {
796    a.expect("Bouh!");
797}
798"#,
799        );
800    }
801
802    #[test]
803    fn checks_output_in_example() {
804        check_assist(
805            generate_doc_example,
806            r#"
807///$0
808pub fn returns_a_value$0() -> i32 {
809    0
810}
811"#,
812            r#"
813///
814///
815/// # Examples
816///
817/// ```
818/// use ra_test_fixture::returns_a_value;
819///
820/// assert_eq!(returns_a_value(), );
821/// ```
822pub fn returns_a_value() -> i32 {
823    0
824}
825"#,
826        );
827    }
828
829    #[test]
830    fn detects_result_output() {
831        check_assist(
832            generate_documentation_template,
833            r#"
834pub fn returns_a_result$0() -> Result<i32, std::io::Error> {
835    Ok(0)
836}
837"#,
838            r#"
839/// .
840///
841/// # Errors
842///
843/// This function will return an error if .
844pub fn returns_a_result() -> Result<i32, std::io::Error> {
845    Ok(0)
846}
847"#,
848        );
849    }
850
851    #[test]
852    fn checks_ref_mut_in_example() {
853        check_assist(
854            generate_doc_example,
855            r#"
856///$0
857pub fn modifies_a_value$0(a: &mut i32) {
858    *a = 0;
859}
860"#,
861            r#"
862///
863///
864/// # Examples
865///
866/// ```
867/// use ra_test_fixture::modifies_a_value;
868///
869/// let mut a = ;
870/// modifies_a_value(&mut a);
871/// assert_eq!(a, );
872/// ```
873pub fn modifies_a_value(a: &mut i32) {
874    *a = 0;
875}
876"#,
877        );
878    }
879
880    #[test]
881    fn stores_result_if_at_least_3_params() {
882        check_assist(
883            generate_doc_example,
884            r#"
885///$0
886pub fn sum3$0(a: i32, b: i32, c: i32) -> i32 {
887    a + b + c
888}
889"#,
890            r#"
891///
892///
893/// # Examples
894///
895/// ```
896/// use ra_test_fixture::sum3;
897///
898/// let result = sum3(a, b, c);
899/// assert_eq!(result, );
900/// ```
901pub fn sum3(a: i32, b: i32, c: i32) -> i32 {
902    a + b + c
903}
904"#,
905        );
906    }
907
908    #[test]
909    fn supports_fn_in_mods() {
910        check_assist(
911            generate_doc_example,
912            r#"
913pub mod a {
914    pub mod b {
915        ///$0
916        pub fn noop() {}
917    }
918}
919"#,
920            r#"
921pub mod a {
922    pub mod b {
923        ///
924        ///
925        /// # Examples
926        ///
927        /// ```
928        /// use ra_test_fixture::a::b::noop;
929        ///
930        /// noop();
931        /// ```
932        pub fn noop() {}
933    }
934}
935"#,
936        );
937    }
938
939    #[test]
940    fn supports_fn_in_impl() {
941        check_assist(
942            generate_doc_example,
943            r#"
944pub struct MyStruct;
945impl MyStruct {
946    ///$0
947    pub fn noop() {}
948}
949"#,
950            r#"
951pub struct MyStruct;
952impl MyStruct {
953    ///
954    ///
955    /// # Examples
956    ///
957    /// ```
958    /// use ra_test_fixture::MyStruct;
959    ///
960    /// MyStruct::noop();
961    /// ```
962    pub fn noop() {}
963}
964"#,
965        );
966    }
967
968    #[test]
969    fn supports_unsafe_fn_in_trait() {
970        check_assist(
971            generate_documentation_template,
972            r#"
973pub trait MyTrait {
974    unsafe fn unsafe_funct$0ion_trait();
975}
976"#,
977            r#"
978pub trait MyTrait {
979    /// .
980    ///
981    /// # Safety
982    ///
983    /// .
984    unsafe fn unsafe_function_trait();
985}
986"#,
987        );
988    }
989
990    #[test]
991    fn supports_fn_in_trait_with_default_panicking() {
992        check_assist(
993            generate_documentation_template,
994            r#"
995pub trait MyTrait {
996    fn function_trait_with_$0default_panicking() {
997        panic!()
998    }
999}
1000"#,
1001            r#"
1002pub trait MyTrait {
1003    /// .
1004    ///
1005    /// # Panics
1006    ///
1007    /// Panics if .
1008    fn function_trait_with_default_panicking() {
1009        panic!()
1010    }
1011}
1012"#,
1013        );
1014    }
1015
1016    #[test]
1017    fn supports_fn_in_trait_returning_result() {
1018        check_assist(
1019            generate_documentation_template,
1020            r#"
1021pub trait MyTrait {
1022    fn function_tr$0ait_returning_result() -> Result<(), std::io::Error>;
1023}
1024"#,
1025            r#"
1026pub trait MyTrait {
1027    /// .
1028    ///
1029    /// # Errors
1030    ///
1031    /// This function will return an error if .
1032    fn function_trait_returning_result() -> Result<(), std::io::Error>;
1033}
1034"#,
1035        );
1036    }
1037
1038    #[test]
1039    fn detects_new() {
1040        check_assist(
1041            generate_documentation_template,
1042            r#"
1043pub struct String(u8);
1044impl String {
1045    pub fn new$0(x: u8) -> String {
1046        String(x)
1047    }
1048}
1049"#,
1050            r#"
1051pub struct String(u8);
1052impl String {
1053    /// Creates a new [`String`].
1054    pub fn new(x: u8) -> String {
1055        String(x)
1056    }
1057}
1058"#,
1059        );
1060        check_assist(
1061            generate_documentation_template,
1062            r#"
1063#[derive(Debug, PartialEq)]
1064pub struct MyGenericStruct<T> {
1065    pub x: T,
1066}
1067impl<T> MyGenericStruct<T> {
1068    pub fn new$0(x: T) -> MyGenericStruct<T> {
1069        MyGenericStruct { x }
1070    }
1071}
1072"#,
1073            r#"
1074#[derive(Debug, PartialEq)]
1075pub struct MyGenericStruct<T> {
1076    pub x: T,
1077}
1078impl<T> MyGenericStruct<T> {
1079    /// Creates a new [`MyGenericStruct<T>`].
1080    pub fn new(x: T) -> MyGenericStruct<T> {
1081        MyGenericStruct { x }
1082    }
1083}
1084"#,
1085        );
1086    }
1087
1088    #[test]
1089    fn removes_one_lifetime_from_description() {
1090        check_assist(
1091            generate_documentation_template,
1092            r#"
1093#[derive(Debug, PartialEq)]
1094pub struct MyGenericStruct<'a, T> {
1095    pub x: &'a T,
1096}
1097impl<'a, T> MyGenericStruct<'a, T> {
1098    pub fn new$0(x: &'a T) -> Self {
1099        MyGenericStruct { x }
1100    }
1101}
1102"#,
1103            r#"
1104#[derive(Debug, PartialEq)]
1105pub struct MyGenericStruct<'a, T> {
1106    pub x: &'a T,
1107}
1108impl<'a, T> MyGenericStruct<'a, T> {
1109    /// Creates a new [`MyGenericStruct<T>`].
1110    pub fn new(x: &'a T) -> Self {
1111        MyGenericStruct { x }
1112    }
1113}
1114"#,
1115        );
1116    }
1117
1118    #[test]
1119    fn removes_all_lifetimes_from_description() {
1120        check_assist(
1121            generate_documentation_template,
1122            r#"
1123#[derive(Debug, PartialEq)]
1124pub struct MyGenericStruct<'a, 'b, T> {
1125    pub x: &'a T,
1126    pub y: &'b T,
1127}
1128impl<'a, 'b, T> MyGenericStruct<'a, 'b, T> {
1129    pub fn new$0(x: &'a T, y: &'b T) -> Self {
1130        MyGenericStruct { x, y }
1131    }
1132}
1133"#,
1134            r#"
1135#[derive(Debug, PartialEq)]
1136pub struct MyGenericStruct<'a, 'b, T> {
1137    pub x: &'a T,
1138    pub y: &'b T,
1139}
1140impl<'a, 'b, T> MyGenericStruct<'a, 'b, T> {
1141    /// Creates a new [`MyGenericStruct<T>`].
1142    pub fn new(x: &'a T, y: &'b T) -> Self {
1143        MyGenericStruct { x, y }
1144    }
1145}
1146"#,
1147        );
1148    }
1149
1150    #[test]
1151    fn removes_all_lifetimes_and_brackets_from_description() {
1152        check_assist(
1153            generate_documentation_template,
1154            r#"
1155#[derive(Debug, PartialEq)]
1156pub struct MyGenericStruct<'a, 'b> {
1157    pub x: &'a usize,
1158    pub y: &'b usize,
1159}
1160impl<'a, 'b> MyGenericStruct<'a, 'b> {
1161    pub fn new$0(x: &'a usize, y: &'b usize) -> Self {
1162        MyGenericStruct { x, y }
1163    }
1164}
1165"#,
1166            r#"
1167#[derive(Debug, PartialEq)]
1168pub struct MyGenericStruct<'a, 'b> {
1169    pub x: &'a usize,
1170    pub y: &'b usize,
1171}
1172impl<'a, 'b> MyGenericStruct<'a, 'b> {
1173    /// Creates a new [`MyGenericStruct`].
1174    pub fn new(x: &'a usize, y: &'b usize) -> Self {
1175        MyGenericStruct { x, y }
1176    }
1177}
1178"#,
1179        );
1180    }
1181
1182    #[test]
1183    fn detects_new_with_self() {
1184        check_assist(
1185            generate_documentation_template,
1186            r#"
1187#[derive(Debug, PartialEq)]
1188pub struct MyGenericStruct2<T> {
1189    pub x: T,
1190}
1191impl<T> MyGenericStruct2<T> {
1192    pub fn new$0(x: T) -> Self {
1193        MyGenericStruct2 { x }
1194    }
1195}
1196"#,
1197            r#"
1198#[derive(Debug, PartialEq)]
1199pub struct MyGenericStruct2<T> {
1200    pub x: T,
1201}
1202impl<T> MyGenericStruct2<T> {
1203    /// Creates a new [`MyGenericStruct2<T>`].
1204    pub fn new(x: T) -> Self {
1205        MyGenericStruct2 { x }
1206    }
1207}
1208"#,
1209        );
1210    }
1211
1212    #[test]
1213    fn supports_method_call() {
1214        check_assist(
1215            generate_doc_example,
1216            r#"
1217impl<T> MyGenericStruct<T> {
1218    ///$0
1219    pub fn consume(self) {}
1220}
1221"#,
1222            r#"
1223impl<T> MyGenericStruct<T> {
1224    ///
1225    ///
1226    /// # Examples
1227    ///
1228    /// ```
1229    /// use ra_test_fixture::MyGenericStruct;
1230    ///
1231    /// let my_generic_struct = ;
1232    /// my_generic_struct.consume();
1233    /// ```
1234    pub fn consume(self) {}
1235}
1236"#,
1237        );
1238    }
1239
1240    #[test]
1241    fn checks_modified_self_param() {
1242        check_assist(
1243            generate_doc_example,
1244            r#"
1245impl<T> MyGenericStruct<T> {
1246    ///$0
1247    pub fn modify(&mut self, new_value: T) {
1248        self.x = new_value;
1249    }
1250}
1251"#,
1252            r#"
1253impl<T> MyGenericStruct<T> {
1254    ///
1255    ///
1256    /// # Examples
1257    ///
1258    /// ```
1259    /// use ra_test_fixture::MyGenericStruct;
1260    ///
1261    /// let mut my_generic_struct = ;
1262    /// my_generic_struct.modify(new_value);
1263    /// assert_eq!(my_generic_struct, );
1264    /// ```
1265    pub fn modify(&mut self, new_value: T) {
1266        self.x = new_value;
1267    }
1268}
1269"#,
1270        );
1271    }
1272
1273    #[test]
1274    fn generates_intro_for_getters() {
1275        check_assist(
1276            generate_documentation_template,
1277            r#"
1278pub struct S;
1279impl S {
1280    pub fn speed$0(&self) -> f32 { 0.0 }
1281}
1282"#,
1283            r#"
1284pub struct S;
1285impl S {
1286    /// Returns the speed of this [`S`].
1287    pub fn speed(&self) -> f32 { 0.0 }
1288}
1289"#,
1290        );
1291        check_assist(
1292            generate_documentation_template,
1293            r#"
1294pub struct S;
1295impl S {
1296    pub fn data$0(&self) -> &[u8] { &[] }
1297}
1298"#,
1299            r#"
1300pub struct S;
1301impl S {
1302    /// Returns a reference to the data of this [`S`].
1303    pub fn data(&self) -> &[u8] { &[] }
1304}
1305"#,
1306        );
1307        check_assist(
1308            generate_documentation_template,
1309            r#"
1310pub struct S;
1311impl S {
1312    pub fn data$0(&mut self) -> &mut [u8] { &mut [] }
1313}
1314"#,
1315            r#"
1316pub struct S;
1317impl S {
1318    /// Returns a mutable reference to the data of this [`S`].
1319    pub fn data(&mut self) -> &mut [u8] { &mut [] }
1320}
1321"#,
1322        );
1323        check_assist(
1324            generate_documentation_template,
1325            r#"
1326pub struct S;
1327impl S {
1328    pub fn data_mut$0(&mut self) -> &mut [u8] { &mut [] }
1329}
1330"#,
1331            r#"
1332pub struct S;
1333impl S {
1334    /// Returns a mutable reference to the data of this [`S`].
1335    pub fn data_mut(&mut self) -> &mut [u8] { &mut [] }
1336}
1337"#,
1338        );
1339    }
1340
1341    #[test]
1342    fn no_getter_intro_for_prefixed_methods() {
1343        check_assist(
1344            generate_documentation_template,
1345            r#"
1346pub struct S;
1347impl S {
1348    pub fn as_bytes$0(&self) -> &[u8] { &[] }
1349}
1350"#,
1351            r#"
1352pub struct S;
1353impl S {
1354    /// .
1355    pub fn as_bytes(&self) -> &[u8] { &[] }
1356}
1357"#,
1358        );
1359    }
1360
1361    #[test]
1362    fn generates_intro_for_setters() {
1363        check_assist(
1364            generate_documentation_template,
1365            r#"
1366pub struct S;
1367impl S {
1368    pub fn set_data$0(&mut self, data: Vec<u8>) {}
1369}
1370"#,
1371            r#"
1372pub struct S;
1373impl S {
1374    /// Sets the data of this [`S`].
1375    pub fn set_data(&mut self, data: Vec<u8>) {}
1376}
1377"#,
1378        );
1379        check_assist(
1380            generate_documentation_template,
1381            r#"
1382pub struct S;
1383impl S {
1384    pub fn set_domain_name$0(&mut self, name: String) {}
1385}
1386"#,
1387            r#"
1388pub struct S;
1389impl S {
1390    /// Sets the domain name of this [`S`].
1391    pub fn set_domain_name(&mut self, name: String) {}
1392}
1393"#,
1394        );
1395    }
1396}