aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2018-10-31 19:41:24 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2018-10-31 19:41:24 +0000
commit55ebe6380aef233fff86b7e6cead361787bf1f65 (patch)
tree9ed060e4738a0504ddfd146649b9cf8a2f2fac40
parentdfba29e4fb66457d101db295e3c356a932ac005e (diff)
parent74320945b664916005d263552194201cbe9a52bc (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]>
-rw-r--r--crates/ra_analysis/src/descriptors/function/mod.rs60
-rw-r--r--crates/ra_analysis/tests/tests.rs147
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs13
-rw-r--r--crates/ra_syntax/src/ast/generated.rs1
-rw-r--r--crates/ra_syntax/src/ast/mod.rs18
-rw-r--r--crates/ra_syntax/src/grammar.ron1
6 files changed, 235 insertions, 5 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 @@
1pub(super) mod imp; 1pub(super) mod imp;
2mod scope; 2mod scope;
3 3
4use std::cmp::{min, max};
5
4use ra_syntax::{ 6use ra_syntax::{
5 ast::{self, AstNode, NameOwner} 7 ast::{self, AstNode, DocCommentsOwner, NameOwner},
8 TextRange, TextUnit
6}; 9};
7 10
8use crate::{ 11use 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
35impl FnDescriptor { 39impl 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]
199fn test_fn_signature_with_docs_simple() {
200 let (desc, param) = get_signature(
201 r#"
202// test
203fn foo(j: u32) -> u32 {
204 j
205}
206
207fn 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]
222fn 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/// ```
234pub fn add_one(x: i32) -> i32 {
235 x + 1
236}
237
238pub 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(
249r#"Adds one to the number given.
250
251# Examples
252
253```rust
254let five = 5;
255
256assert_eq!(6, my_crate::add_one(5));
257```"#.into()));
258}
259
260#[test]
261fn test_fn_signature_with_docs_impl() {
262 let (desc, param) = get_signature(
263 r#"
264struct addr;
265impl 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
280pub 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(
291r#"Adds one to the number given.
292
293# Examples
294
295```rust
296let five = 5;
297
298assert_eq!(6, my_crate::add_one(5));
299```"#.into()));
300}
301
302#[test]
303fn test_fn_signature_with_docs_from_actix() {
304 let (desc, param) = get_signature(
305 r#"
306pub trait WriteHandler<E>
307where
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
327pub 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(
339r#"Method is called when writer finishes.
340
341By default this method stops actor's `Context`."#.into()));
342}
343
344
198fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { 345fn 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()
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 4ac08e527..20cb5f772 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -5,7 +5,7 @@ use languageserver_types::{
5 CodeActionResponse, Command, CompletionItem, CompletionItemKind, Diagnostic, 5 CodeActionResponse, Command, CompletionItem, CompletionItemKind, Diagnostic,
6 DiagnosticSeverity, DocumentSymbol, FoldingRange, FoldingRangeKind, FoldingRangeParams, 6 DiagnosticSeverity, DocumentSymbol, FoldingRange, FoldingRangeKind, FoldingRangeParams,
7 InsertTextFormat, Location, Position, SymbolInformation, TextDocumentIdentifier, TextEdit, 7 InsertTextFormat, Location, Position, SymbolInformation, TextDocumentIdentifier, TextEdit,
8 RenameParams, WorkspaceEdit, PrepareRenameResponse 8 RenameParams, WorkspaceEdit, PrepareRenameResponse, Documentation, MarkupContent, MarkupKind
9}; 9};
10use gen_lsp_server::ErrorCode; 10use gen_lsp_server::ErrorCode;
11use ra_analysis::{FileId, FoldKind, Query, RunnableKind}; 11use ra_analysis::{FileId, FoldKind, Query, RunnableKind};
@@ -465,9 +465,18 @@ pub fn handle_signature_help(
465 }) 465 })
466 .collect(); 466 .collect();
467 467
468 let documentation = if let Some(doc) = descriptor.doc {
469 Some(Documentation::MarkupContent(MarkupContent {
470 kind: MarkupKind::Markdown,
471 value: doc
472 }))
473 } else {
474 None
475 };
476
468 let sig_info = SignatureInformation { 477 let sig_info = SignatureInformation {
469 label: descriptor.label, 478 label: descriptor.label,
470 documentation: None, 479 documentation,
471 parameters: Some(parameters), 480 parameters: Some(parameters),
472 }; 481 };
473 482
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index d0cd060d3..b6a15dbdc 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -864,6 +864,7 @@ impl<'a> AstNode<'a> for FnDef<'a> {
864impl<'a> ast::NameOwner<'a> for FnDef<'a> {} 864impl<'a> ast::NameOwner<'a> for FnDef<'a> {}
865impl<'a> ast::TypeParamsOwner<'a> for FnDef<'a> {} 865impl<'a> ast::TypeParamsOwner<'a> for FnDef<'a> {}
866impl<'a> ast::AttrsOwner<'a> for FnDef<'a> {} 866impl<'a> ast::AttrsOwner<'a> for FnDef<'a> {}
867impl<'a> ast::DocCommentsOwner<'a> for FnDef<'a> {}
867impl<'a> FnDef<'a> { 868impl<'a> FnDef<'a> {
868 pub fn param_list(self) -> Option<ParamList<'a>> { 869 pub fn param_list(self) -> Option<ParamList<'a>> {
869 super::child_opt(self) 870 super::child_opt(self)
diff --git a/crates/ra_syntax/src/ast/mod.rs b/crates/ra_syntax/src/ast/mod.rs
index c033263a1..3aa11b9dd 100644
--- a/crates/ra_syntax/src/ast/mod.rs
+++ b/crates/ra_syntax/src/ast/mod.rs
@@ -65,6 +65,24 @@ pub trait AttrsOwner<'a>: AstNode<'a> {
65 } 65 }
66} 66}
67 67
68pub trait DocCommentsOwner<'a>: AstNode<'a> {
69 fn doc_comments(self) -> AstChildren<'a, Comment<'a>> { children(self) }
70
71 /// Returns the textual content of a doc comment block as a single string.
72 /// That is, strips leading `///` and joins lines
73 fn doc_comment_text(self) -> String {
74 self.doc_comments()
75 .map(|comment| {
76 let prefix = comment.prefix();
77 let trimmed = comment.text().as_str()
78 .trim()
79 .trim_start_matches(prefix)
80 .trim_start();
81 trimmed.to_owned()
82 }).join("\n")
83 }
84}
85
68impl<'a> FnDef<'a> { 86impl<'a> FnDef<'a> {
69 pub fn has_atom_attr(&self, atom: &str) -> bool { 87 pub fn has_atom_attr(&self, atom: &str) -> bool {
70 self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom) 88 self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom)
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index c1c215e0d..6951db010 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -251,6 +251,7 @@ Grammar(
251 "NameOwner", 251 "NameOwner",
252 "TypeParamsOwner", 252 "TypeParamsOwner",
253 "AttrsOwner", 253 "AttrsOwner",
254 "DocCommentsOwner"
254 ], 255 ],
255 options: [ "ParamList", ["body", "Block"], "RetType" ], 256 options: [ "ParamList", ["body", "Block"], "RetType" ],
256 ), 257 ),