]> git.lizzy.rs Git - rust.git/blobdiff - crates/ra_ide_api/src/call_info.rs
Pass Documentation up to LSP and add "rust" to our codeblocks there
[rust.git] / crates / ra_ide_api / src / call_info.rs
index efa320d83d0bf1ac4b0b1f155c66cef6a511600f..2eb388e0e763dec1ac33342ce9858245881c3aab 100644 (file)
@@ -1,31 +1,35 @@
-use std::cmp::{max, min};
-
-use ra_db::{SyntaxDatabase, Cancelable};
+use test_utils::tested_by;
+use ra_db::SourceDatabase;
 use ra_syntax::{
     AstNode, SyntaxNode, TextUnit, TextRange,
     SyntaxKind::FN_DEF,
-    ast::{self, ArgListOwner, DocCommentsOwner},
+    ast::{self, ArgListOwner},
     algo::find_node_at_offset,
 };
+use hir::Docs;
 
 use crate::{FilePosition, CallInfo, db::RootDatabase};
 
 /// Computes parameter information for the given call expression.
-pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Cancelable<Option<CallInfo>> {
-    let file = db.source_file(position.file_id);
+pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> {
+    let file = db.parse(position.file_id);
     let syntax = file.syntax();
 
     // Find the calling expression and it's NameRef
-    let calling_node = ctry!(FnCallNode::with_node(syntax, position.offset));
-    let name_ref = ctry!(calling_node.name_ref());
+    let calling_node = FnCallNode::with_node(syntax, position.offset)?;
+    let name_ref = calling_node.name_ref()?;
 
     // Resolve the function's NameRef (NOTE: this isn't entirely accurate).
     let file_symbols = db.index_resolve(name_ref);
-    let symbol = ctry!(file_symbols.into_iter().find(|it| it.ptr.kind() == FN_DEF));
-    let fn_file = db.source_file(symbol.file_id);
-    let fn_def = symbol.ptr.resolve(&fn_file);
-    let fn_def = ast::FnDef::cast(&fn_def).unwrap();
-    let mut call_info = ctry!(CallInfo::new(fn_def));
+    let symbol = file_symbols
+        .into_iter()
+        .find(|it| it.ptr.kind() == FN_DEF)?;
+    let fn_file = db.parse(symbol.file_id);
+    let fn_def = symbol.ptr.to_node(&fn_file);
+    let fn_def = ast::FnDef::cast(fn_def).unwrap();
+    let function = hir::source_binder::function_from_source(db, symbol.file_id, fn_def)?;
+
+    let mut call_info = CallInfo::new(db, function, fn_def)?;
     // If we have a calling expression let's find which argument we are on
     let num_params = call_info.parameters.len();
     let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some();
@@ -41,7 +45,12 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Cancelable
         // where offset is in that list (or beyond).
         // Revisit this after we get documentation comments in.
         if let Some(ref arg_list) = calling_node.arg_list() {
-            let start = arg_list.syntax().range().start();
+            let arg_list_range = arg_list.syntax().range();
+            if !arg_list_range.contains_inclusive(position.offset) {
+                tested_by!(call_info_bad_offset);
+                return None;
+            }
+            let start = arg_list_range.start();
 
             let range_search = TextRange::from_to(start, position.offset);
             let mut commas: usize = arg_list
@@ -61,7 +70,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Cancelable
         }
     }
 
-    Ok(Some(call_info))
+    Some(call_info)
 }
 
 enum FnCallNode<'a> {
