aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-04-02 08:50:09 +0100
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-04-02 08:50:09 +0100
commitd21a677715196c46b73017acbae0105ef554284d (patch)
tree318d41ba567cf7ce1c5b4ead1857da64aa24242f
parentc2912892effbcf24d94da235b9ac0d2a7fccea5d (diff)
parent3f3ff2f0f4c188c606a96506325d96726c842239 (diff)
Merge #1085
1085: add ast::tokens r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--crates/ra_assists/src/add_missing_impl_members.rs2
-rw-r--r--crates/ra_assists/src/inline_local_variable.rs9
-rw-r--r--crates/ra_fmt/src/lib.rs3
-rw-r--r--crates/ra_ide_api/src/completion/snapshots/completion_item__enum_variant_with_details.snap9
-rw-r--r--crates/ra_ide_api/src/extend_selection.rs12
-rw-r--r--crates/ra_ide_api/src/folding_ranges.rs10
-rw-r--r--crates/ra_ide_api/src/join_lines.rs4
-rw-r--r--crates/ra_ide_api/src/typing.rs2
-rw-r--r--crates/ra_syntax/src/ast.rs275
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs92
-rw-r--r--crates/ra_syntax/src/ast/traits.rs150
11 files changed, 292 insertions, 276 deletions
diff --git a/crates/ra_assists/src/add_missing_impl_members.rs b/crates/ra_assists/src/add_missing_impl_members.rs
index 5b01e898e..19a2d05bc 100644
--- a/crates/ra_assists/src/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/add_missing_impl_members.rs
@@ -5,7 +5,7 @@ use crate::{Assist, AssistId, AssistCtx};
5use hir::Resolver; 5use hir::Resolver;
6use hir::db::HirDatabase; 6use hir::db::HirDatabase;
7use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc}; 7use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc};
8use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner}; 8use ra_syntax::ast::{self, AstNode, AstToken, FnDef, ImplItem, ImplItemKind, NameOwner};
9use ra_db::FilePosition; 9use ra_db::FilePosition;
10use ra_fmt::{leading_indent, reindent}; 10use ra_fmt::{leading_indent, reindent};
11 11
diff --git a/crates/ra_assists/src/inline_local_variable.rs b/crates/ra_assists/src/inline_local_variable.rs
index 0d7b884b8..950c2910b 100644
--- a/crates/ra_assists/src/inline_local_variable.rs
+++ b/crates/ra_assists/src/inline_local_variable.rs
@@ -1,14 +1,9 @@
1use hir::{ 1use hir::{
2 db::HirDatabase, 2 db::HirDatabase,
3 source_binder::function_from_child_node 3 source_binder::function_from_child_node,
4}; 4};
5use ra_syntax::{ 5use ra_syntax::{
6 ast::{ 6 ast::{self, AstNode, AstToken, PatKind, ExprKind},
7 self,
8 AstNode,
9 PatKind,
10 ExprKind
11 },
12 TextRange, 7 TextRange,
13}; 8};
14 9
diff --git a/crates/ra_fmt/src/lib.rs b/crates/ra_fmt/src/lib.rs
index ea90dc2b8..85b7ce250 100644
--- a/crates/ra_fmt/src/lib.rs
+++ b/crates/ra_fmt/src/lib.rs
@@ -2,9 +2,8 @@
2//! 2//!
3use itertools::Itertools; 3use itertools::Itertools;
4use ra_syntax::{ 4use ra_syntax::{
5 AstNode,
6 SyntaxNode, SyntaxKind::*, SyntaxToken, SyntaxKind, 5 SyntaxNode, SyntaxKind::*, SyntaxToken, SyntaxKind,
7 ast, 6 ast::{self, AstNode, AstToken},
8 algo::generate, 7 algo::generate,
9}; 8};
10 9
diff --git a/crates/ra_ide_api/src/completion/snapshots/completion_item__enum_variant_with_details.snap b/crates/ra_ide_api/src/completion/snapshots/completion_item__enum_variant_with_details.snap
index 70ea96e1b..daccd9fba 100644
--- a/crates/ra_ide_api/src/completion/snapshots/completion_item__enum_variant_with_details.snap
+++ b/crates/ra_ide_api/src/completion/snapshots/completion_item__enum_variant_with_details.snap
@@ -1,6 +1,6 @@
1--- 1---
2created: "2019-02-18T09:22:24.062138085Z" 2created: "2019-04-02T07:43:12.954637543Z"
3creator: insta@0.6.2 3creator: insta@0.7.4
4source: crates/ra_ide_api/src/completion/completion_item.rs 4source: crates/ra_ide_api/src/completion/completion_item.rs
5expression: kind_completions 5expression: kind_completions
6--- 6---
@@ -33,6 +33,9 @@ expression: kind_completions
33 delete: [180; 180), 33 delete: [180; 180),
34 insert: "S", 34 insert: "S",
35 kind: EnumVariant, 35 kind: EnumVariant,
36 detail: "(S)" 36 detail: "(S)",
37 documentation: Documentation(
38 ""
39 )
37 } 40 }
38] 41]
diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs
index e743bf0fe..7293ba359 100644
--- a/crates/ra_ide_api/src/extend_selection.rs
+++ b/crates/ra_ide_api/src/extend_selection.rs
@@ -1,9 +1,9 @@
1use ra_db::SourceDatabase; 1use ra_db::SourceDatabase;
2use ra_syntax::{ 2use ra_syntax::{
3 Direction, SyntaxNode, TextRange, TextUnit, AstNode, SyntaxElement, 3 Direction, SyntaxNode, TextRange, TextUnit, SyntaxElement,
4 algo::{find_covering_element, find_token_at_offset, TokenAtOffset}, 4 algo::{find_covering_element, find_token_at_offset, TokenAtOffset},
5 SyntaxKind::*, SyntaxToken, 5 SyntaxKind::*, SyntaxToken,
6 ast::Comment, 6 ast::{self, AstNode, AstToken},
7}; 7};
8 8
9use crate::{FileRange, db::RootDatabase}; 9use crate::{FileRange, db::RootDatabase};
@@ -55,7 +55,7 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
55 if token.range() != range { 55 if token.range() != range {
56 return Some(token.range()); 56 return Some(token.range());
57 } 57 }
58 if let Some(comment) = Comment::cast(token) { 58 if let Some(comment) = ast::Comment::cast(token) {
59 if let Some(range) = extend_comments(comment) { 59 if let Some(range) = extend_comments(comment) {
60 return Some(range); 60 return Some(range);
61 } 61 }
@@ -176,7 +176,7 @@ fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
176 None 176 None
177} 177}
178 178
179fn extend_comments(comment: Comment) -> Option<TextRange> { 179fn extend_comments(comment: ast::Comment) -> Option<TextRange> {
180 let prev = adj_comments(comment, Direction::Prev); 180 let prev = adj_comments(comment, Direction::Prev);
181 let next = adj_comments(comment, Direction::Next); 181 let next = adj_comments(comment, Direction::Next);
182 if prev != next { 182 if prev != next {
@@ -186,14 +186,14 @@ fn extend_comments(comment: Comment) -> Option<TextRange> {
186 } 186 }
187} 187}
188 188
189fn adj_comments(comment: Comment, dir: Direction) -> Comment { 189fn adj_comments(comment: ast::Comment, dir: Direction) -> ast::Comment {
190 let mut res = comment; 190 let mut res = comment;
191 for element in comment.syntax().siblings_with_tokens(dir) { 191 for element in comment.syntax().siblings_with_tokens(dir) {
192 let token = match element.as_token() { 192 let token = match element.as_token() {
193 None => break, 193 None => break,
194 Some(token) => token, 194 Some(token) => token,
195 }; 195 };
196 if let Some(c) = Comment::cast(token) { 196 if let Some(c) = ast::Comment::cast(token) {
197 res = c 197 res = c
198 } else if token.kind() != WHITESPACE || token.text().contains("\n\n") { 198 } else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
199 break; 199 break;
diff --git a/crates/ra_ide_api/src/folding_ranges.rs b/crates/ra_ide_api/src/folding_ranges.rs
index a6fe8a5d5..eada0b7de 100644
--- a/crates/ra_ide_api/src/folding_ranges.rs
+++ b/crates/ra_ide_api/src/folding_ranges.rs
@@ -1,9 +1,9 @@
1use rustc_hash::FxHashSet; 1use rustc_hash::FxHashSet;
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 AstNode, SourceFile, SyntaxNode, TextRange, Direction, SyntaxElement, 4 SourceFile, SyntaxNode, TextRange, Direction, SyntaxElement,
5 SyntaxKind::{self, *}, 5 SyntaxKind::{self, *},
6 ast::{self, VisibilityOwner, Comment}, 6 ast::{self, AstNode, AstToken, VisibilityOwner},
7}; 7};
8 8
9#[derive(Debug, PartialEq, Eq)] 9#[derive(Debug, PartialEq, Eq)]
@@ -139,8 +139,8 @@ fn contiguous_range_for_group_unless<'a>(
139} 139}
140 140
141fn contiguous_range_for_comment<'a>( 141fn contiguous_range_for_comment<'a>(
142 first: Comment<'a>, 142 first: ast::Comment<'a>,
143 visited: &mut FxHashSet<Comment<'a>>, 143 visited: &mut FxHashSet<ast::Comment<'a>>,
144) -> Option<TextRange> { 144) -> Option<TextRange> {
145 visited.insert(first); 145 visited.insert(first);
146 146
@@ -157,7 +157,7 @@ fn contiguous_range_for_comment<'a>(
157 continue; 157 continue;
158 } 158 }
159 } 159 }
160 if let Some(c) = Comment::cast(token) { 160 if let Some(c) = ast::Comment::cast(token) {
161 if c.flavor() == group_flavor { 161 if c.flavor() == group_flavor {
162 visited.insert(c); 162 visited.insert(c);
163 last = c; 163 last = c;
diff --git a/crates/ra_ide_api/src/join_lines.rs b/crates/ra_ide_api/src/join_lines.rs
index 57b6f8384..598717311 100644
--- a/crates/ra_ide_api/src/join_lines.rs
+++ b/crates/ra_ide_api/src/join_lines.rs
@@ -1,9 +1,9 @@
1use itertools::Itertools; 1use itertools::Itertools;
2use ra_syntax::{ 2use ra_syntax::{
3 SourceFile, TextRange, TextUnit, AstNode, SyntaxNode, SyntaxElement, SyntaxToken, 3 SourceFile, TextRange, TextUnit, SyntaxNode, SyntaxElement, SyntaxToken,
4 SyntaxKind::{self, WHITESPACE, COMMA, R_CURLY, R_PAREN, R_BRACK}, 4 SyntaxKind::{self, WHITESPACE, COMMA, R_CURLY, R_PAREN, R_BRACK},
5 algo::{find_covering_element, non_trivia_sibling}, 5 algo::{find_covering_element, non_trivia_sibling},
6 ast, 6 ast::{self, AstNode, AstToken},
7 Direction, 7 Direction,
8}; 8};
9use ra_fmt::{ 9use ra_fmt::{
diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs
index 4510d663d..aeeeea082 100644
--- a/crates/ra_ide_api/src/typing.rs
+++ b/crates/ra_ide_api/src/typing.rs
@@ -2,7 +2,7 @@ use ra_syntax::{
2 AstNode, SourceFile, SyntaxKind::*, 2 AstNode, SourceFile, SyntaxKind::*,
3 TextUnit, TextRange, SyntaxToken, 3 TextUnit, TextRange, SyntaxToken,
4 algo::{find_node_at_offset, find_token_at_offset, TokenAtOffset}, 4 algo::{find_node_at_offset, find_token_at_offset, TokenAtOffset},
5 ast::{self}, 5 ast::{self, AstToken},
6}; 6};
7use ra_fmt::leading_indent; 7use ra_fmt::leading_indent;
8use ra_text_edit::{TextEdit, TextEditBuilder}; 8use ra_text_edit::{TextEdit, TextEditBuilder};
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index ffd115cef..beef2c6e2 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -1,17 +1,24 @@
1//! Abstract Syntax Tree, layered on top of untyped `SyntaxNode`s 1//! Abstract Syntax Tree, layered on top of untyped `SyntaxNode`s
2mod generated; 2mod generated;
3mod traits;
4mod tokens;
3 5
4use std::marker::PhantomData; 6use std::marker::PhantomData;
5 7
6use itertools::Itertools; 8use itertools::Itertools;
7 9
8pub use self::generated::*;
9use crate::{ 10use crate::{
10 syntax_node::{SyntaxNode, SyntaxNodeChildren, TreeArc, RaTypes, SyntaxToken, SyntaxElement, SyntaxElementChildren}, 11 syntax_node::{SyntaxNode, SyntaxNodeChildren, TreeArc, RaTypes, SyntaxToken, SyntaxElement},
11 SmolStr, 12 SmolStr,
12 SyntaxKind::*, 13 SyntaxKind::*,
13}; 14};
14 15
16pub use self::{
17 generated::*,
18 traits::*,
19 tokens::*,
20};
21
15/// The main trait to go from untyped `SyntaxNode` to a typed ast. The 22/// The main trait to go from untyped `SyntaxNode` to a typed ast. The
16/// conversion itself has zero runtime cost: ast and syntax nodes have exactly 23/// conversion itself has zero runtime cost: ast and syntax nodes have exactly
17/// the same representation: a pointer to the tree root and a pointer to the 24/// the same representation: a pointer to the tree root and a pointer to the
@@ -25,134 +32,32 @@ pub trait AstNode:
25 fn syntax(&self) -> &SyntaxNode; 32 fn syntax(&self) -> &SyntaxNode;
26} 33}
27 34
28pub trait TypeAscriptionOwner: AstNode {
29 fn ascribed_type(&self) -> Option<&TypeRef> {
30 child_opt(self)
31 }
32}
33
34pub trait NameOwner: AstNode {
35 fn name(&self) -> Option<&Name> {
36 child_opt(self)
37 }
38}
39
40pub trait VisibilityOwner: AstNode {
41 fn visibility(&self) -> Option<&Visibility> {
42 child_opt(self)
43 }
44}
45
46pub trait LoopBodyOwner: AstNode {
47 fn loop_body(&self) -> Option<&Block> {
48 child_opt(self)
49 }
50}
51
52pub trait ArgListOwner: AstNode {
53 fn arg_list(&self) -> Option<&ArgList> {
54 child_opt(self)
55 }
56}
57
58pub trait FnDefOwner: AstNode {
59 fn functions(&self) -> AstChildren<FnDef> {
60 children(self)
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum ItemOrMacro<'a> {
66 Item(&'a ModuleItem),
67 Macro(&'a MacroCall),
68}
69
70pub trait ModuleItemOwner: AstNode {
71 fn items(&self) -> AstChildren<ModuleItem> {
72 children(self)
73 }
74 fn items_with_macros(&self) -> ItemOrMacroIter {
75 ItemOrMacroIter(self.syntax().children())
76 }
77}
78
79#[derive(Debug)] 35#[derive(Debug)]
80pub struct ItemOrMacroIter<'a>(SyntaxNodeChildren<'a>); 36pub struct AstChildren<'a, N> {
81 37 inner: SyntaxNodeChildren<'a>,
82impl<'a> Iterator for ItemOrMacroIter<'a> { 38 ph: PhantomData<N>,
83 type Item = ItemOrMacro<'a>;
84 fn next(&mut self) -> Option<ItemOrMacro<'a>> {
85 loop {
86 let n = self.0.next()?;
87 if let Some(item) = ModuleItem::cast(n) {
88 return Some(ItemOrMacro::Item(item));
89 }
90 if let Some(call) = MacroCall::cast(n) {
91 return Some(ItemOrMacro::Macro(call));
92 }
93 }
94 }
95}
96
97pub trait TypeParamsOwner: AstNode {
98 fn type_param_list(&self) -> Option<&TypeParamList> {
99 child_opt(self)
100 }
101
102 fn where_clause(&self) -> Option<&WhereClause> {
103 child_opt(self)
104 }
105} 39}
106 40
107pub trait TypeBoundsOwner: AstNode { 41impl<'a, N> AstChildren<'a, N> {
108 fn type_bound_list(&self) -> Option<&TypeBoundList> { 42 fn new(parent: &'a SyntaxNode) -> Self {
109 child_opt(self) 43 AstChildren { inner: parent.children(), ph: PhantomData }
110 } 44 }
111} 45}
112 46
113pub trait AttrsOwner: AstNode { 47impl<'a, N: AstNode + 'a> Iterator for AstChildren<'a, N> {
114 fn attrs(&self) -> AstChildren<Attr> { 48 type Item = &'a N;
115 children(self) 49 fn next(&mut self) -> Option<&'a N> {
116 } 50 self.inner.by_ref().find_map(N::cast)
117 fn has_atom_attr(&self, atom: &str) -> bool {
118 self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom)
119 } 51 }
120} 52}
121 53
122pub trait DocCommentsOwner: AstNode { 54pub trait AstToken<'a> {
123 fn doc_comments(&self) -> CommentIter { 55 fn cast(token: SyntaxToken<'a>) -> Option<Self>
124 CommentIter { iter: self.syntax().children_with_tokens() } 56 where
125 } 57 Self: Sized;
126 58 fn syntax(&self) -> SyntaxToken<'a>;
127 /// Returns the textual content of a doc comment block as a single string. 59 fn text(&self) -> &'a SmolStr {
128 /// That is, strips leading `///` (+ optional 1 character of whitespace) 60 self.syntax().text()
129 /// and joins lines.
130 fn doc_comment_text(&self) -> Option<std::string::String> {
131 let docs = self
132 .doc_comments()
133 .filter(|comment| comment.is_doc_comment())
134 .map(|comment| {
135 let prefix_len = comment.prefix().len();
136
137 let line = comment.text().as_str();
138
139 // Determine if the prefix or prefix + 1 char is stripped
140 let pos =
141 if line.chars().nth(prefix_len).map(|c| c.is_whitespace()).unwrap_or(false) {
142 prefix_len + 1
143 } else {
144 prefix_len
145 };
146
147 line[pos..].to_owned()
148 })
149 .join("\n");
150
151 if docs.is_empty() {
152 None
153 } else {
154 Some(docs)
155 }
156 } 61 }
157} 62}
158 63
@@ -203,111 +108,6 @@ impl Attr {
203 } 108 }
204} 109}
205 110
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
207pub struct Comment<'a>(SyntaxToken<'a>);
208
209impl<'a> Comment<'a> {
210 pub fn cast(token: SyntaxToken<'a>) -> Option<Self> {
211 if token.kind() == COMMENT {
212 Some(Comment(token))
213 } else {
214 None
215 }
216 }
217
218 pub fn syntax(&self) -> SyntaxToken<'a> {
219 self.0
220 }
221
222 pub fn text(&self) -> &'a SmolStr {
223 self.0.text()
224 }
225
226 pub fn flavor(&self) -> CommentFlavor {
227 let text = self.text();
228 if text.starts_with("///") {
229 CommentFlavor::Doc
230 } else if text.starts_with("//!") {
231 CommentFlavor::ModuleDoc
232 } else if text.starts_with("//") {
233 CommentFlavor::Line
234 } else {
235 CommentFlavor::Multiline
236 }
237 }
238
239 pub fn is_doc_comment(&self) -> bool {
240 self.flavor().is_doc_comment()
241 }
242
243 pub fn prefix(&self) -> &'static str {
244 self.flavor().prefix()
245 }
246}
247
248pub struct CommentIter<'a> {
249 iter: SyntaxElementChildren<'a>,
250}
251
252impl<'a> Iterator for CommentIter<'a> {
253 type Item = Comment<'a>;
254 fn next(&mut self) -> Option<Comment<'a>> {
255 self.iter.by_ref().find_map(|el| el.as_token().and_then(Comment::cast))
256 }
257}
258
259#[derive(Debug, PartialEq, Eq)]
260pub enum CommentFlavor {
261 Line,
262 Doc,
263 ModuleDoc,
264 Multiline,
265}
266
267impl CommentFlavor {
268 pub fn prefix(&self) -> &'static str {
269 use self::CommentFlavor::*;
270 match *self {
271 Line => "//",
272 Doc => "///",
273 ModuleDoc => "//!",
274 Multiline => "/*",
275 }
276 }
277
278 pub fn is_doc_comment(&self) -> bool {
279 match self {
280 CommentFlavor::Doc | CommentFlavor::ModuleDoc => true,
281 _ => false,
282 }
283 }
284}
285
286pub struct Whitespace<'a>(SyntaxToken<'a>);
287
288impl<'a> Whitespace<'a> {
289 pub fn cast(token: SyntaxToken<'a>) -> Option<Self> {
290 if token.kind() == WHITESPACE {
291 Some(Whitespace(token))
292 } else {
293 None
294 }
295 }
296
297 pub fn syntax(&self) -> SyntaxToken<'a> {
298 self.0
299 }
300
301 pub fn text(&self) -> &'a SmolStr {
302 self.0.text()
303 }
304
305 pub fn spans_multiple_lines(&self) -> bool {
306 let text = self.text();
307 text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n'))
308 }
309}
310
311impl Name { 111impl Name {
312 pub fn text(&self) -> &SmolStr { 112 pub fn text(&self) -> &SmolStr {
313 let ident = self.syntax().first_child_or_token().unwrap().as_token().unwrap(); 113 let ident = self.syntax().first_child_or_token().unwrap().as_token().unwrap();
@@ -468,29 +268,6 @@ fn children<P: AstNode, C: AstNode>(parent: &P) -> AstChildren<C> {
468 AstChildren::new(parent.syntax()) 268 AstChildren::new(parent.syntax())
469} 269}
470 270
471#[derive(Debug)]
472pub struct AstChildren<'a, N> {
473 inner: SyntaxNodeChildren<'a>,
474 ph: PhantomData<N>,
475}
476
477impl<'a, N> AstChildren<'a, N> {
478 fn new(parent: &'a SyntaxNode) -> Self {
479 AstChildren { inner: parent.children(), ph: PhantomData }
480 }
481}
482
483impl<'a, N: AstNode + 'a> Iterator for AstChildren<'a, N> {
484 type Item = &'a N;
485 fn next(&mut self) -> Option<&'a N> {
486 loop {
487 if let Some(n) = N::cast(self.inner.next()?) {
488 return Some(n);
489 }
490 }
491 }
492}
493
494#[derive(Debug, Clone, PartialEq, Eq)] 271#[derive(Debug, Clone, PartialEq, Eq)]
495pub enum StructFlavor<'a> { 272pub enum StructFlavor<'a> {
496 Tuple(&'a PosFieldDefList), 273 Tuple(&'a PosFieldDefList),
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs
new file mode 100644
index 000000000..76a12cd64
--- /dev/null
+++ b/crates/ra_syntax/src/ast/tokens.rs
@@ -0,0 +1,92 @@
1use crate::{
2 SyntaxToken,
3 SyntaxKind::{COMMENT, WHITESPACE},
4 ast::AstToken,
5};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub struct Comment<'a>(SyntaxToken<'a>);
9
10impl<'a> AstToken<'a> for Comment<'a> {
11 fn cast(token: SyntaxToken<'a>) -> Option<Self> {
12 if token.kind() == COMMENT {
13 Some(Comment(token))
14 } else {
15 None
16 }
17 }
18 fn syntax(&self) -> SyntaxToken<'a> {
19 self.0
20 }
21}
22
23impl<'a> Comment<'a> {
24 pub fn flavor(&self) -> CommentFlavor {
25 let text = self.text();
26 if text.starts_with("///") {
27 CommentFlavor::OuterDoc
28 } else if text.starts_with("//!") {
29 CommentFlavor::InnerDoc
30 } else if text.starts_with("//") {
31 CommentFlavor::Line
32 } else {
33 CommentFlavor::Multiline
34 }
35 }
36
37 pub fn is_doc_comment(&self) -> bool {
38 self.flavor().is_doc_comment()
39 }
40
41 pub fn prefix(&self) -> &'static str {
42 self.flavor().prefix()
43 }
44}
45
46#[derive(Debug, PartialEq, Eq)]
47pub enum CommentFlavor {
48 Line,
49 OuterDoc,
50 InnerDoc,
51 Multiline,
52}
53
54impl CommentFlavor {
55 pub fn prefix(&self) -> &'static str {
56 match *self {
57 CommentFlavor::Line => "//",
58 CommentFlavor::OuterDoc => "///",
59 CommentFlavor::InnerDoc => "//!",
60 CommentFlavor::Multiline => "/*",
61 }
62 }
63
64 pub fn is_doc_comment(&self) -> bool {
65 match self {
66 CommentFlavor::OuterDoc | CommentFlavor::InnerDoc => true,
67 _ => false,
68 }
69 }
70}
71
72pub struct Whitespace<'a>(SyntaxToken<'a>);
73
74impl<'a> AstToken<'a> for Whitespace<'a> {
75 fn cast(token: SyntaxToken<'a>) -> Option<Self> {
76 if token.kind() == WHITESPACE {
77 Some(Whitespace(token))
78 } else {
79 None
80 }
81 }
82 fn syntax(&self) -> SyntaxToken<'a> {
83 self.0
84 }
85}
86
87impl<'a> Whitespace<'a> {
88 pub fn spans_multiple_lines(&self) -> bool {
89 let text = self.text();
90 text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n'))
91 }
92}
diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs
new file mode 100644
index 000000000..43d1509fa
--- /dev/null
+++ b/crates/ra_syntax/src/ast/traits.rs
@@ -0,0 +1,150 @@
1use itertools::Itertools;
2
3use crate::{
4 syntax_node::{SyntaxNodeChildren, SyntaxElementChildren},
5 ast::{self, child_opt, children, AstNode, AstToken, AstChildren},
6};
7
8pub trait TypeAscriptionOwner: AstNode {
9 fn ascribed_type(&self) -> Option<&ast::TypeRef> {
10 child_opt(self)
11 }
12}
13
14pub trait NameOwner: AstNode {
15 fn name(&self) -> Option<&ast::Name> {
16 child_opt(self)
17 }
18}
19
20pub trait VisibilityOwner: AstNode {
21 fn visibility(&self) -> Option<&ast::Visibility> {
22 child_opt(self)
23 }
24}
25
26pub trait LoopBodyOwner: AstNode {
27 fn loop_body(&self) -> Option<&ast::Block> {
28 child_opt(self)
29 }
30}
31
32pub trait ArgListOwner: AstNode {
33 fn arg_list(&self) -> Option<&ast::ArgList> {
34 child_opt(self)
35 }
36}
37
38pub trait FnDefOwner: AstNode {
39 fn functions(&self) -> AstChildren<ast::FnDef> {
40 children(self)
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ItemOrMacro<'a> {
46 Item(&'a ast::ModuleItem),
47 Macro(&'a ast::MacroCall),
48}
49
50pub trait ModuleItemOwner: AstNode {
51 fn items(&self) -> AstChildren<ast::ModuleItem> {
52 children(self)
53 }
54 fn items_with_macros(&self) -> ItemOrMacroIter {
55 ItemOrMacroIter(self.syntax().children())
56 }
57}
58
59#[derive(Debug)]
60pub struct ItemOrMacroIter<'a>(SyntaxNodeChildren<'a>);
61
62impl<'a> Iterator for ItemOrMacroIter<'a> {
63 type Item = ItemOrMacro<'a>;
64 fn next(&mut self) -> Option<ItemOrMacro<'a>> {
65 loop {
66 let n = self.0.next()?;
67 if let Some(item) = ast::ModuleItem::cast(n) {
68 return Some(ItemOrMacro::Item(item));
69 }
70 if let Some(call) = ast::MacroCall::cast(n) {
71 return Some(ItemOrMacro::Macro(call));
72 }
73 }
74 }
75}
76
77pub trait TypeParamsOwner: AstNode {
78 fn type_param_list(&self) -> Option<&ast::TypeParamList> {
79 child_opt(self)
80 }
81
82 fn where_clause(&self) -> Option<&ast::WhereClause> {
83 child_opt(self)
84 }
85}
86
87pub trait TypeBoundsOwner: AstNode {
88 fn type_bound_list(&self) -> Option<&ast::TypeBoundList> {
89 child_opt(self)
90 }
91}
92
93pub trait AttrsOwner: AstNode {
94 fn attrs(&self) -> AstChildren<ast::Attr> {
95 children(self)
96 }
97 fn has_atom_attr(&self, atom: &str) -> bool {
98 self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom)
99 }
100}
101
102pub trait DocCommentsOwner: AstNode {
103 fn doc_comments(&self) -> CommentIter {
104 CommentIter { iter: self.syntax().children_with_tokens() }
105 }
106
107 /// Returns the textual content of a doc comment block as a single string.
108 /// That is, strips leading `///` (+ optional 1 character of whitespace)
109 /// and joins lines.
110 fn doc_comment_text(&self) -> Option<String> {
111 let mut has_comments = false;
112 let docs = self
113 .doc_comments()
114 .filter(|comment| comment.is_doc_comment())
115 .map(|comment| {
116 has_comments = true;
117 let prefix_len = comment.prefix().len();
118
119 let line = comment.text().as_str();
120
121 // Determine if the prefix or prefix + 1 char is stripped
122 let pos =
123 if line.chars().nth(prefix_len).map(|c| c.is_whitespace()).unwrap_or(false) {
124 prefix_len + 1
125 } else {
126 prefix_len
127 };
128
129 line[pos..].to_owned()
130 })
131 .join("\n");
132
133 if has_comments {
134 Some(docs)
135 } else {
136 None
137 }
138 }
139}
140
141pub struct CommentIter<'a> {
142 iter: SyntaxElementChildren<'a>,
143}
144
145impl<'a> Iterator for CommentIter<'a> {
146 type Item = ast::Comment<'a>;
147 fn next(&mut self) -> Option<ast::Comment<'a>> {
148 self.iter.by_ref().find_map(|el| el.as_token().and_then(ast::Comment::cast))
149 }
150}