]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide_api/src/call_info.rs
ee1e137993673c3bd631b447b49bd8ea06ad2538
[rust.git] / crates / ra_ide_api / src / call_info.rs
1 use test_utils::tested_by;
2 use ra_db::SourceDatabase;
3 use ra_syntax::{
4     AstNode, SyntaxNode, TextUnit, TextRange,
5     SyntaxKind::FN_DEF,
6     ast::{self, ArgListOwner, DocCommentsOwner},
7     algo::find_node_at_offset,
8 };
9
10 use crate::{FilePosition, CallInfo, db::RootDatabase};
11
12 /// Computes parameter information for the given call expression.
13 pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> {
14     let file = db.parse(position.file_id);
15     let syntax = file.syntax();
16
17     // Find the calling expression and it's NameRef
18     let calling_node = FnCallNode::with_node(syntax, position.offset)?;
19     let name_ref = calling_node.name_ref()?;
20
21     // Resolve the function's NameRef (NOTE: this isn't entirely accurate).
22     let file_symbols = db.index_resolve(name_ref);
23     let symbol = file_symbols
24         .into_iter()
25         .find(|it| it.ptr.kind() == FN_DEF)?;
26     let fn_file = db.parse(symbol.file_id);
27     let fn_def = symbol.ptr.to_node(&fn_file);
28     let fn_def = ast::FnDef::cast(fn_def).unwrap();
29     let mut call_info = CallInfo::new(fn_def)?;
30     // If we have a calling expression let's find which argument we are on
31     let num_params = call_info.parameters.len();
32     let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some();
33
34     if num_params == 1 {
35         if !has_self {
36             call_info.active_parameter = Some(0);
37         }
38     } else if num_params > 1 {
39         // Count how many parameters into the call we are.
40         // TODO: This is best effort for now and should be fixed at some point.
41         // It may be better to see where we are in the arg_list and then check
42         // where offset is in that list (or beyond).
43         // Revisit this after we get documentation comments in.
44         if let Some(ref arg_list) = calling_node.arg_list() {
45             let arg_list_range = arg_list.syntax().range();
46             if !arg_list_range.contains_inclusive(position.offset) {
47                 tested_by!(call_info_bad_offset);
48                 return None;
49             }
50             let start = arg_list_range.start();
51
52             let range_search = TextRange::from_to(start, position.offset);
53             let mut commas: usize = arg_list
54                 .syntax()
55                 .text()
56                 .slice(range_search)
57                 .to_string()
58                 .matches(',')
59                 .count();
60
61             // If we have a method call eat the first param since it's just self.
62             if has_self {
63                 commas += 1;
64             }
65
66             call_info.active_parameter = Some(commas);
67         }
68     }
69
70     Some(call_info)
71 }
72
73 enum FnCallNode<'a> {
74     CallExpr(&'a ast::CallExpr),
75     MethodCallExpr(&'a ast::MethodCallExpr),
76 }
77
78 impl<'a> FnCallNode<'a> {
79     pub fn with_node(syntax: &'a SyntaxNode, offset: TextUnit) -> Option<FnCallNode<'a>> {
80         if let Some(expr) = find_node_at_offset::<ast::CallExpr>(syntax, offset) {
81             return Some(FnCallNode::CallExpr(expr));
82         }
83         if let Some(expr) = find_node_at_offset::<ast::MethodCallExpr>(syntax, offset) {
84             return Some(FnCallNode::MethodCallExpr(expr));
85         }
86         None
87     }
88
89     pub fn name_ref(&self) -> Option<&'a ast::NameRef> {
90         match *self {
91             FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()?.kind() {
92                 ast::ExprKind::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?,
93                 _ => return None,
94             }),
95
96             FnCallNode::MethodCallExpr(call_expr) => call_expr
97                 .syntax()
98                 .children()
99                 .filter_map(ast::NameRef::cast)
100                 .nth(0),
101         }
102     }
103
104     pub fn arg_list(&self) -> Option<&'a ast::ArgList> {
105         match *self {
106             FnCallNode::CallExpr(expr) => expr.arg_list(),
107             FnCallNode::MethodCallExpr(expr) => expr.arg_list(),
108         }
109     }
110 }
111
112 impl CallInfo {
113     fn new(node: &ast::FnDef) -> Option<Self> {
114         let label: String = if let Some(body) = node.body() {
115             let body_range = body.syntax().range();
116             let label: String = node
117                 .syntax()
118                 .children()
119                 .filter(|child| !child.range().is_subrange(&body_range)) // Filter out body
120                 .filter(|child| ast::Comment::cast(child).is_none()) // Filter out doc comments
121                 .map(|node| node.text().to_string())
122                 .collect();
123             label
124         } else {
125             node.syntax().text().to_string()
126         };
127
128         let mut doc = None;
129         if let Some(docs) = node.doc_comment_text() {
130             // Massage markdown
131             let mut processed_lines = Vec::new();
132             let mut in_code_block = false;
133             for line in docs.lines() {
134                 if line.starts_with("```") {
135                     in_code_block = !in_code_block;
136                 }
137
138                 let line = if in_code_block && line.starts_with("```") && !line.contains("rust") {
139                     "```rust".into()
140                 } else {
141                     line.to_string()
142                 };
143
144                 processed_lines.push(line);
145             }
146
147             doc = Some(processed_lines.join("\n"));
148         }
149
150         Some(CallInfo {
151             parameters: param_list(node),
152             label: label.trim().to_owned(),
153             doc,
154             active_parameter: None,
155         })
156     }
157 }
158
159 fn param_list(node: &ast::FnDef) -> Vec<String> {
160     let mut res = vec![];
161     if let Some(param_list) = node.param_list() {
162         if let Some(self_param) = param_list.self_param() {
163             res.push(self_param.syntax().text().to_string())
164         }
165
166         // Maybe use param.pat here? See if we can just extract the name?
167         //res.extend(param_list.params().map(|p| p.syntax().text().to_string()));
168         res.extend(
169             param_list
170                 .params()
171                 .filter_map(|p| p.pat())
172                 .map(|pat| pat.syntax().text().to_string()),
173         );
174     }
175     res
176 }
177
178 #[cfg(test)]
179 mod tests {
180     use test_utils::covers;
181
182     use crate::mock_analysis::single_file_with_position;
183
184     use super::*;
185
186     fn call_info(text: &str) -> CallInfo {
187         let (analysis, position) = single_file_with_position(text);
188         analysis.call_info(position).unwrap().unwrap()
189     }
190
191     #[test]
192     fn test_fn_signature_two_args_first() {
193         let info = call_info(
194             r#"fn foo(x: u32, y: u32) -> u32 {x + y}
195 fn bar() { foo(<|>3, ); }"#,
196         );
197
198         assert_eq!(info.parameters, vec!("x".to_string(), "y".to_string()));
199         assert_eq!(info.active_parameter, Some(0));
200     }
201
202     #[test]
203     fn test_fn_signature_two_args_second() {
204         let info = call_info(
205             r#"fn foo(x: u32, y: u32) -> u32 {x + y}
206 fn bar() { foo(3, <|>); }"#,
207         );
208
209         assert_eq!(info.parameters, vec!("x".to_string(), "y".to_string()));
210         assert_eq!(info.active_parameter, Some(1));
211     }
212
213     #[test]
214     fn test_fn_signature_for_impl() {
215         let info = call_info(
216             r#"struct F; impl F { pub fn new() { F{}} }
217 fn bar() {let _ : F = F::new(<|>);}"#,
218         );
219
220         assert_eq!(info.parameters, Vec::<String>::new());
221         assert_eq!(info.active_parameter, None);
222     }
223
224     #[test]
225     fn test_fn_signature_for_method_self() {
226         let info = call_info(
227             r#"struct F;
228 impl F {
229     pub fn new() -> F{
230         F{}
231     }
232
233     pub fn do_it(&self) {}
234 }
235
236 fn bar() {
237     let f : F = F::new();
238     f.do_it(<|>);
239 }"#,
240         );
241
242         assert_eq!(info.parameters, vec!["&self".to_string()]);
243         assert_eq!(info.active_parameter, None);
244     }
245
246     #[test]
247     fn test_fn_signature_for_method_with_arg() {
248         let info = call_info(
249             r#"struct F;
250 impl F {
251     pub fn new() -> F{
252         F{}
253     }
254
255     pub fn do_it(&self, x: i32) {}
256 }
257
258 fn bar() {
259     let f : F = F::new();
260     f.do_it(<|>);
261 }"#,
262         );
263
264         assert_eq!(info.parameters, vec!["&self".to_string(), "x".to_string()]);
265         assert_eq!(info.active_parameter, Some(1));
266     }
267
268     #[test]
269     fn test_fn_signature_with_docs_simple() {
270         let info = call_info(
271             r#"
272 /// test
273 // non-doc-comment
274 fn foo(j: u32) -> u32 {
275     j
276 }
277
278 fn bar() {
279     let _ = foo(<|>);
280 }
281 "#,
282         );
283
284         assert_eq!(info.parameters, vec!["j".to_string()]);
285         assert_eq!(info.active_parameter, Some(0));
286         assert_eq!(info.label, "fn foo(j: u32) -> u32".to_string());
287         assert_eq!(info.doc, Some("test".into()));
288     }
289
290     #[test]
291     fn test_fn_signature_with_docs() {
292         let info = call_info(
293             r#"
294 /// Adds one to the number given.
295 ///
296 /// # Examples
297 ///
298 /// ```
299 /// let five = 5;
300 ///
301 /// assert_eq!(6, my_crate::add_one(5));
302 /// ```
303 pub fn add_one(x: i32) -> i32 {
304     x + 1
305 }
306
307 pub fn do() {
308     add_one(<|>
309 }"#,
310         );
311
312         assert_eq!(info.parameters, vec!["x".to_string()]);
313         assert_eq!(info.active_parameter, Some(0));
314         assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string());
315         assert_eq!(
316             info.doc,
317             Some(
318                 r#"Adds one to the number given.
319
320 # Examples
321
322 ```rust
323 let five = 5;
324
325 assert_eq!(6, my_crate::add_one(5));
326 ```"#
327                     .into()
328             )
329         );
330     }
331
332     #[test]
333     fn test_fn_signature_with_docs_impl() {
334         let info = call_info(
335             r#"
336 struct addr;
337 impl addr {
338     /// Adds one to the number given.
339     ///
340     /// # Examples
341     ///
342     /// ```
343     /// let five = 5;
344     ///
345     /// assert_eq!(6, my_crate::add_one(5));
346     /// ```
347     pub fn add_one(x: i32) -> i32 {
348         x + 1
349     }
350 }
351
352 pub fn do_it() {
353     addr {};
354     addr::add_one(<|>);
355 }"#,
356         );
357
358         assert_eq!(info.parameters, vec!["x".to_string()]);
359         assert_eq!(info.active_parameter, Some(0));
360         assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string());
361         assert_eq!(
362             info.doc,
363             Some(
364                 r#"Adds one to the number given.
365
366 # Examples
367
368 ```rust
369 let five = 5;
370
371 assert_eq!(6, my_crate::add_one(5));
372 ```"#
373                     .into()
374             )
375         );
376     }
377
378     #[test]
379     fn test_fn_signature_with_docs_from_actix() {
380         let info = call_info(
381             r#"
382 pub trait WriteHandler<E>
383 where
384     Self: Actor,
385     Self::Context: ActorContext,
386 {
387     /// Method is called when writer emits error.
388     ///
389     /// If this method returns `ErrorAction::Continue` writer processing
390     /// continues otherwise stream processing stops.
391     fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running {
392         Running::Stop
393     }
394
395     /// Method is called when writer finishes.
396     ///
397     /// By default this method stops actor's `Context`.
398     fn finished(&mut self, ctx: &mut Self::Context) {
399         ctx.stop()
400     }
401 }
402
403 pub fn foo() {
404     WriteHandler r;
405     r.finished(<|>);
406 }
407
408 "#,
409         );
410
411         assert_eq!(
412             info.parameters,
413             vec!["&mut self".to_string(), "ctx".to_string()]
414         );
415         assert_eq!(info.active_parameter, Some(1));
416         assert_eq!(
417             info.doc,
418             Some(
419                 r#"Method is called when writer finishes.
420
421 By default this method stops actor's `Context`."#
422                     .into()
423             )
424         );
425     }
426
427     #[test]
428     fn call_info_bad_offset() {
429         covers!(call_info_bad_offset);
430         let (analysis, position) = single_file_with_position(
431             r#"fn foo(x: u32, y: u32) -> u32 {x + y}
432                fn bar() { foo <|> (3, ); }"#,
433         );
434         let call_info = analysis.call_info(position).unwrap();
435         assert!(call_info.is_none());
436     }
437 }