]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide_api/src/call_info.rs
fix panic in call info
[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         let docs = node.doc_comment_text();
130         if !docs.is_empty() {
131             // Massage markdown
132             let mut processed_lines = Vec::new();
133             let mut in_code_block = false;
134             for line in docs.lines() {
135                 if line.starts_with("```") {
136                     in_code_block = !in_code_block;
137                 }
138
139                 let line = if in_code_block && line.starts_with("```") && !line.contains("rust") {
140                     "```rust".into()
141                 } else {
142                     line.to_string()
143                 };
144
145                 processed_lines.push(line);
146             }
147
148             doc = Some(processed_lines.join("\n"));
149         }
150
151         Some(CallInfo {
152             parameters: param_list(node),
153             label: label.trim().to_owned(),
154             doc,
155             active_parameter: None,
156         })
157     }
158 }
159
160 fn param_list(node: &ast::FnDef) -> Vec<String> {
161     let mut res = vec![];
162     if let Some(param_list) = node.param_list() {
163         if let Some(self_param) = param_list.self_param() {
164             res.push(self_param.syntax().text().to_string())
165         }
166
167         // Maybe use param.pat here? See if we can just extract the name?
168         //res.extend(param_list.params().map(|p| p.syntax().text().to_string()));
169         res.extend(
170             param_list
171                 .params()
172                 .filter_map(|p| p.pat())
173                 .map(|pat| pat.syntax().text().to_string()),
174         );
175     }
176     res
177 }
178
179 #[cfg(test)]
180 mod tests {
181     use test_utils::covers;
182
183     use crate::mock_analysis::single_file_with_position;
184
185     use super::*;
186
187     fn call_info(text: &str) -> CallInfo {
188         let (analysis, position) = single_file_with_position(text);
189         analysis.call_info(position).unwrap().unwrap()
190     }
191
192     #[test]
193     fn test_fn_signature_two_args_first() {
194         let info = call_info(
195             r#"fn foo(x: u32, y: u32) -> u32 {x + y}
196 fn bar() { foo(<|>3, ); }"#,
197         );
198
199         assert_eq!(info.parameters, vec!("x".to_string(), "y".to_string()));
200         assert_eq!(info.active_parameter, Some(0));
201     }
202
203     #[test]
204     fn test_fn_signature_two_args_second() {
205         let info = call_info(
206             r#"fn foo(x: u32, y: u32) -> u32 {x + y}
207 fn bar() { foo(3, <|>); }"#,
208         );
209
210         assert_eq!(info.parameters, vec!("x".to_string(), "y".to_string()));
211         assert_eq!(info.active_parameter, Some(1));
212     }
213
214     #[test]
215     fn test_fn_signature_for_impl() {
216         let info = call_info(
217             r#"struct F; impl F { pub fn new() { F{}} }
218 fn bar() {let _ : F = F::new(<|>);}"#,
219         );
220
221         assert_eq!(info.parameters, Vec::<String>::new());
222         assert_eq!(info.active_parameter, None);
223     }
224
225     #[test]
226     fn test_fn_signature_for_method_self() {
227         let info = call_info(
228             r#"struct F;
229 impl F {
230     pub fn new() -> F{
231         F{}
232     }
233
234     pub fn do_it(&self) {}
235 }
236
237 fn bar() {
238     let f : F = F::new();
239     f.do_it(<|>);
240 }"#,
241         );
242
243         assert_eq!(info.parameters, vec!["&self".to_string()]);
244         assert_eq!(info.active_parameter, None);
245     }
246
247     #[test]
248     fn test_fn_signature_for_method_with_arg() {
249         let info = call_info(
250             r#"struct F;
251 impl F {
252     pub fn new() -> F{
253         F{}
254     }
255
256     pub fn do_it(&self, x: i32) {}
257 }
258
259 fn bar() {
260     let f : F = F::new();
261     f.do_it(<|>);
262 }"#,
263         );
264
265         assert_eq!(info.parameters, vec!["&self".to_string(), "x".to_string()]);
266         assert_eq!(info.active_parameter, Some(1));
267     }
268
269     #[test]
270     fn test_fn_signature_with_docs_simple() {
271         let info = call_info(
272             r#"
273 /// test
274 // non-doc-comment
275 fn foo(j: u32) -> u32 {
276     j
277 }
278
279 fn bar() {
280     let _ = foo(<|>);
281 }
282 "#,
283         );
284
285         assert_eq!(info.parameters, vec!["j".to_string()]);
286         assert_eq!(info.active_parameter, Some(0));
287         assert_eq!(info.label, "fn foo(j: u32) -> u32".to_string());
288         assert_eq!(info.doc, Some("test".into()));
289     }
290
291     #[test]
292     fn test_fn_signature_with_docs() {
293         let info = call_info(
294             r#"
295 /// Adds one to the number given.
296 ///
297 /// # Examples
298 ///
299 /// ```
300 /// let five = 5;
301 ///
302 /// assert_eq!(6, my_crate::add_one(5));
303 /// ```
304 pub fn add_one(x: i32) -> i32 {
305     x + 1
306 }
307
308 pub fn do() {
309     add_one(<|>
310 }"#,
311         );
312
313         assert_eq!(info.parameters, vec!["x".to_string()]);
314         assert_eq!(info.active_parameter, Some(0));
315         assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string());
316         assert_eq!(
317             info.doc,
318             Some(
319                 r#"Adds one to the number given.
320
321 # Examples
322
323 ```rust
324 let five = 5;
325
326 assert_eq!(6, my_crate::add_one(5));
327 ```"#
328                     .into()
329             )
330         );
331     }
332
333     #[test]
334     fn test_fn_signature_with_docs_impl() {
335         let info = call_info(
336             r#"
337 struct addr;
338 impl addr {
339     /// Adds one to the number given.
340     ///
341     /// # Examples
342     ///
343     /// ```
344     /// let five = 5;
345     ///
346     /// assert_eq!(6, my_crate::add_one(5));
347     /// ```
348     pub fn add_one(x: i32) -> i32 {
349         x + 1
350     }
351 }
352
353 pub fn do_it() {
354     addr {};
355     addr::add_one(<|>);
356 }"#,
357         );
358
359         assert_eq!(info.parameters, vec!["x".to_string()]);
360         assert_eq!(info.active_parameter, Some(0));
361         assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string());
362         assert_eq!(
363             info.doc,
364             Some(
365                 r#"Adds one to the number given.
366
367 # Examples
368
369 ```rust
370 let five = 5;
371
372 assert_eq!(6, my_crate::add_one(5));
373 ```"#
374                     .into()
375             )
376         );
377     }
378
379     #[test]
380     fn test_fn_signature_with_docs_from_actix() {
381         let info = call_info(
382             r#"
383 pub trait WriteHandler<E>
384 where
385     Self: Actor,
386     Self::Context: ActorContext,
387 {
388     /// Method is called when writer emits error.
389     ///
390     /// If this method returns `ErrorAction::Continue` writer processing
391     /// continues otherwise stream processing stops.
392     fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running {
393         Running::Stop
394     }
395
396     /// Method is called when writer finishes.
397     ///
398     /// By default this method stops actor's `Context`.
399     fn finished(&mut self, ctx: &mut Self::Context) {
400         ctx.stop()
401     }
402 }
403
404 pub fn foo() {
405     WriteHandler r;
406     r.finished(<|>);
407 }
408
409 "#,
410         );
411
412         assert_eq!(
413             info.parameters,
414             vec!["&mut self".to_string(), "ctx".to_string()]
415         );
416         assert_eq!(info.active_parameter, Some(1));
417         assert_eq!(
418             info.doc,
419             Some(
420                 r#"Method is called when writer finishes.
421
422 By default this method stops actor's `Context`."#
423                     .into()
424             )
425         );
426     }
427
428     #[test]
429     fn call_info_bad_offset() {
430         covers!(call_info_bad_offset);
431         let (analysis, position) = single_file_with_position(
432             r#"fn foo(x: u32, y: u32) -> u32 {x + y}
433                fn bar() { foo <|> (3, ); }"#,
434         );
435         let call_info = analysis.call_info(position).unwrap();
436         assert!(call_info.is_none());
437     }
438 }