-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();
// 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
}
}
- Ok(Some(call_info))
+ Some(call_info)
}
enum FnCallNode<'a> {
}
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() {
#[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()
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]
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()
)
);
}
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()
)
);
}
);
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());
+ }
}