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