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