diff options
-rw-r--r-- | crates/ra_assists/src/add_missing_impl_members.rs | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/inline_local_variable.rs | 9 | ||||
-rw-r--r-- | crates/ra_fmt/src/lib.rs | 3 | ||||
-rw-r--r-- | crates/ra_ide_api/src/completion/snapshots/completion_item__enum_variant_with_details.snap | 9 | ||||
-rw-r--r-- | crates/ra_ide_api/src/extend_selection.rs | 12 | ||||
-rw-r--r-- | crates/ra_ide_api/src/folding_ranges.rs | 10 | ||||
-rw-r--r-- | crates/ra_ide_api/src/join_lines.rs | 4 | ||||
-rw-r--r-- | crates/ra_ide_api/src/typing.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast.rs | 275 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/tokens.rs | 92 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/traits.rs | 150 |
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}; | |||
5 | use hir::Resolver; | 5 | use hir::Resolver; |
6 | use hir::db::HirDatabase; | 6 | use hir::db::HirDatabase; |
7 | use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc}; | 7 | use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc}; |
8 | use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner}; | 8 | use ra_syntax::ast::{self, AstNode, AstToken, FnDef, ImplItem, ImplItemKind, NameOwner}; |
9 | use ra_db::FilePosition; | 9 | use ra_db::FilePosition; |
10 | use ra_fmt::{leading_indent, reindent}; | 10 | use 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 @@ | |||
1 | use hir::{ | 1 | use hir::{ |
2 | db::HirDatabase, | 2 | db::HirDatabase, |
3 | source_binder::function_from_child_node | 3 | source_binder::function_from_child_node, |
4 | }; | 4 | }; |
5 | use ra_syntax::{ | 5 | use 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 | //! |
3 | use itertools::Itertools; | 3 | use itertools::Itertools; |
4 | use ra_syntax::{ | 4 | use 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 | --- |
2 | created: "2019-02-18T09:22:24.062138085Z" | 2 | created: "2019-04-02T07:43:12.954637543Z" |
3 | creator: insta@0.6.2 | 3 | creator: insta@0.7.4 |
4 | source: crates/ra_ide_api/src/completion/completion_item.rs | 4 | source: crates/ra_ide_api/src/completion/completion_item.rs |
5 | expression: kind_completions | 5 | expression: 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 @@ | |||
1 | use ra_db::SourceDatabase; | 1 | use ra_db::SourceDatabase; |
2 | use ra_syntax::{ | 2 | use 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 | ||
9 | use crate::{FileRange, db::RootDatabase}; | 9 | use 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 | ||
179 | fn extend_comments(comment: Comment) -> Option<TextRange> { | 179 | fn 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 | ||
189 | fn adj_comments(comment: Comment, dir: Direction) -> Comment { | 189 | fn 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 @@ | |||
1 | use rustc_hash::FxHashSet; | 1 | use rustc_hash::FxHashSet; |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use 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 | ||
141 | fn contiguous_range_for_comment<'a>( | 141 | fn 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 @@ | |||
1 | use itertools::Itertools; | 1 | use itertools::Itertools; |
2 | use ra_syntax::{ | 2 | use 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 | }; |
9 | use ra_fmt::{ | 9 | use 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 | }; |
7 | use ra_fmt::leading_indent; | 7 | use ra_fmt::leading_indent; |
8 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 8 | use 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 |
2 | mod generated; | 2 | mod generated; |
3 | mod traits; | ||
4 | mod tokens; | ||
3 | 5 | ||
4 | use std::marker::PhantomData; | 6 | use std::marker::PhantomData; |
5 | 7 | ||
6 | use itertools::Itertools; | 8 | use itertools::Itertools; |
7 | 9 | ||
8 | pub use self::generated::*; | ||
9 | use crate::{ | 10 | use 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 | ||
16 | pub 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 | ||
28 | pub trait TypeAscriptionOwner: AstNode { | ||
29 | fn ascribed_type(&self) -> Option<&TypeRef> { | ||
30 | child_opt(self) | ||
31 | } | ||
32 | } | ||
33 | |||
34 | pub trait NameOwner: AstNode { | ||
35 | fn name(&self) -> Option<&Name> { | ||
36 | child_opt(self) | ||
37 | } | ||
38 | } | ||
39 | |||
40 | pub trait VisibilityOwner: AstNode { | ||
41 | fn visibility(&self) -> Option<&Visibility> { | ||
42 | child_opt(self) | ||
43 | } | ||
44 | } | ||
45 | |||
46 | pub trait LoopBodyOwner: AstNode { | ||
47 | fn loop_body(&self) -> Option<&Block> { | ||
48 | child_opt(self) | ||
49 | } | ||
50 | } | ||
51 | |||
52 | pub trait ArgListOwner: AstNode { | ||
53 | fn arg_list(&self) -> Option<&ArgList> { | ||
54 | child_opt(self) | ||
55 | } | ||
56 | } | ||
57 | |||
58 | pub trait FnDefOwner: AstNode { | ||
59 | fn functions(&self) -> AstChildren<FnDef> { | ||
60 | children(self) | ||
61 | } | ||
62 | } | ||
63 | |||
64 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
65 | pub enum ItemOrMacro<'a> { | ||
66 | Item(&'a ModuleItem), | ||
67 | Macro(&'a MacroCall), | ||
68 | } | ||
69 | |||
70 | pub 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)] |
80 | pub struct ItemOrMacroIter<'a>(SyntaxNodeChildren<'a>); | 36 | pub struct AstChildren<'a, N> { |
81 | 37 | inner: SyntaxNodeChildren<'a>, | |
82 | impl<'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 | |||
97 | pub 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 | ||
107 | pub trait TypeBoundsOwner: AstNode { | 41 | impl<'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 | ||
113 | pub trait AttrsOwner: AstNode { | 47 | impl<'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 | ||
122 | pub trait DocCommentsOwner: AstNode { | 54 | pub 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)] | ||
207 | pub struct Comment<'a>(SyntaxToken<'a>); | ||
208 | |||
209 | impl<'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 | |||
248 | pub struct CommentIter<'a> { | ||
249 | iter: SyntaxElementChildren<'a>, | ||
250 | } | ||
251 | |||
252 | impl<'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)] | ||
260 | pub enum CommentFlavor { | ||
261 | Line, | ||
262 | Doc, | ||
263 | ModuleDoc, | ||
264 | Multiline, | ||
265 | } | ||
266 | |||
267 | impl 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 | |||
286 | pub struct Whitespace<'a>(SyntaxToken<'a>); | ||
287 | |||
288 | impl<'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 | |||
311 | impl Name { | 111 | impl 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)] | ||
472 | pub struct AstChildren<'a, N> { | ||
473 | inner: SyntaxNodeChildren<'a>, | ||
474 | ph: PhantomData<N>, | ||
475 | } | ||
476 | |||
477 | impl<'a, N> AstChildren<'a, N> { | ||
478 | fn new(parent: &'a SyntaxNode) -> Self { | ||
479 | AstChildren { inner: parent.children(), ph: PhantomData } | ||
480 | } | ||
481 | } | ||
482 | |||
483 | impl<'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)] |
495 | pub enum StructFlavor<'a> { | 272 | pub 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 @@ | |||
1 | use crate::{ | ||
2 | SyntaxToken, | ||
3 | SyntaxKind::{COMMENT, WHITESPACE}, | ||
4 | ast::AstToken, | ||
5 | }; | ||
6 | |||
7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
8 | pub struct Comment<'a>(SyntaxToken<'a>); | ||
9 | |||
10 | impl<'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 | |||
23 | impl<'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)] | ||
47 | pub enum CommentFlavor { | ||
48 | Line, | ||
49 | OuterDoc, | ||
50 | InnerDoc, | ||
51 | Multiline, | ||
52 | } | ||
53 | |||
54 | impl 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 | |||
72 | pub struct Whitespace<'a>(SyntaxToken<'a>); | ||
73 | |||
74 | impl<'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 | |||
87 | impl<'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 @@ | |||
1 | use itertools::Itertools; | ||
2 | |||
3 | use crate::{ | ||
4 | syntax_node::{SyntaxNodeChildren, SyntaxElementChildren}, | ||
5 | ast::{self, child_opt, children, AstNode, AstToken, AstChildren}, | ||
6 | }; | ||
7 | |||
8 | pub trait TypeAscriptionOwner: AstNode { | ||
9 | fn ascribed_type(&self) -> Option<&ast::TypeRef> { | ||
10 | child_opt(self) | ||
11 | } | ||
12 | } | ||
13 | |||
14 | pub trait NameOwner: AstNode { | ||
15 | fn name(&self) -> Option<&ast::Name> { | ||
16 | child_opt(self) | ||
17 | } | ||
18 | } | ||
19 | |||
20 | pub trait VisibilityOwner: AstNode { | ||
21 | fn visibility(&self) -> Option<&ast::Visibility> { | ||
22 | child_opt(self) | ||
23 | } | ||
24 | } | ||
25 | |||
26 | pub trait LoopBodyOwner: AstNode { | ||
27 | fn loop_body(&self) -> Option<&ast::Block> { | ||
28 | child_opt(self) | ||
29 | } | ||
30 | } | ||
31 | |||
32 | pub trait ArgListOwner: AstNode { | ||
33 | fn arg_list(&self) -> Option<&ast::ArgList> { | ||
34 | child_opt(self) | ||
35 | } | ||
36 | } | ||
37 | |||
38 | pub trait FnDefOwner: AstNode { | ||
39 | fn functions(&self) -> AstChildren<ast::FnDef> { | ||
40 | children(self) | ||
41 | } | ||
42 | } | ||
43 | |||
44 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
45 | pub enum ItemOrMacro<'a> { | ||
46 | Item(&'a ast::ModuleItem), | ||
47 | Macro(&'a ast::MacroCall), | ||
48 | } | ||
49 | |||
50 | pub 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)] | ||
60 | pub struct ItemOrMacroIter<'a>(SyntaxNodeChildren<'a>); | ||
61 | |||
62 | impl<'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 | |||
77 | pub 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 | |||
87 | pub trait TypeBoundsOwner: AstNode { | ||
88 | fn type_bound_list(&self) -> Option<&ast::TypeBoundList> { | ||
89 | child_opt(self) | ||
90 | } | ||
91 | } | ||
92 | |||
93 | pub 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 | |||
102 | pub 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 | |||
141 | pub struct CommentIter<'a> { | ||
142 | iter: SyntaxElementChildren<'a>, | ||
143 | } | ||
144 | |||
145 | impl<'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 | } | ||