aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax/src/ast
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_syntax/src/ast')
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs92
-rw-r--r--crates/ra_syntax/src/ast/traits.rs150
2 files changed, 242 insertions, 0 deletions
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}