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