diff options
Diffstat (limited to 'crates/ra_hir/src/function.rs')
-rw-r--r-- | crates/ra_hir/src/function.rs | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/crates/ra_hir/src/function.rs b/crates/ra_hir/src/function.rs new file mode 100644 index 000000000..5187dc051 --- /dev/null +++ b/crates/ra_hir/src/function.rs | |||
@@ -0,0 +1,151 @@ | |||
1 | mod scope; | ||
2 | |||
3 | use std::{ | ||
4 | cmp::{max, min}, | ||
5 | sync::Arc, | ||
6 | }; | ||
7 | |||
8 | use ra_syntax::{ | ||
9 | TextRange, TextUnit, | ||
10 | ast::{self, AstNode, DocCommentsOwner, NameOwner}, | ||
11 | }; | ||
12 | |||
13 | use crate::{ DefId, HirDatabase }; | ||
14 | |||
15 | pub use self::scope::FnScopes; | ||
16 | |||
17 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
18 | pub struct FnId(pub(crate) DefId); | ||
19 | |||
20 | pub struct Function { | ||
21 | fn_id: FnId, | ||
22 | } | ||
23 | |||
24 | impl Function { | ||
25 | pub(crate) fn new(def_id: DefId) -> Function { | ||
26 | let fn_id = FnId(def_id); | ||
27 | Function { fn_id } | ||
28 | } | ||
29 | |||
30 | pub fn scope(&self, db: &impl HirDatabase) -> Arc<FnScopes> { | ||
31 | db.fn_scopes(self.fn_id) | ||
32 | } | ||
33 | |||
34 | pub fn signature_info(&self, db: &impl HirDatabase) -> Option<FnSignatureInfo> { | ||
35 | let syntax = db.fn_syntax(self.fn_id); | ||
36 | FnSignatureInfo::new(syntax.borrowed()) | ||
37 | } | ||
38 | } | ||
39 | |||
40 | #[derive(Debug, Clone)] | ||
41 | pub struct FnSignatureInfo { | ||
42 | pub name: String, | ||
43 | pub label: String, | ||
44 | pub ret_type: Option<String>, | ||
45 | pub params: Vec<String>, | ||
46 | pub doc: Option<String>, | ||
47 | } | ||
48 | |||
49 | impl FnSignatureInfo { | ||
50 | fn new(node: ast::FnDef) -> Option<Self> { | ||
51 | let name = node.name()?.text().to_string(); | ||
52 | |||
53 | let mut doc = None; | ||
54 | |||
55 | // Strip the body out for the label. | ||
56 | let mut label: String = if let Some(body) = node.body() { | ||
57 | let body_range = body.syntax().range(); | ||
58 | let label: String = node | ||
59 | .syntax() | ||
60 | .children() | ||
61 | .filter(|child| !child.range().is_subrange(&body_range)) | ||
62 | .map(|node| node.text().to_string()) | ||
63 | .collect(); | ||
64 | label | ||
65 | } else { | ||
66 | node.syntax().text().to_string() | ||
67 | }; | ||
68 | |||
69 | if let Some((comment_range, docs)) = FnSignatureInfo::extract_doc_comments(node) { | ||
70 | let comment_range = comment_range | ||
71 | .checked_sub(node.syntax().range().start()) | ||
72 | .unwrap(); | ||
73 | let start = comment_range.start().to_usize(); | ||
74 | let end = comment_range.end().to_usize(); | ||
75 | |||
76 | // Remove the comment from the label | ||
77 | label.replace_range(start..end, ""); | ||
78 | |||
79 | // Massage markdown | ||
80 | let mut processed_lines = Vec::new(); | ||
81 | let mut in_code_block = false; | ||
82 | for line in docs.lines() { | ||
83 | if line.starts_with("```") { | ||
84 | in_code_block = !in_code_block; | ||
85 | } | ||
86 | |||
87 | let line = if in_code_block && line.starts_with("```") && !line.contains("rust") { | ||
88 | "```rust".into() | ||
89 | } else { | ||
90 | line.to_string() | ||
91 | }; | ||
92 | |||
93 | processed_lines.push(line); | ||
94 | } | ||
95 | |||
96 | if !processed_lines.is_empty() { | ||
97 | doc = Some(processed_lines.join("\n")); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | let params = FnSignatureInfo::param_list(node); | ||
102 | let ret_type = node.ret_type().map(|r| r.syntax().text().to_string()); | ||
103 | |||
104 | Some(FnSignatureInfo { | ||
105 | name, | ||
106 | ret_type, | ||
107 | params, | ||
108 | label: label.trim().to_owned(), | ||
109 | doc, | ||
110 | }) | ||
111 | } | ||
112 | |||
113 | fn extract_doc_comments(node: ast::FnDef) -> Option<(TextRange, String)> { | ||
114 | if node.doc_comments().count() == 0 { | ||
115 | return None; | ||
116 | } | ||
117 | |||
118 | let comment_text = node.doc_comment_text(); | ||
119 | |||
120 | let (begin, end) = node | ||
121 | .doc_comments() | ||
122 | .map(|comment| comment.syntax().range()) | ||
123 | .map(|range| (range.start().to_usize(), range.end().to_usize())) | ||
124 | .fold((std::usize::MAX, std::usize::MIN), |acc, range| { | ||
125 | (min(acc.0, range.0), max(acc.1, range.1)) | ||
126 | }); | ||
127 | |||
128 | let range = TextRange::from_to(TextUnit::from_usize(begin), TextUnit::from_usize(end)); | ||
129 | |||
130 | Some((range, comment_text)) | ||
131 | } | ||
132 | |||
133 | fn param_list(node: ast::FnDef) -> Vec<String> { | ||
134 | let mut res = vec![]; | ||
135 | if let Some(param_list) = node.param_list() { | ||
136 | if let Some(self_param) = param_list.self_param() { | ||
137 | res.push(self_param.syntax().text().to_string()) | ||
138 | } | ||
139 | |||
140 | // Maybe use param.pat here? See if we can just extract the name? | ||
141 | //res.extend(param_list.params().map(|p| p.syntax().text().to_string())); | ||
142 | res.extend( | ||
143 | param_list | ||
144 | .params() | ||
145 | .filter_map(|p| p.pat()) | ||
146 | .map(|pat| pat.syntax().text().to_string()), | ||
147 | ); | ||
148 | } | ||
149 | res | ||
150 | } | ||
151 | } | ||