aboutsummaryrefslogtreecommitdiff
path: root/crates/syntax/src/ast/edit.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/syntax/src/ast/edit.rs')
-rw-r--r--crates/syntax/src/ast/edit.rs503
1 files changed, 38 insertions, 465 deletions
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 8c60927e4..61952377f 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -2,20 +2,16 @@
2//! immutable, all function here return a fresh copy of the tree, instead of 2//! immutable, all function here return a fresh copy of the tree, instead of
3//! doing an in-place modification. 3//! doing an in-place modification.
4use std::{ 4use std::{
5 array, fmt, iter, 5 fmt, iter,
6 ops::{self, RangeInclusive}, 6 ops::{self, RangeInclusive},
7}; 7};
8 8
9use arrayvec::ArrayVec; 9use arrayvec::ArrayVec;
10 10
11use crate::{ 11use crate::{
12 algo::{self, SyntaxRewriter}, 12 algo,
13 ast::{ 13 ast::{self, make, AstNode},
14 self, 14 ted, AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxKind,
15 make::{self, tokens},
16 AstNode, GenericParamsOwner, NameOwner, TypeBoundsOwner,
17 },
18 AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind,
19 SyntaxKind::{ATTR, COMMENT, WHITESPACE}, 15 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
20 SyntaxNode, SyntaxToken, T, 16 SyntaxNode, SyntaxToken, T,
21}; 17};
@@ -29,250 +25,6 @@ impl ast::BinExpr {
29 } 25 }
30} 26}
31 27
32impl ast::Fn {
33 #[must_use]
34 pub fn with_body(&self, body: ast::BlockExpr) -> ast::Fn {
35 let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
36 let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.body() {
37 old_body.syntax().clone().into()
38 } else if let Some(semi) = self.semicolon_token() {
39 to_insert.push(make::tokens::single_space().into());
40 semi.into()
41 } else {
42 to_insert.push(make::tokens::single_space().into());
43 to_insert.push(body.syntax().clone().into());
44 return self.insert_children(InsertPosition::Last, to_insert);
45 };
46 to_insert.push(body.syntax().clone().into());
47 self.replace_children(single_node(old_body_or_semi), to_insert)
48 }
49
50 #[must_use]
51 pub fn with_generic_param_list(&self, generic_args: ast::GenericParamList) -> ast::Fn {
52 if let Some(old) = self.generic_param_list() {
53 return self.replace_descendant(old, generic_args);
54 }
55
56 let anchor = self.name().expect("The function must have a name").syntax().clone();
57
58 let to_insert = [generic_args.syntax().clone().into()];
59 self.insert_children(InsertPosition::After(anchor.into()), array::IntoIter::new(to_insert))
60 }
61}
62
63fn make_multiline<N>(node: N) -> N
64where
65 N: AstNode + Clone,
66{
67 let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
68 Some(it) => it,
69 None => return node,
70 };
71 let sibling = match l_curly.next_sibling_or_token() {
72 Some(it) => it,
73 None => return node,
74 };
75 let existing_ws = match sibling.as_token() {
76 None => None,
77 Some(tok) if tok.kind() != WHITESPACE => None,
78 Some(ws) => {
79 if ws.text().contains('\n') {
80 return node;
81 }
82 Some(ws.clone())
83 }
84 };
85
86 let indent = leading_indent(node.syntax()).unwrap_or_default();
87 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
88 let to_insert = iter::once(ws.ws().into());
89 match existing_ws {
90 None => node.insert_children(InsertPosition::After(l_curly), to_insert),
91 Some(ws) => node.replace_children(single_node(ws), to_insert),
92 }
93}
94
95impl ast::Impl {
96 #[must_use]
97 pub fn with_assoc_item_list(&self, items: ast::AssocItemList) -> ast::Impl {
98 let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
99 if let Some(old_items) = self.assoc_item_list() {
100 let to_replace: SyntaxElement = old_items.syntax().clone().into();
101 to_insert.push(items.syntax().clone().into());
102 self.replace_children(single_node(to_replace), to_insert)
103 } else {
104 to_insert.push(make::tokens::single_space().into());
105 to_insert.push(items.syntax().clone().into());
106 self.insert_children(InsertPosition::Last, to_insert)
107 }
108 }
109}
110
111impl ast::AssocItemList {
112 #[must_use]
113 pub fn append_items(
114 &self,
115 items: impl IntoIterator<Item = ast::AssocItem>,
116 ) -> ast::AssocItemList {
117 let mut res = self.clone();
118 if !self.syntax().text().contains_char('\n') {
119 res = make_multiline(res);
120 }
121 items.into_iter().for_each(|it| res = res.append_item(it));
122 res.fixup_trailing_whitespace().unwrap_or(res)
123 }
124
125 #[must_use]
126 pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList {
127 let (indent, position, whitespace) = match self.assoc_items().last() {
128 Some(it) => (
129 leading_indent(it.syntax()).unwrap_or_default().to_string(),
130 InsertPosition::After(it.syntax().clone().into()),
131 "\n\n",
132 ),
133 None => match self.l_curly_token() {
134 Some(it) => (
135 " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
136 InsertPosition::After(it.into()),
137 "\n",
138 ),
139 None => return self.clone(),
140 },
141 };
142 let ws = tokens::WsBuilder::new(&format!("{}{}", whitespace, indent));
143 let to_insert: ArrayVec<SyntaxElement, 2> =
144 [ws.ws().into(), item.syntax().clone().into()].into();
145 self.insert_children(position, to_insert)
146 }
147
148 /// Remove extra whitespace between last item and closing curly brace.
149 fn fixup_trailing_whitespace(&self) -> Option<ast::AssocItemList> {
150 let first_token_after_items =
151 self.assoc_items().last()?.syntax().next_sibling_or_token()?;
152 let last_token_before_curly = self.r_curly_token()?.prev_sibling_or_token()?;
153 if last_token_before_curly != first_token_after_items {
154 // there is something more between last item and
155 // right curly than just whitespace - bail out
156 return None;
157 }
158 let whitespace =
159 last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?;
160 let text = whitespace.syntax().text();
161 let newline = text.rfind('\n')?;
162 let keep = tokens::WsBuilder::new(&text[newline..]);
163 Some(self.replace_children(
164 first_token_after_items..=last_token_before_curly,
165 std::iter::once(keep.ws().into()),
166 ))
167 }
168}
169
170impl ast::RecordExprFieldList {
171 #[must_use]
172 pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList {
173 self.insert_field(InsertPosition::Last, field)
174 }
175
176 #[must_use]
177 pub fn insert_field(
178 &self,
179 position: InsertPosition<&'_ ast::RecordExprField>,
180 field: &ast::RecordExprField,
181 ) -> ast::RecordExprFieldList {
182 let is_multiline = self.syntax().text().contains_char('\n');
183 let ws;
184 let space = if is_multiline {
185 ws = tokens::WsBuilder::new(&format!(
186 "\n{} ",
187 leading_indent(self.syntax()).unwrap_or_default()
188 ));
189 ws.ws()
190 } else {
191 tokens::single_space()
192 };
193
194 let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
195 to_insert.push(space.into());
196 to_insert.push(field.syntax().clone().into());
197 to_insert.push(make::token(T![,]).into());
198
199 macro_rules! after_l_curly {
200 () => {{
201 let anchor = match self.l_curly_token() {
202 Some(it) => it.into(),
203 None => return self.clone(),
204 };
205 InsertPosition::After(anchor)
206 }};
207 }
208
209 macro_rules! after_field {
210 ($anchor:expr) => {
211 if let Some(comma) = $anchor
212 .syntax()
213 .siblings_with_tokens(Direction::Next)
214 .find(|it| it.kind() == T![,])
215 {
216 InsertPosition::After(comma)
217 } else {
218 to_insert.insert(0, make::token(T![,]).into());
219 InsertPosition::After($anchor.syntax().clone().into())
220 }
221 };
222 }
223
224 let position = match position {
225 InsertPosition::First => after_l_curly!(),
226 InsertPosition::Last => {
227 if !is_multiline {
228 // don't insert comma before curly
229 to_insert.pop();
230 }
231 match self.fields().last() {
232 Some(it) => after_field!(it),
233 None => after_l_curly!(),
234 }
235 }
236 InsertPosition::Before(anchor) => {
237 InsertPosition::Before(anchor.syntax().clone().into())
238 }
239 InsertPosition::After(anchor) => after_field!(anchor),
240 };
241
242 self.insert_children(position, to_insert)
243 }
244}
245
246impl ast::TypeAlias {
247 #[must_use]
248 pub fn remove_bounds(&self) -> ast::TypeAlias {
249 let colon = match self.colon_token() {
250 Some(it) => it,
251 None => return self.clone(),
252 };
253 let end = match self.type_bound_list() {
254 Some(it) => it.syntax().clone().into(),
255 None => colon.clone().into(),
256 };
257 self.replace_children(colon.into()..=end, iter::empty())
258 }
259}
260
261impl ast::TypeParam {
262 #[must_use]
263 pub fn remove_bounds(&self) -> ast::TypeParam {
264 let colon = match self.colon_token() {
265 Some(it) => it,
266 None => return self.clone(),
267 };
268 let end = match self.type_bound_list() {
269 Some(it) => it.syntax().clone().into(),
270 None => colon.clone().into(),
271 };
272 self.replace_children(colon.into()..=end, iter::empty())
273 }
274}
275
276impl ast::Path { 28impl ast::Path {
277 #[must_use] 29 #[must_use]
278 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { 30 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path {
@@ -313,33 +65,7 @@ impl ast::PathSegment {
313 } 65 }
314} 66}
315 67
316impl ast::Use {
317 #[must_use]
318 pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::Use {
319 if let Some(old) = self.use_tree() {
320 return self.replace_descendant(old, use_tree);
321 }
322 self.clone()
323 }
324}
325
326impl ast::UseTree { 68impl ast::UseTree {
327 #[must_use]
328 pub fn with_path(&self, path: ast::Path) -> ast::UseTree {
329 if let Some(old) = self.path() {
330 return self.replace_descendant(old, path);
331 }
332 self.clone()
333 }
334
335 #[must_use]
336 pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree {
337 if let Some(old) = self.use_tree_list() {
338 return self.replace_descendant(old, use_tree_list);
339 }
340 self.clone()
341 }
342
343 /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items. 69 /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items.
344 #[must_use] 70 #[must_use]
345 pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree { 71 pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree {
@@ -376,134 +102,6 @@ impl ast::UseTree {
376 } 102 }
377} 103}
378 104
379impl ast::MatchArmList {
380 #[must_use]
381 pub fn append_arms(&self, items: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
382 let mut res = self.clone();
383 res = res.strip_if_only_whitespace();
384 if !res.syntax().text().contains_char('\n') {
385 res = make_multiline(res);
386 }
387 items.into_iter().for_each(|it| res = res.append_arm(it));
388 res
389 }
390
391 fn strip_if_only_whitespace(&self) -> ast::MatchArmList {
392 let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']);
393 iter.next(); // Eat the curly
394 let mut inner = iter.take_while(|it| it.kind() != T!['}']);
395 if !inner.clone().all(|it| it.kind() == WHITESPACE) {
396 return self.clone();
397 }
398 let start = match inner.next() {
399 Some(s) => s,
400 None => return self.clone(),
401 };
402 let end = match inner.last() {
403 Some(s) => s,
404 None => start.clone(),
405 };
406 self.replace_children(start..=end, &mut iter::empty())
407 }
408
409 #[must_use]
410 pub fn remove_placeholder(&self) -> ast::MatchArmList {
411 let placeholder =
412 self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
413 if let Some(placeholder) = placeholder {
414 self.remove_arm(&placeholder)
415 } else {
416 self.clone()
417 }
418 }
419
420 #[must_use]
421 fn remove_arm(&self, arm: &ast::MatchArm) -> ast::MatchArmList {
422 let start = arm.syntax().clone();
423 let end = if let Some(comma) = start
424 .siblings_with_tokens(Direction::Next)
425 .skip(1)
426 .find(|it| !it.kind().is_trivia())
427 .filter(|it| it.kind() == T![,])
428 {
429 comma
430 } else {
431 start.clone().into()
432 };
433 self.replace_children(start.into()..=end, None)
434 }
435
436 #[must_use]
437 pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList {
438 let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) {
439 Some(t) => t,
440 None => return self.clone(),
441 };
442 let position = InsertPosition::Before(r_curly);
443 let arm_ws = tokens::WsBuilder::new(" ");
444 let match_indent = &leading_indent(self.syntax()).unwrap_or_default();
445 let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent));
446 let to_insert: ArrayVec<SyntaxElement, 3> =
447 [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into();
448 self.insert_children(position, to_insert)
449 }
450}
451
452impl ast::GenericParamList {
453 #[must_use]
454 pub fn append_params(
455 &self,
456 params: impl IntoIterator<Item = ast::GenericParam>,
457 ) -> ast::GenericParamList {
458 let mut res = self.clone();
459 params.into_iter().for_each(|it| res = res.append_param(it));
460 res
461 }
462
463 #[must_use]
464 pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList {
465 let space = tokens::single_space();
466
467 let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
468 if self.generic_params().next().is_some() {
469 to_insert.push(space.into());
470 }
471 to_insert.push(item.syntax().clone().into());
472
473 macro_rules! after_l_angle {
474 () => {{
475 let anchor = match self.l_angle_token() {
476 Some(it) => it.into(),
477 None => return self.clone(),
478 };
479 InsertPosition::After(anchor)
480 }};
481 }
482
483 macro_rules! after_field {
484 ($anchor:expr) => {
485 if let Some(comma) = $anchor
486 .syntax()
487 .siblings_with_tokens(Direction::Next)
488 .find(|it| it.kind() == T![,])
489 {
490 InsertPosition::After(comma)
491 } else {
492 to_insert.insert(0, make::token(T![,]).into());
493 InsertPosition::After($anchor.syntax().clone().into())
494 }
495 };
496 }
497
498 let position = match self.generic_params().last() {
499 Some(it) => after_field!(it),
500 None => after_l_angle!(),
501 };
502
503 self.insert_children(position, to_insert)
504 }
505}
506
507#[must_use] 105#[must_use]
508pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 106pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
509 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() 107 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
@@ -554,6 +152,12 @@ impl ops::Add<u8> for IndentLevel {
554} 152}
555 153
556impl IndentLevel { 154impl IndentLevel {
155 pub fn single() -> IndentLevel {
156 IndentLevel(0)
157 }
158 pub fn is_zero(&self) -> bool {
159 self.0 == 0
160 }
557 pub fn from_element(element: &SyntaxElement) -> IndentLevel { 161 pub fn from_element(element: &SyntaxElement) -> IndentLevel {
558 match element { 162 match element {
559 rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it), 163 rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),
@@ -588,54 +192,40 @@ impl IndentLevel {
588 /// ``` 192 /// ```
589 /// if you indent the block, the `{` token would stay put. 193 /// if you indent the block, the `{` token would stay put.
590 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { 194 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode {
591 let mut rewriter = SyntaxRewriter::default(); 195 let res = node.clone_subtree().clone_for_update();
592 node.descendants_with_tokens() 196 let tokens = res.preorder_with_tokens().filter_map(|event| match event {
593 .filter_map(|el| el.into_token()) 197 rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
594 .filter_map(ast::Whitespace::cast) 198 _ => None,
595 .filter(|ws| { 199 });
596 let text = ws.syntax().text(); 200 for token in tokens {
597 text.contains('\n') 201 if let Some(ws) = ast::Whitespace::cast(token) {
598 }) 202 if ws.text().contains('\n') {
599 .for_each(|ws| { 203 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self));
600 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,)); 204 ted::replace(ws.syntax(), &new_ws)
601 rewriter.replace(ws.syntax(), &new_ws) 205 }
602 }); 206 }
603 rewriter.rewrite(&node) 207 }
208 res.clone_subtree()
604 } 209 }
605 210
606 fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode { 211 fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
607 let mut rewriter = SyntaxRewriter::default(); 212 let res = node.clone_subtree().clone_for_update();
608 node.descendants_with_tokens() 213 let tokens = res.preorder_with_tokens().filter_map(|event| match event {
609 .filter_map(|el| el.into_token()) 214 rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
610 .filter_map(ast::Whitespace::cast) 215 _ => None,
611 .filter(|ws| { 216 });
612 let text = ws.syntax().text(); 217 for token in tokens {
613 text.contains('\n') 218 if let Some(ws) = ast::Whitespace::cast(token) {
614 }) 219 if ws.text().contains('\n') {
615 .for_each(|ws| { 220 let new_ws = make::tokens::whitespace(
616 let new_ws = make::tokens::whitespace( 221 &ws.syntax().text().replace(&format!("\n{}", self), "\n"),
617 &ws.syntax().text().replace(&format!("\n{}", self), "\n"), 222 );
618 ); 223 ted::replace(ws.syntax(), &new_ws)
619 rewriter.replace(ws.syntax(), &new_ws) 224 }
620 });
621 rewriter.rewrite(&node)
622 }
623}
624
625// FIXME: replace usages with IndentLevel above
626fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
627 for token in prev_tokens(node.first_token()?) {
628 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
629 let ws_text = ws.text();
630 if let Some(pos) = ws_text.rfind('\n') {
631 return Some(ws_text[pos + 1..].into());
632 } 225 }
633 } 226 }
634 if token.text().contains('\n') { 227 res.clone_subtree()
635 break;
636 }
637 } 228 }
638 None
639} 229}
640 230
641fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { 231fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
@@ -662,23 +252,6 @@ pub trait AstNodeEdit: AstNode + Clone + Sized {
662 let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert); 252 let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert);
663 Self::cast(new_syntax).unwrap() 253 Self::cast(new_syntax).unwrap()
664 } 254 }
665
666 #[must_use]
667 fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self {
668 self.replace_descendants(iter::once((old, new)))
669 }
670
671 #[must_use]
672 fn replace_descendants<D: AstNode>(
673 &self,
674 replacement_map: impl IntoIterator<Item = (D, D)>,
675 ) -> Self {
676 let mut rewriter = SyntaxRewriter::default();
677 for (from, to) in replacement_map {
678 rewriter.replace(from.syntax(), to.syntax())
679 }
680 rewriter.rewrite_ast(self)
681 }
682 fn indent_level(&self) -> IndentLevel { 255 fn indent_level(&self) -> IndentLevel {
683 IndentLevel::from_node(self.syntax()) 256 IndentLevel::from_node(self.syntax())
684 } 257 }