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/edit.rs271
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs93
-rw-r--r--crates/ra_syntax/src/ast/generated.rs46
-rw-r--r--crates/ra_syntax/src/ast/make.rs48
-rw-r--r--crates/ra_syntax/src/ast/traits.rs2
5 files changed, 407 insertions, 53 deletions
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.
4use std::{iter, ops::RangeInclusive};
5
6use arrayvec::ArrayVec;
7use rustc_hash::FxHashMap;
8
9use 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
21impl 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
41impl 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
109impl 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
189impl 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]
205pub 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
209fn 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]
223pub 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...
237fn 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]
254fn 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]
264fn 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
4use itertools::Itertools;
5
6use crate::{ 4use 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
26fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { 34fn text_of_first_token(node: &SyntaxNode) -> &SmolStr {
@@ -28,62 +36,37 @@ fn text_of_first_token(node: &SyntaxNode) -> &SmolStr {
28} 36}
29 37
30impl ast::Attr { 38impl 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
179impl 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)]
197pub enum StructKind { 190pub 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
3use crate::{ 3use crate::{
4 ast::{self, AstChildren, AstNode}, 4 ast::{self, AstChildren, AstNode},
@@ -166,11 +166,52 @@ impl AstNode for Attr {
166 } 166 }
167} 167}
168impl Attr { 168impl 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)]
177pub enum AttrInput {
178 Literal(Literal),
179 TokenTree(TokenTree),
180}
181impl From<Literal> for AttrInput {
182 fn from(node: Literal) -> AttrInput {
183 AttrInput::Literal(node)
184 }
185}
186impl From<TokenTree> for AttrInput {
187 fn from(node: TokenTree) -> AttrInput {
188 AttrInput::TokenTree(node)
189 }
190}
191impl 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}
213impl AttrInput {}
214#[derive(Debug, Clone, PartialEq, Eq, Hash)]
174pub struct AwaitExpr { 215pub 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}
1965impl ast::AttrsOwner for ModuleItem {}
1924impl ModuleItem {} 1966impl ModuleItem {}
1925#[derive(Debug, Clone, PartialEq, Eq, Hash)] 1967#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1926pub struct Name { 1968pub 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
137pub 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