diff options
Diffstat (limited to 'crates/ra_syntax/src')
-rw-r--r-- | crates/ra_syntax/src/algo.rs | 19 | ||||
-rw-r--r-- | crates/ra_syntax/src/algo/visit.rs | 110 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast.rs | 1 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 271 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/extensions.rs | 93 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/generated.rs | 46 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 48 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/traits.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/fuzz.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/grammar.ron | 8 | ||||
-rw-r--r-- | crates/ra_syntax/src/lib.rs | 31 | ||||
-rw-r--r-- | crates/ra_syntax/src/parsing/lexer.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/parsing/text_token_source.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/parsing/text_tree_sink.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ptr.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/syntax_error.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/validation.rs | 20 | ||||
-rw-r--r-- | crates/ra_syntax/src/validation/block.rs | 2 |
18 files changed, 476 insertions, 187 deletions
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 46680a08f..7cfea70f9 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs | |||
@@ -1,8 +1,9 @@ | |||
1 | pub mod visit; | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use std::ops::RangeInclusive; | 3 | use std::ops::RangeInclusive; |
4 | 4 | ||
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use ra_text_edit::TextEditBuilder; | ||
6 | use rustc_hash::FxHashMap; | 7 | use rustc_hash::FxHashMap; |
7 | 8 | ||
8 | use crate::{ | 9 | use crate::{ |
@@ -63,6 +64,18 @@ pub enum InsertPosition<T> { | |||
63 | After(T), | 64 | After(T), |
64 | } | 65 | } |
65 | 66 | ||
67 | pub struct TreeDiff { | ||
68 | replacements: FxHashMap<SyntaxElement, SyntaxElement>, | ||
69 | } | ||
70 | |||
71 | impl TreeDiff { | ||
72 | pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { | ||
73 | for (from, to) in self.replacements.iter() { | ||
74 | builder.replace(from.text_range(), to.to_string()) | ||
75 | } | ||
76 | } | ||
77 | } | ||
78 | |||
66 | /// Finds minimal the diff, which, applied to `from`, will result in `to`. | 79 | /// Finds minimal the diff, which, applied to `from`, will result in `to`. |
67 | /// | 80 | /// |
68 | /// Specifically, returns a map whose keys are descendants of `from` and values | 81 | /// Specifically, returns a map whose keys are descendants of `from` and values |
@@ -70,12 +83,12 @@ pub enum InsertPosition<T> { | |||
70 | /// | 83 | /// |
71 | /// A trivial solution is a singletom map `{ from: to }`, but this function | 84 | /// A trivial solution is a singletom map `{ from: to }`, but this function |
72 | /// tries to find a more fine-grained diff. | 85 | /// tries to find a more fine-grained diff. |
73 | pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> FxHashMap<SyntaxElement, SyntaxElement> { | 86 | pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { |
74 | let mut buf = FxHashMap::default(); | 87 | let mut buf = FxHashMap::default(); |
75 | // FIXME: this is both horrible inefficient and gives larger than | 88 | // FIXME: this is both horrible inefficient and gives larger than |
76 | // necessary diff. I bet there's a cool algorithm to diff trees properly. | 89 | // necessary diff. I bet there's a cool algorithm to diff trees properly. |
77 | go(&mut buf, from.clone().into(), to.clone().into()); | 90 | go(&mut buf, from.clone().into(), to.clone().into()); |
78 | return buf; | 91 | return TreeDiff { replacements: buf }; |
79 | 92 | ||
80 | fn go( | 93 | fn go( |
81 | buf: &mut FxHashMap<SyntaxElement, SyntaxElement>, | 94 | buf: &mut FxHashMap<SyntaxElement, SyntaxElement>, |
diff --git a/crates/ra_syntax/src/algo/visit.rs b/crates/ra_syntax/src/algo/visit.rs deleted file mode 100644 index 87bd15cc0..000000000 --- a/crates/ra_syntax/src/algo/visit.rs +++ /dev/null | |||
@@ -1,110 +0,0 @@ | |||
1 | use crate::{AstNode, SyntaxNode}; | ||
2 | |||
3 | use std::marker::PhantomData; | ||
4 | |||
5 | pub fn visitor<'a, T>() -> impl Visitor<'a, Output = T> { | ||
6 | EmptyVisitor { ph: PhantomData } | ||
7 | } | ||
8 | |||
9 | pub fn visitor_ctx<'a, T, C>(ctx: C) -> impl VisitorCtx<'a, Output = T, Ctx = C> { | ||
10 | EmptyVisitorCtx { ph: PhantomData, ctx } | ||
11 | } | ||
12 | |||
13 | pub trait Visitor<'a>: Sized { | ||
14 | type Output; | ||
15 | fn accept(self, node: &'a SyntaxNode) -> Option<Self::Output>; | ||
16 | fn visit<N, F>(self, f: F) -> Vis<Self, N, F> | ||
17 | where | ||
18 | N: AstNode + 'a, | ||
19 | F: FnOnce(N) -> Self::Output, | ||
20 | { | ||
21 | Vis { inner: self, f, ph: PhantomData } | ||
22 | } | ||
23 | } | ||
24 | |||
25 | pub trait VisitorCtx<'a>: Sized { | ||
26 | type Output; | ||
27 | type Ctx; | ||
28 | fn accept(self, node: &'a SyntaxNode) -> Result<Self::Output, Self::Ctx>; | ||
29 | fn visit<N, F>(self, f: F) -> VisCtx<Self, N, F> | ||
30 | where | ||
31 | N: AstNode + 'a, | ||
32 | F: FnOnce(N, Self::Ctx) -> Self::Output, | ||
33 | { | ||
34 | VisCtx { inner: self, f, ph: PhantomData } | ||
35 | } | ||
36 | } | ||
37 | |||
38 | #[derive(Debug)] | ||
39 | struct EmptyVisitor<T> { | ||
40 | ph: PhantomData<fn() -> T>, | ||
41 | } | ||
42 | |||
43 | impl<'a, T> Visitor<'a> for EmptyVisitor<T> { | ||
44 | type Output = T; | ||
45 | |||
46 | fn accept(self, _node: &'a SyntaxNode) -> Option<T> { | ||
47 | None | ||
48 | } | ||
49 | } | ||
50 | |||
51 | #[derive(Debug)] | ||
52 | struct EmptyVisitorCtx<T, C> { | ||
53 | ctx: C, | ||
54 | ph: PhantomData<fn() -> T>, | ||
55 | } | ||
56 | |||
57 | impl<'a, T, C> VisitorCtx<'a> for EmptyVisitorCtx<T, C> { | ||
58 | type Output = T; | ||
59 | type Ctx = C; | ||
60 | |||
61 | fn accept(self, _node: &'a SyntaxNode) -> Result<T, C> { | ||
62 | Err(self.ctx) | ||
63 | } | ||
64 | } | ||
65 | |||
66 | #[derive(Debug)] | ||
67 | pub struct Vis<V, N, F> { | ||
68 | inner: V, | ||
69 | f: F, | ||
70 | ph: PhantomData<fn(N)>, | ||
71 | } | ||
72 | |||
73 | impl<'a, V, N, F> Visitor<'a> for Vis<V, N, F> | ||
74 | where | ||
75 | V: Visitor<'a>, | ||
76 | N: AstNode + 'a, | ||
77 | F: FnOnce(N) -> <V as Visitor<'a>>::Output, | ||
78 | { | ||
79 | type Output = <V as Visitor<'a>>::Output; | ||
80 | |||
81 | fn accept(self, node: &'a SyntaxNode) -> Option<Self::Output> { | ||
82 | let Vis { inner, f, .. } = self; | ||
83 | inner.accept(node).or_else(|| N::cast(node.clone()).map(f)) | ||
84 | } | ||
85 | } | ||
86 | |||
87 | #[derive(Debug)] | ||
88 | pub struct VisCtx<V, N, F> { | ||
89 | inner: V, | ||
90 | f: F, | ||
91 | ph: PhantomData<fn(N)>, | ||
92 | } | ||
93 | |||
94 | impl<'a, V, N, F> VisitorCtx<'a> for VisCtx<V, N, F> | ||
95 | where | ||
96 | V: VisitorCtx<'a>, | ||
97 | N: AstNode + 'a, | ||
98 | F: FnOnce(N, <V as VisitorCtx<'a>>::Ctx) -> <V as VisitorCtx<'a>>::Output, | ||
99 | { | ||
100 | type Output = <V as VisitorCtx<'a>>::Output; | ||
101 | type Ctx = <V as VisitorCtx<'a>>::Ctx; | ||
102 | |||
103 | fn accept(self, node: &'a SyntaxNode) -> Result<Self::Output, Self::Ctx> { | ||
104 | let VisCtx { inner, f, .. } = self; | ||
105 | inner.accept(node).or_else(|ctx| match N::cast(node.clone()) { | ||
106 | None => Err(ctx), | ||
107 | Some(node) => Ok(f(node, ctx)), | ||
108 | }) | ||
109 | } | ||
110 | } | ||
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index f464d6534..1b2ce921a 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs | |||
@@ -5,6 +5,7 @@ mod traits; | |||
5 | mod tokens; | 5 | mod tokens; |
6 | mod extensions; | 6 | mod extensions; |
7 | mod expr_extensions; | 7 | mod expr_extensions; |
8 | pub mod edit; | ||
8 | pub mod make; | 9 | pub mod make; |
9 | 10 | ||
10 | use std::marker::PhantomData; | 11 | use std::marker::PhantomData; |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs new file mode 100644 index 000000000..03f3b5fbb --- /dev/null +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -0,0 +1,271 @@ | |||
1 | //! This module contains functions for editing syntax trees. As the trees are | ||
2 | //! immutable, all function here return a fresh copy of the tree, instead of | ||
3 | //! doing an in-place modification. | ||
4 | use std::{iter, ops::RangeInclusive}; | ||
5 | |||
6 | use arrayvec::ArrayVec; | ||
7 | use rustc_hash::FxHashMap; | ||
8 | |||
9 | use crate::{ | ||
10 | algo, | ||
11 | ast::{ | ||
12 | self, | ||
13 | make::{self, tokens}, | ||
14 | AstNode, TypeBoundsOwner, | ||
15 | }, | ||
16 | AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, | ||
17 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, | ||
18 | SyntaxNode, T, | ||
19 | }; | ||
20 | |||
21 | impl ast::FnDef { | ||
22 | #[must_use] | ||
23 | pub fn with_body(&self, body: ast::Block) -> ast::FnDef { | ||
24 | let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new(); | ||
25 | let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.body() { | ||
26 | old_body.syntax().clone().into() | ||
27 | } else if let Some(semi) = self.semicolon_token() { | ||
28 | to_insert.push(make::tokens::single_space().into()); | ||
29 | semi.into() | ||
30 | } else { | ||
31 | to_insert.push(make::tokens::single_space().into()); | ||
32 | to_insert.push(body.syntax().clone().into()); | ||
33 | return insert_children(self, InsertPosition::Last, to_insert.into_iter()); | ||
34 | }; | ||
35 | to_insert.push(body.syntax().clone().into()); | ||
36 | let replace_range = RangeInclusive::new(old_body_or_semi.clone(), old_body_or_semi); | ||
37 | replace_children(self, replace_range, to_insert.into_iter()) | ||
38 | } | ||
39 | } | ||
40 | |||
41 | impl ast::ItemList { | ||
42 | #[must_use] | ||
43 | pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList { | ||
44 | let mut res = self.clone(); | ||
45 | if !self.syntax().text().contains_char('\n') { | ||
46 | res = res.make_multiline(); | ||
47 | } | ||
48 | items.for_each(|it| res = res.append_item(it)); | ||
49 | res | ||
50 | } | ||
51 | |||
52 | #[must_use] | ||
53 | pub fn append_item(&self, item: ast::ImplItem) -> ast::ItemList { | ||
54 | let (indent, position) = match self.impl_items().last() { | ||
55 | Some(it) => ( | ||
56 | leading_indent(it.syntax()).unwrap_or_default().to_string(), | ||
57 | InsertPosition::After(it.syntax().clone().into()), | ||
58 | ), | ||
59 | None => match self.l_curly() { | ||
60 | Some(it) => ( | ||
61 | " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(), | ||
62 | InsertPosition::After(it), | ||
63 | ), | ||
64 | None => return self.clone(), | ||
65 | }, | ||
66 | }; | ||
67 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
68 | let to_insert: ArrayVec<[SyntaxElement; 2]> = | ||
69 | [ws.ws().into(), item.syntax().clone().into()].into(); | ||
70 | insert_children(self, position, to_insert.into_iter()) | ||
71 | } | ||
72 | |||
73 | fn l_curly(&self) -> Option<SyntaxElement> { | ||
74 | self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) | ||
75 | } | ||
76 | |||
77 | fn make_multiline(&self) -> ast::ItemList { | ||
78 | let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { | ||
79 | Some(it) => it, | ||
80 | None => return self.clone(), | ||
81 | }; | ||
82 | let sibling = match l_curly.next_sibling_or_token() { | ||
83 | Some(it) => it, | ||
84 | None => return self.clone(), | ||
85 | }; | ||
86 | let existing_ws = match sibling.as_token() { | ||
87 | None => None, | ||
88 | Some(tok) if tok.kind() != WHITESPACE => None, | ||
89 | Some(ws) => { | ||
90 | if ws.text().contains('\n') { | ||
91 | return self.clone(); | ||
92 | } | ||
93 | Some(ws.clone()) | ||
94 | } | ||
95 | }; | ||
96 | |||
97 | let indent = leading_indent(self.syntax()).unwrap_or("".into()); | ||
98 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
99 | let to_insert = iter::once(ws.ws().into()); | ||
100 | match existing_ws { | ||
101 | None => insert_children(self, InsertPosition::After(l_curly), to_insert), | ||
102 | Some(ws) => { | ||
103 | replace_children(self, RangeInclusive::new(ws.clone().into(), ws.into()), to_insert) | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | |||
109 | impl ast::RecordFieldList { | ||
110 | #[must_use] | ||
111 | pub fn append_field(&self, field: &ast::RecordField) -> ast::RecordFieldList { | ||
112 | self.insert_field(InsertPosition::Last, field) | ||
113 | } | ||
114 | |||
115 | #[must_use] | ||
116 | pub fn insert_field( | ||
117 | &self, | ||
118 | position: InsertPosition<&'_ ast::RecordField>, | ||
119 | field: &ast::RecordField, | ||
120 | ) -> ast::RecordFieldList { | ||
121 | let is_multiline = self.syntax().text().contains_char('\n'); | ||
122 | let ws; | ||
123 | let space = if is_multiline { | ||
124 | ws = tokens::WsBuilder::new(&format!( | ||
125 | "\n{} ", | ||
126 | leading_indent(self.syntax()).unwrap_or("".into()) | ||
127 | )); | ||
128 | ws.ws() | ||
129 | } else { | ||
130 | tokens::single_space() | ||
131 | }; | ||
132 | |||
133 | let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new(); | ||
134 | to_insert.push(space.into()); | ||
135 | to_insert.push(field.syntax().clone().into()); | ||
136 | to_insert.push(tokens::comma().into()); | ||
137 | |||
138 | macro_rules! after_l_curly { | ||
139 | () => {{ | ||
140 | let anchor = match self.l_curly() { | ||
141 | Some(it) => it, | ||
142 | None => return self.clone(), | ||
143 | }; | ||
144 | InsertPosition::After(anchor) | ||
145 | }}; | ||
146 | } | ||
147 | |||
148 | macro_rules! after_field { | ||
149 | ($anchor:expr) => { | ||
150 | if let Some(comma) = $anchor | ||
151 | .syntax() | ||
152 | .siblings_with_tokens(Direction::Next) | ||
153 | .find(|it| it.kind() == T![,]) | ||
154 | { | ||
155 | InsertPosition::After(comma) | ||
156 | } else { | ||
157 | to_insert.insert(0, tokens::comma().into()); | ||
158 | InsertPosition::After($anchor.syntax().clone().into()) | ||
159 | } | ||
160 | }; | ||
161 | }; | ||
162 | |||
163 | let position = match position { | ||
164 | InsertPosition::First => after_l_curly!(), | ||
165 | InsertPosition::Last => { | ||
166 | if !is_multiline { | ||
167 | // don't insert comma before curly | ||
168 | to_insert.pop(); | ||
169 | } | ||
170 | match self.fields().last() { | ||
171 | Some(it) => after_field!(it), | ||
172 | None => after_l_curly!(), | ||
173 | } | ||
174 | } | ||
175 | InsertPosition::Before(anchor) => { | ||
176 | InsertPosition::Before(anchor.syntax().clone().into()) | ||
177 | } | ||
178 | InsertPosition::After(anchor) => after_field!(anchor), | ||
179 | }; | ||
180 | |||
181 | insert_children(self, position, to_insert.iter().cloned()) | ||
182 | } | ||
183 | |||
184 | fn l_curly(&self) -> Option<SyntaxElement> { | ||
185 | self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) | ||
186 | } | ||
187 | } | ||
188 | |||
189 | impl ast::TypeParam { | ||
190 | #[must_use] | ||
191 | pub fn remove_bounds(&self) -> ast::TypeParam { | ||
192 | let colon = match self.colon_token() { | ||
193 | Some(it) => it, | ||
194 | None => return self.clone(), | ||
195 | }; | ||
196 | let end = match self.type_bound_list() { | ||
197 | Some(it) => it.syntax().clone().into(), | ||
198 | None => colon.clone().into(), | ||
199 | }; | ||
200 | replace_children(self, RangeInclusive::new(colon.into(), end), iter::empty()) | ||
201 | } | ||
202 | } | ||
203 | |||
204 | #[must_use] | ||
205 | pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { | ||
206 | N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() | ||
207 | } | ||
208 | |||
209 | fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { | ||
210 | while let Some(start) = | ||
211 | node.children_with_tokens().find(|it| it.kind() == ATTR || it.kind() == COMMENT) | ||
212 | { | ||
213 | let end = match &start.next_sibling_or_token() { | ||
214 | Some(el) if el.kind() == WHITESPACE => el.clone(), | ||
215 | Some(_) | None => start.clone(), | ||
216 | }; | ||
217 | node = algo::replace_children(&node, RangeInclusive::new(start, end), &mut iter::empty()); | ||
218 | } | ||
219 | node | ||
220 | } | ||
221 | |||
222 | #[must_use] | ||
223 | pub fn replace_descendants<N: AstNode, D: AstNode>( | ||
224 | parent: &N, | ||
225 | replacement_map: impl Iterator<Item = (D, D)>, | ||
226 | ) -> N { | ||
227 | let map = replacement_map | ||
228 | .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into())) | ||
229 | .collect::<FxHashMap<_, _>>(); | ||
230 | let new_syntax = algo::replace_descendants(parent.syntax(), &map); | ||
231 | N::cast(new_syntax).unwrap() | ||
232 | } | ||
233 | |||
234 | // Note this is copy-pasted from fmt. It seems like fmt should be a separate | ||
235 | // crate, but basic tree building should be this crate. However, tree building | ||
236 | // might want to call into fmt... | ||
237 | fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> { | ||
238 | let prev_tokens = std::iter::successors(node.first_token(), |token| token.prev_token()); | ||
239 | for token in prev_tokens { | ||
240 | if let Some(ws) = ast::Whitespace::cast(token.clone()) { | ||
241 | let ws_text = ws.text(); | ||
242 | if let Some(pos) = ws_text.rfind('\n') { | ||
243 | return Some(ws_text[pos + 1..].into()); | ||
244 | } | ||
245 | } | ||
246 | if token.text().contains('\n') { | ||
247 | break; | ||
248 | } | ||
249 | } | ||
250 | None | ||
251 | } | ||
252 | |||
253 | #[must_use] | ||
254 | fn insert_children<N: AstNode>( | ||
255 | parent: &N, | ||
256 | position: InsertPosition<SyntaxElement>, | ||
257 | mut to_insert: impl Iterator<Item = SyntaxElement>, | ||
258 | ) -> N { | ||
259 | let new_syntax = algo::insert_children(parent.syntax(), position, &mut to_insert); | ||
260 | N::cast(new_syntax).unwrap() | ||
261 | } | ||
262 | |||
263 | #[must_use] | ||
264 | fn replace_children<N: AstNode>( | ||
265 | parent: &N, | ||
266 | to_replace: RangeInclusive<SyntaxElement>, | ||
267 | mut to_insert: impl Iterator<Item = SyntaxElement>, | ||
268 | ) -> N { | ||
269 | let new_syntax = algo::replace_children(parent.syntax(), to_replace, &mut to_insert); | ||
270 | N::cast(new_syntax).unwrap() | ||
271 | } | ||
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs index 5f7e9f5b1..cefc00402 100644 --- a/crates/ra_syntax/src/ast/extensions.rs +++ b/crates/ra_syntax/src/ast/extensions.rs | |||
@@ -1,10 +1,8 @@ | |||
1 | //! Various extension methods to ast Nodes, which are hard to code-generate. | 1 | //! Various extension methods to ast Nodes, which are hard to code-generate. |
2 | //! Extensions for various expressions live in a sibling `expr_extensions` module. | 2 | //! Extensions for various expressions live in a sibling `expr_extensions` module. |
3 | 3 | ||
4 | use itertools::Itertools; | ||
5 | |||
6 | use crate::{ | 4 | use crate::{ |
7 | ast::{self, child_opt, children, AstNode, SyntaxNode}, | 5 | ast::{self, child_opt, children, AstChildren, AstNode, AttrInput, SyntaxNode}, |
8 | SmolStr, SyntaxElement, | 6 | SmolStr, SyntaxElement, |
9 | SyntaxKind::*, | 7 | SyntaxKind::*, |
10 | SyntaxToken, T, | 8 | SyntaxToken, T, |
@@ -21,6 +19,16 @@ impl ast::NameRef { | |||
21 | pub fn text(&self) -> &SmolStr { | 19 | pub fn text(&self) -> &SmolStr { |
22 | text_of_first_token(self.syntax()) | 20 | text_of_first_token(self.syntax()) |
23 | } | 21 | } |
22 | |||
23 | pub fn as_tuple_field(&self) -> Option<usize> { | ||
24 | self.syntax().children_with_tokens().find_map(|c| { | ||
25 | if c.kind() == SyntaxKind::INT_NUMBER { | ||
26 | c.as_token().and_then(|tok| tok.text().as_str().parse().ok()) | ||
27 | } else { | ||
28 | None | ||
29 | } | ||
30 | }) | ||
31 | } | ||
24 | } | 32 | } |
25 | 33 | ||
26 | fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { | 34 | fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { |
@@ -28,62 +36,37 @@ fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { | |||
28 | } | 36 | } |
29 | 37 | ||
30 | impl ast::Attr { | 38 | impl ast::Attr { |
31 | pub fn is_inner(&self) -> bool { | 39 | pub fn as_simple_atom(&self) -> Option<SmolStr> { |
32 | let tt = match self.value() { | 40 | match self.input() { |
33 | None => return false, | 41 | None => self.simple_name(), |
34 | Some(tt) => tt, | 42 | Some(_) => None, |
35 | }; | ||
36 | |||
37 | let prev = match tt.syntax().prev_sibling() { | ||
38 | None => return false, | ||
39 | Some(prev) => prev, | ||
40 | }; | ||
41 | |||
42 | prev.kind() == T![!] | ||
43 | } | ||
44 | |||
45 | pub fn as_atom(&self) -> Option<SmolStr> { | ||
46 | let tt = self.value()?; | ||
47 | let (_bra, attr, _ket) = tt.syntax().children_with_tokens().collect_tuple()?; | ||
48 | if attr.kind() == IDENT { | ||
49 | Some(attr.as_token()?.text().clone()) | ||
50 | } else { | ||
51 | None | ||
52 | } | 43 | } |
53 | } | 44 | } |
54 | 45 | ||
55 | pub fn as_call(&self) -> Option<(SmolStr, ast::TokenTree)> { | 46 | pub fn as_simple_call(&self) -> Option<(SmolStr, ast::TokenTree)> { |
56 | let tt = self.value()?; | 47 | match self.input() { |
57 | let (_bra, attr, args, _ket) = tt.syntax().children_with_tokens().collect_tuple()?; | 48 | Some(AttrInput::TokenTree(tt)) => Some((self.simple_name()?, tt)), |
58 | let args = ast::TokenTree::cast(args.as_node()?.clone())?; | 49 | _ => None, |
59 | if attr.kind() == IDENT { | ||
60 | Some((attr.as_token()?.text().clone(), args)) | ||
61 | } else { | ||
62 | None | ||
63 | } | 50 | } |
64 | } | 51 | } |
65 | 52 | ||
66 | pub fn as_named(&self) -> Option<SmolStr> { | 53 | pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> { |
67 | let tt = self.value()?; | 54 | match self.input() { |
68 | let attr = tt.syntax().children_with_tokens().nth(1)?; | 55 | Some(AttrInput::Literal(lit)) => { |
69 | if attr.kind() == IDENT { | 56 | let key = self.simple_name()?; |
70 | Some(attr.as_token()?.text().clone()) | 57 | // FIXME: escape? raw string? |
71 | } else { | 58 | let value = lit.syntax().first_token()?.text().trim_matches('"').into(); |
72 | None | 59 | Some((key, value)) |
60 | } | ||
61 | _ => None, | ||
73 | } | 62 | } |
74 | } | 63 | } |
75 | 64 | ||
76 | pub fn as_key_value(&self) -> Option<(SmolStr, SmolStr)> { | 65 | pub fn simple_name(&self) -> Option<SmolStr> { |
77 | let tt = self.value()?; | 66 | let path = self.path()?; |
78 | let tt_node = tt.syntax(); | 67 | match (path.segment(), path.qualifier()) { |
79 | let attr = tt_node.children_with_tokens().nth(1)?; | 68 | (Some(segment), None) => Some(segment.syntax().first_token()?.text().clone()), |
80 | if attr.kind() == IDENT { | 69 | _ => None, |
81 | let key = attr.as_token()?.text().clone(); | ||
82 | let val_node = tt_node.children_with_tokens().find(|t| t.kind() == STRING)?; | ||
83 | let val = val_node.as_token()?.text().trim_start_matches('"').trim_end_matches('"'); | ||
84 | Some((key, SmolStr::new(val))) | ||
85 | } else { | ||
86 | None | ||
87 | } | 70 | } |
88 | } | 71 | } |
89 | } | 72 | } |
@@ -193,6 +176,16 @@ impl ast::ImplBlock { | |||
193 | } | 176 | } |
194 | } | 177 | } |
195 | 178 | ||
179 | impl ast::AttrsOwner for ast::ImplItem { | ||
180 | fn attrs(&self) -> AstChildren<ast::Attr> { | ||
181 | match self { | ||
182 | ast::ImplItem::FnDef(it) => it.attrs(), | ||
183 | ast::ImplItem::TypeAliasDef(it) => it.attrs(), | ||
184 | ast::ImplItem::ConstDef(it) => it.attrs(), | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | |||
196 | #[derive(Debug, Clone, PartialEq, Eq)] | 189 | #[derive(Debug, Clone, PartialEq, Eq)] |
197 | pub enum StructKind { | 190 | pub enum StructKind { |
198 | Tuple(ast::TupleFieldDefList), | 191 | Tuple(ast::TupleFieldDefList), |
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs index dc1f8c82c..34b22c3e2 100644 --- a/crates/ra_syntax/src/ast/generated.rs +++ b/crates/ra_syntax/src/ast/generated.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | // Generated file, do not edit by hand, see `crate/ra_tools/src/codegen` | 1 | //! Generated file, do not edit by hand, see `crate/ra_tools/src/codegen` |
2 | 2 | ||
3 | use crate::{ | 3 | use crate::{ |
4 | ast::{self, AstChildren, AstNode}, | 4 | ast::{self, AstChildren, AstNode}, |
@@ -166,11 +166,52 @@ impl AstNode for Attr { | |||
166 | } | 166 | } |
167 | } | 167 | } |
168 | impl Attr { | 168 | impl Attr { |
169 | pub fn value(&self) -> Option<TokenTree> { | 169 | pub fn path(&self) -> Option<Path> { |
170 | AstChildren::new(&self.syntax).next() | ||
171 | } | ||
172 | pub fn input(&self) -> Option<AttrInput> { | ||
170 | AstChildren::new(&self.syntax).next() | 173 | AstChildren::new(&self.syntax).next() |
171 | } | 174 | } |
172 | } | 175 | } |
173 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 176 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
177 | pub enum AttrInput { | ||
178 | Literal(Literal), | ||
179 | TokenTree(TokenTree), | ||
180 | } | ||
181 | impl From<Literal> for AttrInput { | ||
182 | fn from(node: Literal) -> AttrInput { | ||
183 | AttrInput::Literal(node) | ||
184 | } | ||
185 | } | ||
186 | impl From<TokenTree> for AttrInput { | ||
187 | fn from(node: TokenTree) -> AttrInput { | ||
188 | AttrInput::TokenTree(node) | ||
189 | } | ||
190 | } | ||
191 | impl AstNode for AttrInput { | ||
192 | fn can_cast(kind: SyntaxKind) -> bool { | ||
193 | match kind { | ||
194 | LITERAL | TOKEN_TREE => true, | ||
195 | _ => false, | ||
196 | } | ||
197 | } | ||
198 | fn cast(syntax: SyntaxNode) -> Option<Self> { | ||
199 | let res = match syntax.kind() { | ||
200 | LITERAL => AttrInput::Literal(Literal { syntax }), | ||
201 | TOKEN_TREE => AttrInput::TokenTree(TokenTree { syntax }), | ||
202 | _ => return None, | ||
203 | }; | ||
204 | Some(res) | ||
205 | } | ||
206 | fn syntax(&self) -> &SyntaxNode { | ||
207 | match self { | ||
208 | AttrInput::Literal(it) => &it.syntax, | ||
209 | AttrInput::TokenTree(it) => &it.syntax, | ||
210 | } | ||
211 | } | ||
212 | } | ||
213 | impl AttrInput {} | ||
214 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
174 | pub struct AwaitExpr { | 215 | pub struct AwaitExpr { |
175 | pub(crate) syntax: SyntaxNode, | 216 | pub(crate) syntax: SyntaxNode, |
176 | } | 217 | } |
@@ -1921,6 +1962,7 @@ impl AstNode for ModuleItem { | |||
1921 | } | 1962 | } |
1922 | } | 1963 | } |
1923 | } | 1964 | } |
1965 | impl ast::AttrsOwner for ModuleItem {} | ||
1924 | impl ModuleItem {} | 1966 | impl ModuleItem {} |
1925 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 1967 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
1926 | pub struct Name { | 1968 | pub struct Name { |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index c06c62b3b..287a40bee 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -133,3 +133,51 @@ fn ast_from_text<N: AstNode>(text: &str) -> N { | |||
133 | let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); | 133 | let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); |
134 | res | 134 | res |
135 | } | 135 | } |
136 | |||
137 | pub mod tokens { | ||
138 | use crate::{AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken, T}; | ||
139 | use once_cell::sync::Lazy; | ||
140 | |||
141 | static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| SourceFile::parse(",\n; ;")); | ||
142 | |||
143 | pub fn comma() -> SyntaxToken { | ||
144 | SOURCE_FILE | ||
145 | .tree() | ||
146 | .syntax() | ||
147 | .descendants_with_tokens() | ||
148 | .filter_map(|it| it.into_token()) | ||
149 | .find(|it| it.kind() == T![,]) | ||
150 | .unwrap() | ||
151 | } | ||
152 | |||
153 | pub fn single_space() -> SyntaxToken { | ||
154 | SOURCE_FILE | ||
155 | .tree() | ||
156 | .syntax() | ||
157 | .descendants_with_tokens() | ||
158 | .filter_map(|it| it.into_token()) | ||
159 | .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ") | ||
160 | .unwrap() | ||
161 | } | ||
162 | |||
163 | pub fn single_newline() -> SyntaxToken { | ||
164 | SOURCE_FILE | ||
165 | .tree() | ||
166 | .syntax() | ||
167 | .descendants_with_tokens() | ||
168 | .filter_map(|it| it.into_token()) | ||
169 | .find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n") | ||
170 | .unwrap() | ||
171 | } | ||
172 | |||
173 | pub struct WsBuilder(SourceFile); | ||
174 | |||
175 | impl WsBuilder { | ||
176 | pub fn new(text: &str) -> WsBuilder { | ||
177 | WsBuilder(SourceFile::parse(text).ok().unwrap()) | ||
178 | } | ||
179 | pub fn ws(&self) -> SyntaxToken { | ||
180 | self.0.syntax().first_child_or_token().unwrap().into_token().unwrap() | ||
181 | } | ||
182 | } | ||
183 | } | ||
diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs index c3e676d4c..f275a4955 100644 --- a/crates/ra_syntax/src/ast/traits.rs +++ b/crates/ra_syntax/src/ast/traits.rs | |||
@@ -99,7 +99,7 @@ pub trait AttrsOwner: AstNode { | |||
99 | children(self) | 99 | children(self) |
100 | } | 100 | } |
101 | fn has_atom_attr(&self, atom: &str) -> bool { | 101 | fn has_atom_attr(&self, atom: &str) -> bool { |
102 | self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom) | 102 | self.attrs().filter_map(|x| x.as_simple_atom()).any(|x| x == atom) |
103 | } | 103 | } |
104 | } | 104 | } |
105 | 105 | ||
diff --git a/crates/ra_syntax/src/fuzz.rs b/crates/ra_syntax/src/fuzz.rs index 698a624ec..7012df7f0 100644 --- a/crates/ra_syntax/src/fuzz.rs +++ b/crates/ra_syntax/src/fuzz.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
1 | use crate::{validation, AstNode, SourceFile, TextRange, TextUnit}; | 3 | use crate::{validation, AstNode, SourceFile, TextRange, TextUnit}; |
2 | use ra_text_edit::AtomTextEdit; | 4 | use ra_text_edit::AtomTextEdit; |
3 | use std::str::{self, FromStr}; | 5 | use std::str::{self, FromStr}; |
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index 5f395501a..25e6f64ce 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron | |||
@@ -1,5 +1,5 @@ | |||
1 | // Stores definitions which must be used in multiple places | 1 | // Stores definitions which must be used in multiple places |
2 | // See `cargo gen-syntax` (defined in crates/tools/src/main.rs) | 2 | // See `cargo gen-syntax` (defined in crates/ra_tools/src/main.rs) |
3 | Grammar( | 3 | Grammar( |
4 | punct: [ | 4 | punct: [ |
5 | (";", "SEMI"), | 5 | (";", "SEMI"), |
@@ -397,7 +397,8 @@ Grammar( | |||
397 | ), | 397 | ), |
398 | "ModuleItem": ( | 398 | "ModuleItem": ( |
399 | enum: ["StructDef", "EnumDef", "FnDef", "TraitDef", "TypeAliasDef", "ImplBlock", | 399 | enum: ["StructDef", "EnumDef", "FnDef", "TraitDef", "TypeAliasDef", "ImplBlock", |
400 | "UseItem", "ExternCrateItem", "ConstDef", "StaticDef", "Module" ] | 400 | "UseItem", "ExternCrateItem", "ConstDef", "StaticDef", "Module" ], |
401 | traits: ["AttrsOwner"] | ||
401 | ), | 402 | ), |
402 | "ImplItem": ( | 403 | "ImplItem": ( |
403 | enum: ["FnDef", "TypeAliasDef", "ConstDef"] | 404 | enum: ["FnDef", "TypeAliasDef", "ConstDef"] |
@@ -576,7 +577,8 @@ Grammar( | |||
576 | traits: [ "NameOwner", "AttrsOwner","DocCommentsOwner" ], | 577 | traits: [ "NameOwner", "AttrsOwner","DocCommentsOwner" ], |
577 | options: [ "TokenTree", "Path" ], | 578 | options: [ "TokenTree", "Path" ], |
578 | ), | 579 | ), |
579 | "Attr": ( options: [ ["value", "TokenTree"] ] ), | 580 | "AttrInput": ( enum: [ "Literal", "TokenTree" ] ), |
581 | "Attr": ( options: [ "Path", [ "input", "AttrInput" ] ] ), | ||
580 | "TokenTree": (), | 582 | "TokenTree": (), |
581 | "TypeParamList": ( | 583 | "TypeParamList": ( |
582 | collections: [ | 584 | collections: [ |
diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index edb6076bb..c315ba552 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs | |||
@@ -160,6 +160,17 @@ impl SourceFile { | |||
160 | } | 160 | } |
161 | } | 161 | } |
162 | 162 | ||
163 | #[macro_export] | ||
164 | macro_rules! match_ast { | ||
165 | (match $node:ident { | ||
166 | $( ast::$ast:ident($it:ident) => $res:block, )* | ||
167 | _ => $catch_all:expr, | ||
168 | }) => {{ | ||
169 | $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )* | ||
170 | { $catch_all } | ||
171 | }}; | ||
172 | } | ||
173 | |||
163 | /// This test does not assert anything and instead just shows off the crate's | 174 | /// This test does not assert anything and instead just shows off the crate's |
164 | /// API. | 175 | /// API. |
165 | #[test] | 176 | #[test] |
@@ -294,7 +305,7 @@ fn api_walkthrough() { | |||
294 | // To recursively process the tree, there are three approaches: | 305 | // To recursively process the tree, there are three approaches: |
295 | // 1. explicitly call getter methods on AST nodes. | 306 | // 1. explicitly call getter methods on AST nodes. |
296 | // 2. use descendants and `AstNode::cast`. | 307 | // 2. use descendants and `AstNode::cast`. |
297 | // 3. use descendants and the visitor. | 308 | // 3. use descendants and `match_ast!`. |
298 | // | 309 | // |
299 | // Here's how the first one looks like: | 310 | // Here's how the first one looks like: |
300 | let exprs_cast: Vec<String> = file | 311 | let exprs_cast: Vec<String> = file |
@@ -304,17 +315,17 @@ fn api_walkthrough() { | |||
304 | .map(|expr| expr.syntax().text().to_string()) | 315 | .map(|expr| expr.syntax().text().to_string()) |
305 | .collect(); | 316 | .collect(); |
306 | 317 | ||
307 | // An alternative is to use a visitor. The visitor does not do traversal | 318 | // An alternative is to use a macro. |
308 | // automatically (so it's more akin to a generic lambda) and is constructed | ||
309 | // from closures. This seems more flexible than a single generated visitor | ||
310 | // trait. | ||
311 | use algo::visit::{visitor, Visitor}; | ||
312 | let mut exprs_visit = Vec::new(); | 319 | let mut exprs_visit = Vec::new(); |
313 | for node in file.syntax().descendants() { | 320 | for node in file.syntax().descendants() { |
314 | if let Some(result) = | 321 | match_ast! { |
315 | visitor().visit::<ast::Expr, _>(|expr| expr.syntax().text().to_string()).accept(&node) | 322 | match node { |
316 | { | 323 | ast::Expr(it) => { |
317 | exprs_visit.push(result); | 324 | let res = it.syntax().text().to_string(); |
325 | exprs_visit.push(res); | ||
326 | }, | ||
327 | _ => (), | ||
328 | } | ||
318 | } | 329 | } |
319 | } | 330 | } |
320 | assert_eq!(exprs_cast, exprs_visit); | 331 | assert_eq!(exprs_cast, exprs_visit); |
diff --git a/crates/ra_syntax/src/parsing/lexer.rs b/crates/ra_syntax/src/parsing/lexer.rs index bdb01d40b..6d839208d 100644 --- a/crates/ra_syntax/src/parsing/lexer.rs +++ b/crates/ra_syntax/src/parsing/lexer.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
1 | use crate::{ | 3 | use crate::{ |
2 | SyntaxKind::{self, *}, | 4 | SyntaxKind::{self, *}, |
3 | TextUnit, | 5 | TextUnit, |
diff --git a/crates/ra_syntax/src/parsing/text_token_source.rs b/crates/ra_syntax/src/parsing/text_token_source.rs index 64cb20ae8..e793f93a4 100644 --- a/crates/ra_syntax/src/parsing/text_token_source.rs +++ b/crates/ra_syntax/src/parsing/text_token_source.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
1 | use ra_parser::Token as PToken; | 3 | use ra_parser::Token as PToken; |
2 | use ra_parser::TokenSource; | 4 | use ra_parser::TokenSource; |
3 | 5 | ||
diff --git a/crates/ra_syntax/src/parsing/text_tree_sink.rs b/crates/ra_syntax/src/parsing/text_tree_sink.rs index be6e51780..142164316 100644 --- a/crates/ra_syntax/src/parsing/text_tree_sink.rs +++ b/crates/ra_syntax/src/parsing/text_tree_sink.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
1 | use std::mem; | 3 | use std::mem; |
2 | 4 | ||
3 | use ra_parser::{ParseError, TreeSink}; | 5 | use ra_parser::{ParseError, TreeSink}; |
diff --git a/crates/ra_syntax/src/ptr.rs b/crates/ra_syntax/src/ptr.rs index 992034ef0..31167cada 100644 --- a/crates/ra_syntax/src/ptr.rs +++ b/crates/ra_syntax/src/ptr.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
1 | use std::{iter::successors, marker::PhantomData}; | 3 | use std::{iter::successors, marker::PhantomData}; |
2 | 4 | ||
3 | use crate::{AstNode, SyntaxKind, SyntaxNode, TextRange}; | 5 | use crate::{AstNode, SyntaxKind, SyntaxNode, TextRange}; |
diff --git a/crates/ra_syntax/src/syntax_error.rs b/crates/ra_syntax/src/syntax_error.rs index 5aefec768..d6eca2ad7 100644 --- a/crates/ra_syntax/src/syntax_error.rs +++ b/crates/ra_syntax/src/syntax_error.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
1 | use std::fmt; | 3 | use std::fmt; |
2 | 4 | ||
3 | use ra_parser::ParseError; | 5 | use ra_parser::ParseError; |
diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs index 16824f3c4..ab4f15908 100644 --- a/crates/ra_syntax/src/validation.rs +++ b/crates/ra_syntax/src/validation.rs | |||
@@ -1,10 +1,11 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
1 | mod block; | 3 | mod block; |
2 | 4 | ||
3 | use rustc_lexer::unescape; | 5 | use rustc_lexer::unescape; |
4 | 6 | ||
5 | use crate::{ | 7 | use crate::{ |
6 | algo::visit::{visitor_ctx, VisitorCtx}, | 8 | ast, match_ast, AstNode, SyntaxError, SyntaxErrorKind, |
7 | ast, AstNode, SyntaxError, SyntaxErrorKind, | ||
8 | SyntaxKind::{BYTE, BYTE_STRING, CHAR, INT_NUMBER, STRING}, | 9 | SyntaxKind::{BYTE, BYTE_STRING, CHAR, INT_NUMBER, STRING}, |
9 | SyntaxNode, SyntaxToken, TextUnit, T, | 10 | SyntaxNode, SyntaxToken, TextUnit, T, |
10 | }; | 11 | }; |
@@ -95,12 +96,15 @@ impl From<rustc_lexer::unescape::EscapeError> for SyntaxErrorKind { | |||
95 | pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> { | 96 | pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> { |
96 | let mut errors = Vec::new(); | 97 | let mut errors = Vec::new(); |
97 | for node in root.descendants() { | 98 | for node in root.descendants() { |
98 | let _ = visitor_ctx(&mut errors) | 99 | match_ast! { |
99 | .visit::<ast::Literal, _>(validate_literal) | 100 | match node { |
100 | .visit::<ast::BlockExpr, _>(block::validate_block_expr) | 101 | ast::Literal(it) => { validate_literal(it, &mut errors) }, |
101 | .visit::<ast::FieldExpr, _>(|it, errors| validate_numeric_name(it.name_ref(), errors)) | 102 | ast::BlockExpr(it) => { block::validate_block_expr(it, &mut errors) }, |
102 | .visit::<ast::RecordField, _>(|it, errors| validate_numeric_name(it.name_ref(), errors)) | 103 | ast::FieldExpr(it) => { validate_numeric_name(it.name_ref(), &mut errors) }, |
103 | .accept(&node); | 104 | ast::RecordField(it) => { validate_numeric_name(it.name_ref(), &mut errors) }, |
105 | _ => (), | ||
106 | } | ||
107 | } | ||
104 | } | 108 | } |
105 | errors | 109 | errors |
106 | } | 110 | } |
diff --git a/crates/ra_syntax/src/validation/block.rs b/crates/ra_syntax/src/validation/block.rs index 3c9e96eb3..c85bbc1f4 100644 --- a/crates/ra_syntax/src/validation/block.rs +++ b/crates/ra_syntax/src/validation/block.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
1 | use crate::{ | 3 | use crate::{ |
2 | ast::{self, AstNode, AttrsOwner}, | 4 | ast::{self, AstNode, AttrsOwner}, |
3 | SyntaxError, | 5 | SyntaxError, |