diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-10-31 19:41:24 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-10-31 19:41:24 +0000 |
commit | 55ebe6380aef233fff86b7e6cead361787bf1f65 (patch) | |
tree | 9ed060e4738a0504ddfd146649b9cf8a2f2fac40 /crates/ra_analysis | |
parent | dfba29e4fb66457d101db295e3c356a932ac005e (diff) | |
parent | 74320945b664916005d263552194201cbe9a52bc (diff) |
Merge #167
167: Attempt to extract useful comments from function signatures r=matklad a=kjeremy
I'm trying to extract useful function comments for signature info. This will also be useful for hover. This is a WIP (and actually works pretty well!) but I don't think it's the right approach long term so some guidance would be appreciated so that we could also get comments for say types and variable instances etc.
Currently `test_fn_signature_with_simple_doc` fails due to a bug in `extend` but we probably shouldn't use this approach anyway. Maybe comments should be attached to nodes somehow? I'm also thinking that maybe the markdown bits should live in the language server.
Thoughts?
Co-authored-by: Jeremy A. Kolb <[email protected]>
Diffstat (limited to 'crates/ra_analysis')
-rw-r--r-- | crates/ra_analysis/src/descriptors/function/mod.rs | 60 | ||||
-rw-r--r-- | crates/ra_analysis/tests/tests.rs | 147 |
2 files changed, 204 insertions, 3 deletions
diff --git a/crates/ra_analysis/src/descriptors/function/mod.rs b/crates/ra_analysis/src/descriptors/function/mod.rs index bb68b0ce7..ae40f3e8f 100644 --- a/crates/ra_analysis/src/descriptors/function/mod.rs +++ b/crates/ra_analysis/src/descriptors/function/mod.rs | |||
@@ -1,8 +1,11 @@ | |||
1 | pub(super) mod imp; | 1 | pub(super) mod imp; |
2 | mod scope; | 2 | mod scope; |
3 | 3 | ||
4 | use std::cmp::{min, max}; | ||
5 | |||
4 | use ra_syntax::{ | 6 | use ra_syntax::{ |
5 | ast::{self, AstNode, NameOwner} | 7 | ast::{self, AstNode, DocCommentsOwner, NameOwner}, |
8 | TextRange, TextUnit | ||
6 | }; | 9 | }; |
7 | 10 | ||
8 | use crate::{ | 11 | use crate::{ |
@@ -30,14 +33,17 @@ pub struct FnDescriptor { | |||
30 | pub label: String, | 33 | pub label: String, |
31 | pub ret_type: Option<String>, | 34 | pub ret_type: Option<String>, |
32 | pub params: Vec<String>, | 35 | pub params: Vec<String>, |
36 | pub doc: Option<String> | ||
33 | } | 37 | } |
34 | 38 | ||
35 | impl FnDescriptor { | 39 | impl FnDescriptor { |
36 | pub fn new(node: ast::FnDef) -> Option<Self> { | 40 | pub fn new(node: ast::FnDef) -> Option<Self> { |
37 | let name = node.name()?.text().to_string(); | 41 | let name = node.name()?.text().to_string(); |
38 | 42 | ||
43 | let mut doc = None; | ||
44 | |||
39 | // Strip the body out for the label. | 45 | // Strip the body out for the label. |
40 | let label: String = if let Some(body) = node.body() { | 46 | let mut label: String = if let Some(body) = node.body() { |
41 | let body_range = body.syntax().range(); | 47 | let body_range = body.syntax().range(); |
42 | let label: String = node | 48 | let label: String = node |
43 | .syntax() | 49 | .syntax() |
@@ -50,6 +56,36 @@ impl FnDescriptor { | |||
50 | node.syntax().text().to_string() | 56 | node.syntax().text().to_string() |
51 | }; | 57 | }; |
52 | 58 | ||
59 | if let Some((comment_range, docs)) = FnDescriptor::extract_doc_comments(node) { | ||
60 | let comment_range = comment_range.checked_sub(node.syntax().range().start()).unwrap(); | ||
61 | let start = comment_range.start().to_usize(); | ||
62 | let end = comment_range.end().to_usize(); | ||
63 | |||
64 | // Remove the comment from the label | ||
65 | label.replace_range(start..end, ""); | ||
66 | |||
67 | // Massage markdown | ||
68 | let mut processed_lines = Vec::new(); | ||
69 | let mut in_code_block = false; | ||
70 | for line in docs.lines() { | ||
71 | if line.starts_with("```") { | ||
72 | in_code_block = !in_code_block; | ||
73 | } | ||
74 | |||
75 | let line = if in_code_block && line.starts_with("```") && !line.contains("rust") { | ||
76 | "```rust".into() | ||
77 | } else { | ||
78 | line.to_string() | ||
79 | }; | ||
80 | |||
81 | processed_lines.push(line); | ||
82 | } | ||
83 | |||
84 | if !processed_lines.is_empty() { | ||
85 | doc = Some(processed_lines.join("\n")); | ||
86 | } | ||
87 | } | ||
88 | |||
53 | let params = FnDescriptor::param_list(node); | 89 | let params = FnDescriptor::param_list(node); |
54 | let ret_type = node.ret_type().map(|r| r.syntax().text().to_string()); | 90 | let ret_type = node.ret_type().map(|r| r.syntax().text().to_string()); |
55 | 91 | ||
@@ -57,10 +93,28 @@ impl FnDescriptor { | |||
57 | name, | 93 | name, |
58 | ret_type, | 94 | ret_type, |
59 | params, | 95 | params, |
60 | label, | 96 | label: label.trim().to_owned(), |
97 | doc | ||
61 | }) | 98 | }) |
62 | } | 99 | } |
63 | 100 | ||
101 | fn extract_doc_comments(node: ast::FnDef) -> Option<(TextRange, String)> { | ||
102 | if node.doc_comments().count() == 0 { | ||
103 | return None; | ||
104 | } | ||
105 | |||
106 | let comment_text = node.doc_comment_text(); | ||
107 | |||
108 | let (begin, end) = node.doc_comments() | ||
109 | .map(|comment| comment.syntax().range()) | ||
110 | .map(|range| (range.start().to_usize(), range.end().to_usize())) | ||
111 | .fold((std::usize::MAX, std::usize::MIN), |acc, range| (min(acc.0, range.0), max(acc.1, range.1))); | ||
112 | |||
113 | let range = TextRange::from_to(TextUnit::from_usize(begin), TextUnit::from_usize(end)); | ||
114 | |||
115 | Some((range, comment_text)) | ||
116 | } | ||
117 | |||
64 | fn param_list(node: ast::FnDef) -> Vec<String> { | 118 | fn param_list(node: ast::FnDef) -> Vec<String> { |
65 | let mut res = vec![]; | 119 | let mut res = vec![]; |
66 | if let Some(param_list) = node.param_list() { | 120 | if let Some(param_list) = node.param_list() { |
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs index 94e025677..9e2478d9e 100644 --- a/crates/ra_analysis/tests/tests.rs +++ b/crates/ra_analysis/tests/tests.rs | |||
@@ -195,6 +195,153 @@ fn bar() { | |||
195 | assert_eq!(param, Some(1)); | 195 | assert_eq!(param, Some(1)); |
196 | } | 196 | } |
197 | 197 | ||
198 | #[test] | ||
199 | fn test_fn_signature_with_docs_simple() { | ||
200 | let (desc, param) = get_signature( | ||
201 | r#" | ||
202 | // test | ||
203 | fn foo(j: u32) -> u32 { | ||
204 | j | ||
205 | } | ||
206 | |||
207 | fn bar() { | ||
208 | let _ = foo(<|>); | ||
209 | } | ||
210 | "#, | ||
211 | ); | ||
212 | |||
213 | assert_eq!(desc.name, "foo".to_string()); | ||
214 | assert_eq!(desc.params, vec!["j".to_string()]); | ||
215 | assert_eq!(desc.ret_type, Some("-> u32".to_string())); | ||
216 | assert_eq!(param, Some(0)); | ||
217 | assert_eq!(desc.label, "fn foo(j: u32) -> u32".to_string()); | ||
218 | assert_eq!(desc.doc, Some("test".into())); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_fn_signature_with_docs() { | ||
223 | let (desc, param) = get_signature( | ||
224 | r#" | ||
225 | /// Adds one to the number given. | ||
226 | /// | ||
227 | /// # Examples | ||
228 | /// | ||
229 | /// ``` | ||
230 | /// let five = 5; | ||
231 | /// | ||
232 | /// assert_eq!(6, my_crate::add_one(5)); | ||
233 | /// ``` | ||
234 | pub fn add_one(x: i32) -> i32 { | ||
235 | x + 1 | ||
236 | } | ||
237 | |||
238 | pub fn do() { | ||
239 | add_one(<|> | ||
240 | }"#, | ||
241 | ); | ||
242 | |||
243 | assert_eq!(desc.name, "add_one".to_string()); | ||
244 | assert_eq!(desc.params, vec!["x".to_string()]); | ||
245 | assert_eq!(desc.ret_type, Some("-> i32".to_string())); | ||
246 | assert_eq!(param, Some(0)); | ||
247 | assert_eq!(desc.label, "pub fn add_one(x: i32) -> i32".to_string()); | ||
248 | assert_eq!(desc.doc, Some( | ||
249 | r#"Adds one to the number given. | ||
250 | |||
251 | # Examples | ||
252 | |||
253 | ```rust | ||
254 | let five = 5; | ||
255 | |||
256 | assert_eq!(6, my_crate::add_one(5)); | ||
257 | ```"#.into())); | ||
258 | } | ||
259 | |||
260 | #[test] | ||
261 | fn test_fn_signature_with_docs_impl() { | ||
262 | let (desc, param) = get_signature( | ||
263 | r#" | ||
264 | struct addr; | ||
265 | impl addr { | ||
266 | /// Adds one to the number given. | ||
267 | /// | ||
268 | /// # Examples | ||
269 | /// | ||
270 | /// ``` | ||
271 | /// let five = 5; | ||
272 | /// | ||
273 | /// assert_eq!(6, my_crate::add_one(5)); | ||
274 | /// ``` | ||
275 | pub fn add_one(x: i32) -> i32 { | ||
276 | x + 1 | ||
277 | } | ||
278 | } | ||
279 | |||
280 | pub fn do_it() { | ||
281 | addr {}; | ||
282 | addr::add_one(<|>); | ||
283 | }"#); | ||
284 | |||
285 | assert_eq!(desc.name, "add_one".to_string()); | ||
286 | assert_eq!(desc.params, vec!["x".to_string()]); | ||
287 | assert_eq!(desc.ret_type, Some("-> i32".to_string())); | ||
288 | assert_eq!(param, Some(0)); | ||
289 | assert_eq!(desc.label, "pub fn add_one(x: i32) -> i32".to_string()); | ||
290 | assert_eq!(desc.doc, Some( | ||
291 | r#"Adds one to the number given. | ||
292 | |||
293 | # Examples | ||
294 | |||
295 | ```rust | ||
296 | let five = 5; | ||
297 | |||
298 | assert_eq!(6, my_crate::add_one(5)); | ||
299 | ```"#.into())); | ||
300 | } | ||
301 | |||
302 | #[test] | ||
303 | fn test_fn_signature_with_docs_from_actix() { | ||
304 | let (desc, param) = get_signature( | ||
305 | r#" | ||
306 | pub trait WriteHandler<E> | ||
307 | where | ||
308 | Self: Actor, | ||
309 | Self::Context: ActorContext, | ||
310 | { | ||
311 | /// Method is called when writer emits error. | ||
312 | /// | ||
313 | /// If this method returns `ErrorAction::Continue` writer processing | ||
314 | /// continues otherwise stream processing stops. | ||
315 | fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running { | ||
316 | Running::Stop | ||
317 | } | ||
318 | |||
319 | /// Method is called when writer finishes. | ||
320 | /// | ||
321 | /// By default this method stops actor's `Context`. | ||
322 | fn finished(&mut self, ctx: &mut Self::Context) { | ||
323 | ctx.stop() | ||
324 | } | ||
325 | } | ||
326 | |||
327 | pub fn foo() { | ||
328 | WriteHandler r; | ||
329 | r.finished(<|>); | ||
330 | } | ||
331 | |||
332 | "#); | ||
333 | |||
334 | assert_eq!(desc.name, "finished".to_string()); | ||
335 | assert_eq!(desc.params, vec!["&mut self".to_string(), "ctx".to_string()]); | ||
336 | assert_eq!(desc.ret_type, None); | ||
337 | assert_eq!(param, Some(1)); | ||
338 | assert_eq!(desc.doc, Some( | ||
339 | r#"Method is called when writer finishes. | ||
340 | |||
341 | By default this method stops actor's `Context`."#.into())); | ||
342 | } | ||
343 | |||
344 | |||
198 | fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { | 345 | fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { |
199 | let (analysis, position) = single_file_with_position(text); | 346 | let (analysis, position) = single_file_with_position(text); |
200 | analysis.find_all_refs(position.file_id, position.offset).unwrap() | 347 | analysis.find_all_refs(position.file_id, position.offset).unwrap() |