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.
6 ops::{self, RangeInclusive},
9 use arrayvec::ArrayVec;
12 algo::{self, neighbor, SyntaxRewriter},
16 AstNode, GenericParamsOwner, NameOwner, TypeBoundsOwner,
18 AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind,
19 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
20 SyntaxNode, SyntaxToken, T,
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))
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());
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);
46 to_insert.push(body.syntax().clone().into());
47 self.replace_children(single_node(old_body_or_semi), to_insert)
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);
56 let anchor = self.name().expect("The function must have a name").syntax().clone();
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)
64 fn make_multiline<N>(node: N) -> N
68 let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
72 let sibling = match l_curly.next_sibling_or_token() {
76 let existing_ws = match sibling.as_token() {
78 Some(tok) if tok.kind() != WHITESPACE => None,
80 if ws.text().contains('\n') {
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());
91 None => node.insert_children(InsertPosition::After(l_curly), to_insert),
92 Some(ws) => node.replace_children(single_node(ws), to_insert),
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)
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)
112 impl ast::AssocItemList {
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);
122 items.into_iter().for_each(|it| res = res.append_item(it));
123 res.fixup_trailing_whitespace().unwrap_or(res)
127 pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList {
128 let (indent, position, whitespace) = match self.assoc_items().last() {
130 leading_indent(it.syntax()).unwrap_or_default().to_string(),
131 InsertPosition::After(it.syntax().clone().into()),
134 None => match self.l_curly_token() {
136 " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
137 InsertPosition::After(it.into()),
140 None => return self.clone(),
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)
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
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()),
171 impl ast::RecordExprFieldList {
173 pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList {
174 self.insert_field(InsertPosition::Last, field)
180 position: InsertPosition<&'_ ast::RecordExprField>,
181 field: &ast::RecordExprField,
182 ) -> ast::RecordExprFieldList {
183 let is_multiline = self.syntax().text().contains_char('\n');
185 let space = if is_multiline {
186 ws = tokens::WsBuilder::new(&format!(
188 leading_indent(self.syntax()).unwrap_or_default()
192 tokens::single_space()
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());
200 macro_rules! after_l_curly {
202 let anchor = match self.l_curly_token() {
203 Some(it) => it.into(),
204 None => return self.clone(),
206 InsertPosition::After(anchor)
210 macro_rules! after_field {
212 if let Some(comma) = $anchor
214 .siblings_with_tokens(Direction::Next)
215 .find(|it| it.kind() == T![,])
217 InsertPosition::After(comma)
219 to_insert.insert(0, make::token(T![,]).into());
220 InsertPosition::After($anchor.syntax().clone().into())
225 let position = match position {
226 InsertPosition::First => after_l_curly!(),
227 InsertPosition::Last => {
229 // don't insert comma before curly
232 match self.fields().last() {
233 Some(it) => after_field!(it),
234 None => after_l_curly!(),
237 InsertPosition::Before(anchor) => {
238 InsertPosition::Before(anchor.syntax().clone().into())
240 InsertPosition::After(anchor) => after_field!(anchor),
243 self.insert_children(position, to_insert)
247 impl ast::TypeAlias {
249 pub fn remove_bounds(&self) -> ast::TypeAlias {
250 let colon = match self.colon_token() {
252 None => return self.clone(),
254 let end = match self.type_bound_list() {
255 Some(it) => it.syntax().clone().into(),
256 None => colon.clone().into(),
258 self.replace_children(colon.into()..=end, iter::empty())
262 impl ast::TypeParam {
264 pub fn remove_bounds(&self) -> ast::TypeParam {
265 let colon = match self.colon_token() {
267 None => return self.clone(),
269 let end = match self.type_bound_list() {
270 Some(it) => it.syntax().clone().into(),
271 None => colon.clone().into(),
273 self.replace_children(colon.into()..=end, iter::empty())
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()),
290 impl ast::PathSegment {
292 pub fn with_generic_args(&self, type_args: ast::GenericArgList) -> ast::PathSegment {
293 self._with_generic_args(type_args, false)
297 pub fn with_turbo_fish(&self, type_args: ast::GenericArgList) -> ast::PathSegment {
298 self._with_generic_args(type_args, true)
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()),
308 let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
310 to_insert.push(make::token(T![::]).into());
312 to_insert.push(type_args.syntax().clone().into());
313 self.insert_children(InsertPosition::Last, to_insert)
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);
326 pub fn remove(&self) -> SyntaxRewriter<'static> {
327 let mut res = SyntaxRewriter::default();
328 res.delete(self.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') {
338 res.delete(next_ws.syntax())
340 res.replace(next_ws.syntax(), &make::tokens::whitespace(rest));
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);
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);
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.
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())
371 match split_path_prefix(&prefix) {
373 None => return self.clone(),
377 let use_tree = make::use_tree(
379 self.use_tree_list(),
381 self.star_token().is_some(),
383 let nested = make::use_tree_list(iter::once(use_tree));
384 return make::use_tree(prefix.clone(), Some(nested), None, false);
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()) {
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()?);
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) {
406 .siblings_with_tokens(dir)
408 .take_while(|it| it.as_node() != Some(nb.syntax()))
409 .for_each(|el| res.delete(&el));
417 impl ast::MatchArmList {
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);
425 items.into_iter().for_each(|it| res = res.append_arm(it));
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) {
436 let start = match inner.next() {
438 None => return self.clone(),
440 let end = match inner.last() {
442 None => start.clone(),
444 self.replace_children(start..=end, &mut iter::empty())
448 pub fn remove_placeholder(&self) -> ast::MatchArmList {
450 self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
451 if let Some(placeholder) = placeholder {
452 self.remove_arm(&placeholder)
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)
464 .skip_while(|it| it.kind().is_trivia())
466 .filter(|it| it.kind() == T![,])
472 self.replace_children(start.into()..=end, None)
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!['}']) {
479 None => return self.clone(),
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)
491 impl ast::GenericParamList {
493 pub fn append_params(
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));
503 pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList {
504 let space = tokens::single_space();
506 let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
507 if self.generic_params().next().is_some() {
508 to_insert.push(space.into());
510 to_insert.push(item.syntax().clone().into());
512 macro_rules! after_l_angle {
514 let anchor = match self.l_angle_token() {
515 Some(it) => it.into(),
516 None => return self.clone(),
518 InsertPosition::After(anchor)
522 macro_rules! after_field {
524 if let Some(comma) = $anchor
526 .siblings_with_tokens(Direction::Next)
527 .find(|it| it.kind() == T![,])
529 InsertPosition::After(comma)
531 to_insert.insert(0, make::token(T![,]).into());
532 InsertPosition::After($anchor.syntax().clone().into())
537 let position = match self.generic_params().last() {
538 Some(it) => after_field!(it),
539 None => after_l_angle!(),
542 self.insert_children(position, to_insert)
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()
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)
555 let end = match &start.next_sibling_or_token() {
556 Some(el) if el.kind() == WHITESPACE => el.clone(),
557 Some(_) | None => start.clone(),
559 node = algo::replace_children(&node, start..=end, &mut iter::empty());
564 #[derive(Debug, Clone, Copy)]
565 pub struct IndentLevel(pub u8);
567 impl From<u8> for IndentLevel {
568 fn from(level: u8) -> IndentLevel {
573 impl fmt::Display for IndentLevel {
574 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577 let len = self.0 as usize * 4;
578 let indent = if len <= spaces.len() {
581 buf = iter::repeat(' ').take(len).collect::<String>();
584 fmt::Display::fmt(indent, f)
588 impl ops::Add<u8> for IndentLevel {
589 type Output = IndentLevel;
590 fn add(self, rhs: u8) -> IndentLevel {
591 IndentLevel(self.0 + rhs)
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),
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);
614 /// XXX: this intentionally doesn't change the indent of the very first token.
615 /// Ie, in something like
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)
628 let text = ws.syntax().text();
632 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,));
633 rewriter.replace(ws.syntax(), &new_ws)
635 rewriter.rewrite(&node)
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)
644 let text = ws.syntax().text();
648 let new_ws = make::tokens::whitespace(
649 &ws.syntax().text().replace(&format!("\n{}", self), "\n"),
651 rewriter.replace(ws.syntax(), &new_ws)
653 rewriter.rewrite(&node)
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());
666 if token.text().contains('\n') {
673 fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
674 iter::successors(Some(token), |token| token.prev_token())
677 pub trait AstNodeEdit: AstNode + Clone + Sized {
681 position: InsertPosition<SyntaxElement>,
682 to_insert: impl IntoIterator<Item = SyntaxElement>,
684 let new_syntax = algo::insert_children(self.syntax(), position, to_insert);
685 Self::cast(new_syntax).unwrap()
691 to_replace: RangeInclusive<SyntaxElement>,
692 to_insert: impl IntoIterator<Item = SyntaxElement>,
694 let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert);
695 Self::cast(new_syntax).unwrap()
699 fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self {
700 self.replace_descendants(iter::once((old, new)))
704 fn replace_descendants<D: AstNode>(
706 replacement_map: impl IntoIterator<Item = (D, D)>,
708 let mut rewriter = SyntaxRewriter::default();
709 for (from, to) in replacement_map {
710 rewriter.replace(from.syntax(), to.syntax())
712 rewriter.rewrite_ast(self)
714 fn indent_level(&self) -> IndentLevel {
715 IndentLevel::from_node(self.syntax())
718 fn indent(&self, level: IndentLevel) -> Self {
719 Self::cast(level.increase_indent(self.syntax().clone())).unwrap()
722 fn dedent(&self, level: IndentLevel) -> Self {
723 Self::cast(level.decrease_indent(self.syntax().clone())).unwrap()
726 fn reset_indent(&self) -> Self {
727 let level = IndentLevel::from_node(self.syntax());
732 impl<N: AstNode + Clone> AstNodeEdit for N {}
734 fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElement> {
735 let element = element.into();
736 element.clone()..=element
740 fn test_increase_indent() {
742 let arm = make::match_arm(iter::once(make::wildcard_pat().into()), make::expr_unit());
743 make::match_arm_list(vec![arm.clone(), arm])
746 arm_list.syntax().to_string(),
752 let indented = arm_list.indent(IndentLevel(2));
754 indented.syntax().to_string(),