]> git.lizzy.rs Git - rust.git/blob - crates/ra_syntax/src/ast/traits.rs
Fix API of Attr
[rust.git] / crates / ra_syntax / src / ast / traits.rs
1 //! Various traits that are implemented by ast nodes.
2 //!
3 //! The implementations are usually trivial, and live in generated.rs
4
5 use itertools::Itertools;
6
7 use crate::{
8     ast::{self, child_opt, children, AstChildren, AstNode, AstToken},
9     syntax_node::{SyntaxElementChildren, SyntaxNodeChildren},
10 };
11
12 pub trait TypeAscriptionOwner: AstNode {
13     fn ascribed_type(&self) -> Option<ast::TypeRef> {
14         child_opt(self)
15     }
16 }
17
18 pub trait NameOwner: AstNode {
19     fn name(&self) -> Option<ast::Name> {
20         child_opt(self)
21     }
22 }
23
24 pub trait VisibilityOwner: AstNode {
25     fn visibility(&self) -> Option<ast::Visibility> {
26         child_opt(self)
27     }
28 }
29
30 pub trait LoopBodyOwner: AstNode {
31     fn loop_body(&self) -> Option<ast::BlockExpr> {
32         child_opt(self)
33     }
34 }
35
36 pub trait ArgListOwner: AstNode {
37     fn arg_list(&self) -> Option<ast::ArgList> {
38         child_opt(self)
39     }
40 }
41
42 pub trait FnDefOwner: AstNode {
43     fn functions(&self) -> AstChildren<ast::FnDef> {
44         children(self)
45     }
46 }
47
48 #[derive(Debug, Clone, PartialEq, Eq)]
49 pub enum ItemOrMacro {
50     Item(ast::ModuleItem),
51     Macro(ast::MacroCall),
52 }
53
54 pub trait ModuleItemOwner: AstNode {
55     fn items(&self) -> AstChildren<ast::ModuleItem> {
56         children(self)
57     }
58     fn items_with_macros(&self) -> ItemOrMacroIter {
59         ItemOrMacroIter(self.syntax().children())
60     }
61 }
62
63 #[derive(Debug)]
64 pub struct ItemOrMacroIter(SyntaxNodeChildren);
65
66 impl Iterator for ItemOrMacroIter {
67     type Item = ItemOrMacro;
68     fn next(&mut self) -> Option<ItemOrMacro> {
69         loop {
70             let n = self.0.next()?;
71             if let Some(item) = ast::ModuleItem::cast(n.clone()) {
72                 return Some(ItemOrMacro::Item(item));
73             }
74             if let Some(call) = ast::MacroCall::cast(n) {
75                 return Some(ItemOrMacro::Macro(call));
76             }
77         }
78     }
79 }
80
81 pub trait TypeParamsOwner: AstNode {
82     fn type_param_list(&self) -> Option<ast::TypeParamList> {
83         child_opt(self)
84     }
85
86     fn where_clause(&self) -> Option<ast::WhereClause> {
87         child_opt(self)
88     }
89 }
90
91 pub trait TypeBoundsOwner: AstNode {
92     fn type_bound_list(&self) -> Option<ast::TypeBoundList> {
93         child_opt(self)
94     }
95 }
96
97 pub trait AttrsOwner: AstNode {
98     fn attrs(&self) -> AstChildren<ast::Attr> {
99         children(self)
100     }
101     fn has_atom_attr(&self, atom: &str) -> bool {
102         self.attrs().filter_map(|x| x.as_simple_atom()).any(|x| x == atom)
103     }
104 }
105
106 pub trait DocCommentsOwner: AstNode {
107     fn doc_comments(&self) -> CommentIter {
108         CommentIter { iter: self.syntax().children_with_tokens() }
109     }
110
111     /// Returns the textual content of a doc comment block as a single string.
112     /// That is, strips leading `///` (+ optional 1 character of whitespace),
113     /// trailing `*/`, trailing whitespace and then joins the lines.
114     fn doc_comment_text(&self) -> Option<String> {
115         let mut has_comments = false;
116         let docs = self
117             .doc_comments()
118             .filter(|comment| comment.kind().doc.is_some())
119             .map(|comment| {
120                 has_comments = true;
121                 let prefix_len = comment.prefix().len();
122
123                 let line = comment.text().as_str();
124
125                 // Determine if the prefix or prefix + 1 char is stripped
126                 let pos =
127                     if line.chars().nth(prefix_len).map(|c| c.is_whitespace()).unwrap_or(false) {
128                         prefix_len + 1
129                     } else {
130                         prefix_len
131                     };
132
133                 let end = if comment.kind().shape.is_block() && line.ends_with("*/") {
134                     line.len() - 2
135                 } else {
136                     line.len()
137                 };
138
139                 line[pos..end].trim_end().to_owned()
140             })
141             .join("\n");
142
143         if has_comments {
144             Some(docs)
145         } else {
146             None
147         }
148     }
149 }
150
151 pub struct CommentIter {
152     iter: SyntaxElementChildren,
153 }
154
155 impl Iterator for CommentIter {
156     type Item = ast::Comment;
157     fn next(&mut self) -> Option<ast::Comment> {
158         self.iter.by_ref().find_map(|el| el.into_token().and_then(ast::Comment::cast))
159     }
160 }
161
162 pub trait DefaultTypeParamOwner: AstNode {
163     fn default_type(&self) -> Option<ast::PathType> {
164         child_opt(self)
165     }
166 }