@@ -104,84 +113,19 @@ pub fn arg_list(&self) -> Option<&'a ast::ArgList> {
 }
 
 impl CallInfo {
-    fn new(node: &ast::FnDef) -> Option<Self> {
-        let mut doc = None;
-
-        // Strip the body out for the label.
-        let mut label: String = if let Some(body) = node.body() {
-            let body_range = body.syntax().range();
-            let label: String = node
-                .syntax()
-                .children()
-                .filter(|child| !child.range().is_subrange(&body_range))
-                .map(|node| node.text().to_string())
-                .collect();
-            label
-        } else {
-            node.syntax().text().to_string()
-        };
-
-        if let Some((comment_range, docs)) = extract_doc_comments(node) {
-            let comment_range = comment_range
-                .checked_sub(node.syntax().range().start())
-                .unwrap();
-            let start = comment_range.start().to_usize();
-            let end = comment_range.end().to_usize();
-
-            // Remove the comment from the label
-            label.replace_range(start..end, "");
-
-            // Massage markdown
-            let mut processed_lines = Vec::new();
-            let mut in_code_block = false;
-            for line in docs.lines() {
-                if line.starts_with("```") {
-                    in_code_block = !in_code_block;
-                }
-
-                let line = if in_code_block && line.starts_with("```") && !line.contains("rust") {
-                    "```rust".into()
-                } else {
-                    line.to_string()
-                };
-
-                processed_lines.push(line);
-            }
-
-            if !processed_lines.is_empty() {
-                doc = Some(processed_lines.join("\n"));
-            }
-        }
+    fn new(db: &RootDatabase, function: hir::Function, node: &ast::FnDef) -> Option<Self> {
+        let label = crate::completion::function_label(node)?;
+        let doc = function.docs(db);
 
         Some(CallInfo {
             parameters: param_list(node),
-            label: label.trim().to_owned(),
+            label,
             doc,
             active_parameter: None,
         })
     }
 }
 
-fn extract_doc_comments(node: &ast::FnDef) -> Option<(TextRange, String)> {
-    if node.doc_comments().count() == 0 {
-        return None;
-    }
-
-    let comment_text = node.doc_comment_text();
-
-    let (begin, end) = node
-        .doc_comments()
-        .map(|comment| comment.syntax().range())
-        .map(|range| (range.start().to_usize(), range.end().to_usize()))
-        .fold((std::usize::MAX, std::usize::MIN), |acc, range| {
-            (min(acc.0, range.0), max(acc.1, range.1))
-        });
-
-    let range = TextRange::from_to(TextUnit::from_usize(begin), TextUnit::from_usize(end));
-
-    Some((range, comment_text))
-}
-
 fn param_list(node: &ast::FnDef) -> Vec<String> {
     let mut res = vec![];
     if let Some(param_list) = node.param_list() {
@@ -203,10 +147,12 @@ fn param_list(node: &ast::FnDef) -> Vec<String> {
 
 #[cfg(test)]
 mod tests {
-    use super::*;
+    use test_utils::covers;
 
     use crate::mock_analysis::single_file_with_position;
 
+    use super::*;
+
     fn call_info(text: &str) -> CallInfo {
         let (analysis, position) = single_file_with_position(text);
         analysis.call_info(position).unwrap().unwrap()
@@ -308,7 +254,7 @@ fn bar() {
         assert_eq!(info.parameters, vec!["j".to_string()]);
         assert_eq!(info.active_parameter, Some(0));
         assert_eq!(info.label, "fn foo(j: u32) -> u32".to_string());
-        assert_eq!(info.doc, Some("test".into()));
+        assert_eq!(info.doc.map(|it| it.into()), Some("test".to_string()));
     }
 
     #[test]
@@ -337,18 +283,18 @@ pub fn do() {
         assert_eq!(info.active_parameter, Some(0));
         assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string());
         assert_eq!(
-            info.doc,
+            info.doc.map(|it| it.into()),
             Some(
                 r#"Adds one to the number given.
 
 # Examples
 
-```rust
+```
 let five = 5;
 
 assert_eq!(6, my_crate::add_one(5));
 ```"#
-                    .into()
+                    .to_string()
             )
         );
     }
@@ -383,18 +329,18 @@ pub fn do_it() {
         assert_eq!(info.active_parameter, Some(0));
         assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string());
         assert_eq!(
-            info.doc,
+            info.doc.map(|it| it.into()),
             Some(
                 r#"Adds one to the number given.
 
 # Examples
 
-```rust
+```
 let five = 5;
 
 assert_eq!(6, my_crate::add_one(5));
 ```"#
-                    .into()
+                    .to_string()
             )
         );
     }
@@ -438,14 +384,24 @@ pub fn foo() {
         );
         assert_eq!(info.active_parameter, Some(1));
         assert_eq!(
-            info.doc,
+            info.doc.map(|it| it.into()),
             Some(
                 r#"Method is called when writer finishes.
 
 By default this method stops actor's `Context`."#
-                    .into()
+                    .to_string()
             )
         );
     }
 
+    #[test]
+    fn call_info_bad_offset() {
+        covers!(call_info_bad_offset);
+        let (analysis, position) = single_file_with_position(
+            r#"fn foo(x: u32, y: u32) -> u32 {x + y}
+               fn bar() { foo <|> (3, ); }"#,
+        );
+        let call_info = analysis.call_info(position).unwrap();
+        assert!(call_info.is_none());
+    }
 }