]> git.lizzy.rs Git - rust.git/blob - crates/syntax/src/ast/edit.rs
80be8b79c6e25b539689fcacefde1b797eac0f16
[rust.git] / crates / syntax / src / ast / edit.rs
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::{
5     fmt, iter,
6     ops::{self, RangeInclusive},
7 };
8
9 use arrayvec::ArrayVec;
10
11 use crate::{
12     algo::{self, neighbor, SyntaxRewriter},
13     ast::{
14         self,
15         make::{self, tokens},
16         AstNode, GenericParamsOwner, NameOwner, TypeBoundsOwner,
17     },
18     AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind,
19     SyntaxKind::{ATTR, COMMENT, WHITESPACE},
20     SyntaxNode, SyntaxToken, T,
21 };
22
23 impl ast::BinExpr {
24     #[must_use]
25     pub fn replace_op(&self, op: SyntaxKind) -> Option<ast::BinExpr> {
26         let op_node: SyntaxElement = self.op_details()?.0.into();
27         let to_insert: Option<SyntaxElement> = Some(make::token(op).into());
28         Some(self.replace_children(single_node(op_node), to_insert))
29     }
30 }
31
32 impl 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 mut to_insert: ArrayVec<[SyntaxElement; 1]> = ArrayVec::new();
59         to_insert.push(generic_args.syntax().clone().into());
60         self.insert_children(InsertPosition::After(anchor.into()), to_insert)
61     }
62 }
63
64 fn make_multiline<N>(node: N) -> N
65 where
66     N: AstNode + Clone,
67 {
68     let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
69         Some(it) => it,
70         None => return node,
71     };
72     let sibling = match l_curly.next_sibling_or_token() {
73         Some(it) => it,
74         None => return node,
75     };
76     let existing_ws = match sibling.as_token() {
77         None => None,
78         Some(tok) if tok.kind() != WHITESPACE => None,
79         Some(ws) => {
80             if ws.text().contains('\n') {
81                 return node;
82             }
83             Some(ws.clone())
84         }
85     };
86
87     let indent = leading_indent(node.syntax()).unwrap_or_default();
88     let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
89     let to_insert = iter::once(ws.ws().into());
90     match existing_ws {
91         None => node.insert_children(InsertPosition::After(l_curly), to_insert),
92         Some(ws) => node.replace_children(single_node(ws), to_insert),
93     }
94 }
95
96 impl ast::Impl {
97     #[must_use]
98     pub fn with_assoc_item_list(&self, items: ast::AssocItemList) -> ast::Impl {
99         let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
100         if let Some(old_items) = self.assoc_item_list() {
101             let to_replace: SyntaxElement = old_items.syntax().clone().into();
102             to_insert.push(items.syntax().clone().into());
103             self.replace_children(single_node(to_replace), to_insert)
104         } else {
105             to_insert.push(make::tokens::single_space().into());
106             to_insert.push(items.syntax().clone().into());
107             self.insert_children(InsertPosition::Last, to_insert)
108         }
109     }
110 }
111
112 impl ast::AssocItemList {
113     #[must_use]
114     pub fn append_items(
115         &self,
116         items: impl IntoIterator<Item = ast::AssocItem>,
117     ) -> ast::AssocItemList {
118         let mut res = self.clone();
119         if !self.syntax().text().contains_char('\n') {
120             res = make_multiline(res);
121         }
122         items.into_iter().for_each(|it| res = res.append_item(it));
123         res.fixup_trailing_whitespace().unwrap_or(res)
124     }
125
126     #[must_use]
127     pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList {
128         let (indent, position, whitespace) = match self.assoc_items().last() {
129             Some(it) => (
130                 leading_indent(it.syntax()).unwrap_or_default().to_string(),
131                 InsertPosition::After(it.syntax().clone().into()),
132                 "\n\n",
133             ),
134             None => match self.l_curly_token() {
135                 Some(it) => (
136                     "    ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
137                     InsertPosition::After(it.into()),
138                     "\n",
139                 ),
140                 None => return self.clone(),
141             },
142         };
143         let ws = tokens::WsBuilder::new(&format!("{}{}", whitespace, indent));
144         let to_insert: ArrayVec<[SyntaxElement; 2]> =
145             [ws.ws().into(), item.syntax().clone().into()].into();
146         self.insert_children(position, to_insert)
147     }
148
149     /// Remove extra whitespace between last item and closing curly brace.
150     fn fixup_trailing_whitespace(&self) -> Option<ast::AssocItemList> {
151         let first_token_after_items =
152             self.assoc_items().last()?.syntax().next_sibling_or_token()?;
153         let last_token_before_curly = self.r_curly_token()?.prev_sibling_or_token()?;
154         if last_token_before_curly != first_token_after_items {
155             // there is something more between last item and
156             // right curly than just whitespace - bail out
157             return None;
158         }
159         let whitespace =
160             last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?;
161         let text = whitespace.syntax().text();
162         let newline = text.rfind('\n')?;
163         let keep = tokens::WsBuilder::new(&text[newline..]);
164         Some(self.replace_children(
165             first_token_after_items..=last_token_before_curly,
166             std::iter::once(keep.ws().into()),
167         ))
168     }
169 }
170
171 impl ast::RecordExprFieldList {
172     #[must_use]
173     pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList {
174         self.insert_field(InsertPosition::Last, field)
175     }
176
177     #[must_use]
178     pub fn insert_field(
179         &self,
180         position: InsertPosition<&'_ ast::RecordExprField>,
181         field: &ast::RecordExprField,
182     ) -> ast::RecordExprFieldList {
183         let is_multiline = self.syntax().text().contains_char('\n');
184         let ws;
185         let space = if is_multiline {
186             ws = tokens::WsBuilder::new(&format!(
187                 "\n{}    ",
188                 leading_indent(self.syntax()).unwrap_or_default()
189             ));
190             ws.ws()
191         } else {
192             tokens::single_space()
193         };
194
195         let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
196         to_insert.push(space.into());
197         to_insert.push(field.syntax().clone().into());
198         to_insert.push(make::token(T![,]).into());
199
200         macro_rules! after_l_curly {
201             () => {{
202                 let anchor = match self.l_curly_token() {
203                     Some(it) => it.into(),
204                     None => return self.clone(),
205                 };
206                 InsertPosition::After(anchor)
207             }};
208         }
209
210         macro_rules! after_field {
211             ($anchor:expr) => {
212                 if let Some(comma) = $anchor
213                     .syntax()
214                     .siblings_with_tokens(Direction::Next)
215                     .find(|it| it.kind() == T![,])
216                 {
217                     InsertPosition::After(comma)
218                 } else {
219                     to_insert.insert(0, make::token(T![,]).into());
220                     InsertPosition::After($anchor.syntax().clone().into())
221                 }
222             };
223         }
224
225         let position = match position {
226             InsertPosition::First => after_l_curly!(),
227             InsertPosition::Last => {
228                 if !is_multiline {
229                     // don't insert comma before curly
230                     to_insert.pop();
231                 }
232                 match self.fields().last() {
233                     Some(it) => after_field!(it),
234                     None => after_l_curly!(),
235                 }
236             }
237             InsertPosition::Before(anchor) => {
238                 InsertPosition::Before(anchor.syntax().clone().into())
239             }
240             InsertPosition::After(anchor) => after_field!(anchor),
241         };
242
243         self.insert_children(position, to_insert)
244     }
245 }
246
247 impl ast::TypeAlias {
248     #[must_use]
249     pub fn remove_bounds(&self) -> ast::TypeAlias {
250         let colon = match self.colon_token() {
251             Some(it) => it,
252             None => return self.clone(),
253         };
254         let end = match self.type_bound_list() {
255             Some(it) => it.syntax().clone().into(),
256             None => colon.clone().into(),
257         };
258         self.replace_children(colon.into()..=end, iter::empty())
259     }
260 }
261
262 impl ast::TypeParam {
263     #[must_use]
264     pub fn remove_bounds(&self) -> ast::TypeParam {
265         let colon = match self.colon_token() {
266             Some(it) => it,
267             None => return self.clone(),
268         };
269         let end = match self.type_bound_list() {
270             Some(it) => it.syntax().clone().into(),
271             None => colon.clone().into(),
272         };
273         self.replace_children(colon.into()..=end, iter::empty())
274     }
275 }
276
277 impl ast::Path {
278     #[must_use]
279     pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path {
280         if let Some(old) = self.segment() {
281             return self.replace_children(
282                 single_node(old.syntax().clone()),
283                 iter::once(segment.syntax().clone().into()),
284             );
285         }
286         self.clone()
287     }
288 }
289
290 impl ast::PathSegment {
291     #[must_use]
292     pub fn with_generic_args(&self, type_args: ast::GenericArgList) -> ast::PathSegment {
293         self._with_generic_args(type_args, false)
294     }
295
296     #[must_use]
297     pub fn with_turbo_fish(&self, type_args: ast::GenericArgList) -> ast::PathSegment {
298         self._with_generic_args(type_args, true)
299     }
300
301     fn _with_generic_args(&self, type_args: ast::GenericArgList, turbo: bool) -> ast::PathSegment {
302         if let Some(old) = self.generic_arg_list() {
303             return self.replace_children(
304                 single_node(old.syntax().clone()),
305                 iter::once(type_args.syntax().clone().into()),
306             );
307         }
308         let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
309         if turbo {
310             to_insert.push(make::token(T![::]).into());
311         }
312         to_insert.push(type_args.syntax().clone().into());
313         self.insert_children(InsertPosition::Last, to_insert)
314     }
315 }
316
317 impl ast::Use {
318     #[must_use]
319     pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::Use {
320         if let Some(old) = self.use_tree() {
321             return self.replace_descendant(old, use_tree);
322         }
323         self.clone()
324     }
325
326     pub fn remove(&self) -> SyntaxRewriter<'static> {
327         let mut res = SyntaxRewriter::default();
328         res.delete(self.syntax());
329         let next_ws = self
330             .syntax()
331             .next_sibling_or_token()
332             .and_then(|it| it.into_token())
333             .and_then(ast::Whitespace::cast);
334         if let Some(next_ws) = next_ws {
335             let ws_text = next_ws.syntax().text();
336             if let Some(rest) = ws_text.strip_prefix('\n') {
337                 if rest.is_empty() {
338                     res.delete(next_ws.syntax())
339                 } else {
340                     res.replace(next_ws.syntax(), &make::tokens::whitespace(rest));
341                 }
342             }
343         }
344         res
345     }
346 }
347
348 impl ast::UseTree {
349     #[must_use]
350     pub fn with_path(&self, path: ast::Path) -> ast::UseTree {
351         if let Some(old) = self.path() {
352             return self.replace_descendant(old, path);
353         }
354         self.clone()
355     }
356
357     #[must_use]
358     pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree {
359         if let Some(old) = self.use_tree_list() {
360             return self.replace_descendant(old, use_tree_list);
361         }
362         self.clone()
363     }
364
365     /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items.
366     #[must_use]
367     pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree {
368         let suffix = if self.path().as_ref() == Some(prefix) && self.use_tree_list().is_none() {
369             make::path_unqualified(make::path_segment_self())
370         } else {
371             match split_path_prefix(&prefix) {
372                 Some(it) => it,
373                 None => return self.clone(),
374             }
375         };
376
377         let use_tree = make::use_tree(
378             suffix,
379             self.use_tree_list(),
380             self.rename(),
381             self.star_token().is_some(),
382         );
383         let nested = make::use_tree_list(iter::once(use_tree));
384         return make::use_tree(prefix.clone(), Some(nested), None, false);
385
386         fn split_path_prefix(prefix: &ast::Path) -> Option<ast::Path> {
387             let parent = prefix.parent_path()?;
388             let segment = parent.segment()?;
389             if algo::has_errors(segment.syntax()) {
390                 return None;
391             }
392             let mut res = make::path_unqualified(segment);
393             for p in iter::successors(parent.parent_path(), |it| it.parent_path()) {
394                 res = make::path_qualified(res, p.segment()?);
395             }
396             Some(res)
397         }
398     }
399
400     pub fn remove(&self) -> SyntaxRewriter<'static> {
401         let mut res = SyntaxRewriter::default();
402         res.delete(self.syntax());
403         for &dir in [Direction::Next, Direction::Prev].iter() {
404             if let Some(nb) = neighbor(self, dir) {
405                 self.syntax()
406                     .siblings_with_tokens(dir)
407                     .skip(1)
408                     .take_while(|it| it.as_node() != Some(nb.syntax()))
409                     .for_each(|el| res.delete(&el));
410                 return res;
411             }
412         }
413         res
414     }
415 }
416
417 impl ast::MatchArmList {
418     #[must_use]
419     pub fn append_arms(&self, items: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
420         let mut res = self.clone();
421         res = res.strip_if_only_whitespace();
422         if !res.syntax().text().contains_char('\n') {
423             res = make_multiline(res);
424         }
425         items.into_iter().for_each(|it| res = res.append_arm(it));
426         res
427     }
428
429     fn strip_if_only_whitespace(&self) -> ast::MatchArmList {
430         let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']);
431         iter.next(); // Eat the curly
432         let mut inner = iter.take_while(|it| it.kind() != T!['}']);
433         if !inner.clone().all(|it| it.kind() == WHITESPACE) {
434             return self.clone();
435         }
436         let start = match inner.next() {
437             Some(s) => s,
438             None => return self.clone(),
439         };
440         let end = match inner.last() {
441             Some(s) => s,
442             None => start.clone(),
443         };
444         self.replace_children(start..=end, &mut iter::empty())
445     }
446
447     #[must_use]
448     pub fn remove_placeholder(&self) -> ast::MatchArmList {
449         let placeholder =
450             self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
451         if let Some(placeholder) = placeholder {
452             self.remove_arm(&placeholder)
453         } else {
454             self.clone()
455         }
456     }
457
458     #[must_use]
459     fn remove_arm(&self, arm: &ast::MatchArm) -> ast::MatchArmList {
460         let start = arm.syntax().clone();
461         let end = if let Some(comma) = start
462             .siblings_with_tokens(Direction::Next)
463             .skip(1)
464             .skip_while(|it| it.kind().is_trivia())
465             .next()
466             .filter(|it| it.kind() == T![,])
467         {
468             comma
469         } else {
470             start.clone().into()
471         };
472         self.replace_children(start.into()..=end, None)
473     }
474
475     #[must_use]
476     pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList {
477         let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) {
478             Some(t) => t,
479             None => return self.clone(),
480         };
481         let position = InsertPosition::Before(r_curly);
482         let arm_ws = tokens::WsBuilder::new("    ");
483         let match_indent = &leading_indent(self.syntax()).unwrap_or_default();
484         let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent));
485         let to_insert: ArrayVec<[SyntaxElement; 3]> =
486             [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into();
487         self.insert_children(position, to_insert)
488     }
489 }
490
491 impl ast::GenericParamList {
492     #[must_use]
493     pub fn append_params(
494         &self,
495         params: impl IntoIterator<Item = ast::GenericParam>,
496     ) -> ast::GenericParamList {
497         let mut res = self.clone();
498         params.into_iter().for_each(|it| res = res.append_param(it));
499         res
500     }
501
502     #[must_use]
503     pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList {
504         let space = tokens::single_space();
505
506         let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
507         if self.generic_params().next().is_some() {
508             to_insert.push(space.into());
509         }
510         to_insert.push(item.syntax().clone().into());
511
512         macro_rules! after_l_angle {
513             () => {{
514                 let anchor = match self.l_angle_token() {
515                     Some(it) => it.into(),
516                     None => return self.clone(),
517                 };
518                 InsertPosition::After(anchor)
519             }};
520         }
521
522         macro_rules! after_field {
523             ($anchor:expr) => {
524                 if let Some(comma) = $anchor
525                     .syntax()
526                     .siblings_with_tokens(Direction::Next)
527                     .find(|it| it.kind() == T![,])
528                 {
529                     InsertPosition::After(comma)
530                 } else {
531                     to_insert.insert(0, make::token(T![,]).into());
532                     InsertPosition::After($anchor.syntax().clone().into())
533                 }
534             };
535         }
536
537         let position = match self.generic_params().last() {
538             Some(it) => after_field!(it),
539             None => after_l_angle!(),
540         };
541
542         self.insert_children(position, to_insert)
543     }
544 }
545
546 #[must_use]
547 pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
548     N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
549 }
550
551 fn remove_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode {
552     while let Some(start) =
553         node.children_with_tokens().find(|it| it.kind() == ATTR || it.kind() == COMMENT)
554     {
555         let end = match &start.next_sibling_or_token() {
556             Some(el) if el.kind() == WHITESPACE => el.clone(),
557             Some(_) | None => start.clone(),
558         };
559         node = algo::replace_children(&node, start..=end, &mut iter::empty());
560     }
561     node
562 }
563
564 #[derive(Debug, Clone, Copy)]
565 pub struct IndentLevel(pub u8);
566
567 impl From<u8> for IndentLevel {
568     fn from(level: u8) -> IndentLevel {
569         IndentLevel(level)
570     }
571 }
572
573 impl fmt::Display for IndentLevel {
574     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575         let spaces = "                                        ";
576         let buf;
577         let len = self.0 as usize * 4;
578         let indent = if len <= spaces.len() {
579             &spaces[..len]
580         } else {
581             buf = iter::repeat(' ').take(len).collect::<String>();
582             &buf
583         };
584         fmt::Display::fmt(indent, f)
585     }
586 }
587
588 impl ops::Add<u8> for IndentLevel {
589     type Output = IndentLevel;
590     fn add(self, rhs: u8) -> IndentLevel {
591         IndentLevel(self.0 + rhs)
592     }
593 }
594
595 impl IndentLevel {
596     pub fn from_node(node: &SyntaxNode) -> IndentLevel {
597         match node.first_token() {
598             Some(it) => Self::from_token(&it),
599             None => return IndentLevel(0),
600         }
601     }
602
603     pub fn from_token(token: &SyntaxToken) -> IndentLevel {
604         for ws in prev_tokens(token.clone()).filter_map(ast::Whitespace::cast) {
605             let text = ws.syntax().text();
606             if let Some(pos) = text.rfind('\n') {
607                 let level = text[pos + 1..].chars().count() / 4;
608                 return IndentLevel(level as u8);
609             }
610         }
611         IndentLevel(0)
612     }
613
614     /// XXX: this intentionally doesn't change the indent of the very first token.
615     /// Ie, in something like
616     /// ```
617     /// fn foo() {
618     ///    92
619     /// }
620     /// ```
621     /// if you indent the block, the `{` token would stay put.
622     fn increase_indent(self, node: SyntaxNode) -> SyntaxNode {
623         let mut rewriter = SyntaxRewriter::default();
624         node.descendants_with_tokens()
625             .filter_map(|el| el.into_token())
626             .filter_map(ast::Whitespace::cast)
627             .filter(|ws| {
628                 let text = ws.syntax().text();
629                 text.contains('\n')
630             })
631             .for_each(|ws| {
632                 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,));
633                 rewriter.replace(ws.syntax(), &new_ws)
634             });
635         rewriter.rewrite(&node)
636     }
637
638     fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
639         let mut rewriter = SyntaxRewriter::default();
640         node.descendants_with_tokens()
641             .filter_map(|el| el.into_token())
642             .filter_map(ast::Whitespace::cast)
643             .filter(|ws| {
644                 let text = ws.syntax().text();
645                 text.contains('\n')
646             })
647             .for_each(|ws| {
648                 let new_ws = make::tokens::whitespace(
649                     &ws.syntax().text().replace(&format!("\n{}", self), "\n"),
650                 );
651                 rewriter.replace(ws.syntax(), &new_ws)
652             });
653         rewriter.rewrite(&node)
654     }
655 }
656
657 // FIXME: replace usages with IndentLevel above
658 fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
659     for token in prev_tokens(node.first_token()?) {
660         if let Some(ws) = ast::Whitespace::cast(token.clone()) {
661             let ws_text = ws.text();
662             if let Some(pos) = ws_text.rfind('\n') {
663                 return Some(ws_text[pos + 1..].into());
664             }
665         }
666         if token.text().contains('\n') {
667             break;
668         }
669     }
670     None
671 }
672
673 fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
674     iter::successors(Some(token), |token| token.prev_token())
675 }
676
677 pub trait AstNodeEdit: AstNode + Clone + Sized {
678     #[must_use]
679     fn insert_children(
680         &self,
681         position: InsertPosition<SyntaxElement>,
682         to_insert: impl IntoIterator<Item = SyntaxElement>,
683     ) -> Self {
684         let new_syntax = algo::insert_children(self.syntax(), position, to_insert);
685         Self::cast(new_syntax).unwrap()
686     }
687
688     #[must_use]
689     fn replace_children(
690         &self,
691         to_replace: RangeInclusive<SyntaxElement>,
692         to_insert: impl IntoIterator<Item = SyntaxElement>,
693     ) -> Self {
694         let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert);
695         Self::cast(new_syntax).unwrap()
696     }
697
698     #[must_use]
699     fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self {
700         self.replace_descendants(iter::once((old, new)))
701     }
702
703     #[must_use]
704     fn replace_descendants<D: AstNode>(
705         &self,
706         replacement_map: impl IntoIterator<Item = (D, D)>,
707     ) -> Self {
708         let mut rewriter = SyntaxRewriter::default();
709         for (from, to) in replacement_map {
710             rewriter.replace(from.syntax(), to.syntax())
711         }
712         rewriter.rewrite_ast(self)
713     }
714     fn indent_level(&self) -> IndentLevel {
715         IndentLevel::from_node(self.syntax())
716     }
717     #[must_use]
718     fn indent(&self, level: IndentLevel) -> Self {
719         Self::cast(level.increase_indent(self.syntax().clone())).unwrap()
720     }
721     #[must_use]
722     fn dedent(&self, level: IndentLevel) -> Self {
723         Self::cast(level.decrease_indent(self.syntax().clone())).unwrap()
724     }
725     #[must_use]
726     fn reset_indent(&self) -> Self {
727         let level = IndentLevel::from_node(self.syntax());
728         self.dedent(level)
729     }
730 }
731
732 impl<N: AstNode + Clone> AstNodeEdit for N {}
733
734 fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElement> {
735     let element = element.into();
736     element.clone()..=element
737 }
738
739 #[test]
740 fn test_increase_indent() {
741     let arm_list = {
742         let arm = make::match_arm(iter::once(make::wildcard_pat().into()), make::expr_unit());
743         make::match_arm_list(vec![arm.clone(), arm])
744     };
745     assert_eq!(
746         arm_list.syntax().to_string(),
747         "{
748     _ => (),
749     _ => (),
750 }"
751     );
752     let indented = arm_list.indent(IndentLevel(2));
753     assert_eq!(
754         indented.syntax().to_string(),
755         "{
756             _ => (),
757             _ => (),
758         }"
759     );
760 }