| //! Syntax Tree editor |
| //! |
| //! Inspired by Roslyn's [`SyntaxEditor`], but is temporarily built upon mutable syntax tree editing. |
| //! |
| //! [`SyntaxEditor`]: https://github.com/dotnet/roslyn/blob/43b0b05cc4f492fd5de00f6f6717409091df8daa/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs |
| |
| use std::{ |
| cell::RefCell, |
| fmt, iter, |
| num::NonZeroU32, |
| ops::RangeInclusive, |
| sync::atomic::{AtomicU32, Ordering}, |
| }; |
| |
| use rowan::TextRange; |
| use rustc_hash::FxHashMap; |
| |
| use crate::{ |
| AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T, |
| ast::{self, edit::IndentLevel, syntax_factory::SyntaxFactory}, |
| }; |
| |
| mod edit_algo; |
| mod edits; |
| mod mapping; |
| |
| pub use edits::{GetOrCreateWhereClause, Removable}; |
| pub use mapping::{SyntaxMapping, SyntaxMappingBuilder}; |
| |
| #[derive(Debug)] |
| pub struct SyntaxEditor { |
| root: SyntaxNode, |
| changes: RefCell<Vec<Change>>, |
| annotations: RefCell<Vec<(SyntaxElement, SyntaxAnnotation)>>, |
| make: SyntaxFactory, |
| } |
| |
| impl SyntaxEditor { |
| /// Creates a syntax editor from `root`. |
| /// |
| /// The returned `root` is guaranteed to be a detached, immutable node. |
| /// If the provided node is not a root (i.e., has a parent) or is already |
| /// mutable, it is cloned into a fresh subtree to satisfy syntax editor |
| /// invariants. |
| pub fn new(root: SyntaxNode) -> (Self, SyntaxNode) { |
| let mut root = root; |
| |
| if root.parent().is_some() || root.is_mutable() { |
| root = root.clone_subtree() |
| }; |
| |
| let editor = Self { |
| root: root.clone(), |
| changes: RefCell::new(Vec::new()), |
| annotations: RefCell::new(Vec::new()), |
| make: SyntaxFactory::with_mappings(), |
| }; |
| |
| (editor, root) |
| } |
| |
| /// Typed-node variant of [`SyntaxEditor::new`]. |
| pub fn with_ast_node<T>(root: &T) -> (Self, T) |
| where |
| T: AstNode, |
| { |
| let (editor, root) = Self::new(root.syntax().clone()); |
| |
| (editor, T::cast(root).unwrap()) |
| } |
| |
| pub fn make(&self) -> &SyntaxFactory { |
| &self.make |
| } |
| |
| pub fn add_annotation(&self, element: impl Element, annotation: SyntaxAnnotation) { |
| self.annotations.borrow_mut().push((element.syntax_element(), annotation)) |
| } |
| |
| pub fn add_annotation_all(&self, elements: Vec<impl Element>, annotation: SyntaxAnnotation) { |
| self.annotations |
| .borrow_mut() |
| .extend(elements.into_iter().map(|e| e.syntax_element()).zip(iter::repeat(annotation))); |
| } |
| |
| pub fn merge(&self, other: SyntaxEditor) { |
| debug_assert!( |
| self.root == other.root || other.root.ancestors().any(|node| node == self.root), |
| "{:?} is not in the same tree as {:?}", |
| other.root, |
| self.root |
| ); |
| |
| self.changes.borrow_mut().append(&mut other.changes.into_inner()); |
| if let Some(mut m) = self.make.mappings() { |
| m.merge(other.make.take()); |
| } |
| self.annotations.borrow_mut().append(&mut other.annotations.into_inner()); |
| } |
| |
| pub fn insert(&self, position: Position, element: impl Element) { |
| debug_assert!(is_ancestor_or_self(&position.parent(), &self.root)); |
| self.changes.borrow_mut().push(Change::Insert(position, element.syntax_element())) |
| } |
| |
| pub fn insert_all(&self, position: Position, elements: Vec<SyntaxElement>) { |
| debug_assert!(is_ancestor_or_self(&position.parent(), &self.root)); |
| self.changes.borrow_mut().push(Change::InsertAll(position, elements)) |
| } |
| |
| pub fn insert_with_whitespace(&self, position: Position, element: impl Element) { |
| self.insert_all_with_whitespace(position, vec![element.syntax_element()]) |
| } |
| |
| pub fn insert_all_with_whitespace(&self, position: Position, mut elements: Vec<SyntaxElement>) { |
| if let Some(first) = elements.first() |
| && let Some(ws) = ws_before(&position, first, &self.make) |
| { |
| elements.insert(0, ws.into()); |
| } |
| if let Some(last) = elements.last() |
| && let Some(ws) = ws_after(&position, last, &self.make) |
| { |
| elements.push(ws.into()); |
| } |
| self.insert_all(position, elements) |
| } |
| |
| pub fn delete(&self, element: impl Element) { |
| let element = element.syntax_element(); |
| debug_assert!(is_ancestor_or_self_of_element(&element, &self.root)); |
| debug_assert!( |
| !matches!(&element, SyntaxElement::Node(node) if node == &self.root), |
| "should not delete root node" |
| ); |
| let mut changes = self.changes.borrow_mut(); |
| for change in changes.iter_mut() { |
| if let Change::Replace(existing, replacement) = change |
| && *existing == element |
| { |
| if replacement.is_none() { |
| return; |
| } |
| *replacement = None; |
| return; |
| } |
| } |
| changes.push(Change::Replace(element, None)); |
| } |
| |
| pub fn delete_all(&self, range: RangeInclusive<SyntaxElement>) { |
| if range.start() == range.end() { |
| self.delete(range.start()); |
| return; |
| } |
| |
| debug_assert!(is_ancestor_or_self_of_element(range.start(), &self.root)); |
| self.changes.borrow_mut().push(Change::ReplaceAll(range, Vec::new())) |
| } |
| |
| pub fn replace(&self, old: impl Element, new: impl Element) { |
| let old = old.syntax_element(); |
| debug_assert!(is_ancestor_or_self_of_element(&old, &self.root)); |
| let new = new.syntax_element(); |
| let mut changes = self.changes.borrow_mut(); |
| for change in changes.iter_mut() { |
| if let Change::Replace(existing, replacement) = change |
| && *existing == old |
| { |
| match replacement { |
| None => return, |
| Some(existing_new) if *existing_new == new => return, |
| Some(existing_new) => { |
| *existing_new = new; |
| return; |
| } |
| } |
| } |
| } |
| changes.push(Change::Replace(old, Some(new))); |
| } |
| |
| pub fn replace_with_many(&self, old: impl Element, new: Vec<SyntaxElement>) { |
| let old = old.syntax_element(); |
| debug_assert!(is_ancestor_or_self_of_element(&old, &self.root)); |
| debug_assert!( |
| !(matches!(&old, SyntaxElement::Node(node) if node == &self.root) && new.len() > 1), |
| "cannot replace root node with many elements" |
| ); |
| self.changes.borrow_mut().push(Change::ReplaceWithMany(old.syntax_element(), new)); |
| } |
| |
| pub fn replace_all(&self, range: RangeInclusive<SyntaxElement>, new: Vec<SyntaxElement>) { |
| if range.start() == range.end() { |
| self.replace_with_many(range.start(), new); |
| return; |
| } |
| |
| debug_assert!(is_ancestor_or_self_of_element(range.start(), &self.root)); |
| self.changes.borrow_mut().push(Change::ReplaceAll(range, new)) |
| } |
| |
| pub fn finish(self) -> SyntaxEdit { |
| edit_algo::apply_edits(self) |
| } |
| |
| pub fn deleted(&self, element: impl Element) -> bool { |
| let element = element.syntax_element(); |
| self.changes |
| .borrow() |
| .iter() |
| .any(|change| matches!(change, Change::Replace(existing, None) if *existing == element)) |
| } |
| } |
| |
| /// Represents a completed [`SyntaxEditor`] operation. |
| pub struct SyntaxEdit { |
| old_root: SyntaxNode, |
| new_root: SyntaxNode, |
| changed_elements: Vec<SyntaxElement>, |
| annotations: FxHashMap<SyntaxAnnotation, Vec<SyntaxElement>>, |
| } |
| |
| impl SyntaxEdit { |
| /// Root of the initial unmodified syntax tree. |
| pub fn old_root(&self) -> &SyntaxNode { |
| &self.old_root |
| } |
| |
| /// Root of the modified syntax tree. |
| pub fn new_root(&self) -> &SyntaxNode { |
| &self.new_root |
| } |
| |
| /// Which syntax elements in the modified syntax tree were inserted or |
| /// modified as part of the edit. |
| /// |
| /// Note that for syntax nodes, only the upper-most parent of a set of |
| /// changes is included, not any child elements that may have been modified. |
| pub fn changed_elements(&self) -> &[SyntaxElement] { |
| self.changed_elements.as_slice() |
| } |
| |
| /// Finds which syntax elements have been annotated with the given |
| /// annotation. |
| /// |
| /// Note that an annotation might not appear in the modified syntax tree if |
| /// the syntax elements that were annotated did not make it into the final |
| /// syntax tree. |
| pub fn find_annotation(&self, annotation: SyntaxAnnotation) -> &[SyntaxElement] { |
| self.annotations.get(&annotation).as_ref().map_or(&[], |it| it.as_slice()) |
| } |
| |
| pub fn find_element(&self, old_node: &SyntaxNode) -> Option<SyntaxNode> { |
| let old_root_start = self.old_root.text_range().start(); |
| let old_start = old_node.text_range().start() - old_root_start; |
| let new_root_start = self.new_root.text_range().start(); |
| let kind = old_node.kind(); |
| |
| self.new_root |
| .descendants() |
| .find(|it| it.kind() == kind && it.text_range().start() - new_root_start == old_start) |
| } |
| } |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| #[repr(transparent)] |
| pub struct SyntaxAnnotation(NonZeroU32); |
| |
| impl Default for SyntaxAnnotation { |
| fn default() -> Self { |
| static COUNTER: AtomicU32 = AtomicU32::new(1); |
| |
| // Only consistency within a thread matters, as SyntaxElements are !Send |
| let id = COUNTER.fetch_add(1, Ordering::Relaxed); |
| |
| Self(NonZeroU32::new(id).expect("syntax annotation id overflow")) |
| } |
| } |
| |
| /// Position describing where to insert elements |
| #[derive(Debug)] |
| pub struct Position { |
| repr: PositionRepr, |
| } |
| |
| impl Position { |
| pub(crate) fn parent(&self) -> SyntaxNode { |
| self.place().0 |
| } |
| |
| pub(crate) fn place(&self) -> (SyntaxNode, usize) { |
| match &self.repr { |
| PositionRepr::FirstChild(parent) => (parent.clone(), 0), |
| PositionRepr::After(child) => (child.parent().unwrap(), child.index() + 1), |
| } |
| } |
| } |
| |
| #[derive(Debug)] |
| enum PositionRepr { |
| FirstChild(SyntaxNode), |
| After(SyntaxElement), |
| } |
| |
| impl Position { |
| pub fn after(elem: impl Element) -> Position { |
| let repr = PositionRepr::After(elem.syntax_element()); |
| Position { repr } |
| } |
| |
| pub fn before(elem: impl Element) -> Position { |
| let elem = elem.syntax_element(); |
| let repr = match elem.prev_sibling_or_token() { |
| Some(it) => PositionRepr::After(it), |
| None => PositionRepr::FirstChild(elem.parent().unwrap()), |
| }; |
| Position { repr } |
| } |
| |
| pub fn first_child_of(node: &(impl Into<SyntaxNode> + Clone)) -> Position { |
| let repr = PositionRepr::FirstChild(node.clone().into()); |
| Position { repr } |
| } |
| |
| pub fn last_child_of(node: &(impl Into<SyntaxNode> + Clone)) -> Position { |
| let node = node.clone().into(); |
| let repr = match node.last_child_or_token() { |
| Some(it) => PositionRepr::After(it), |
| None => PositionRepr::FirstChild(node), |
| }; |
| Position { repr } |
| } |
| } |
| |
| #[derive(Debug)] |
| enum Change { |
| /// Inserts a single element at the specified position. |
| Insert(Position, SyntaxElement), |
| /// Inserts many elements in-order at the specified position. |
| InsertAll(Position, Vec<SyntaxElement>), |
| /// Represents both a replace single element and a delete element operation. |
| Replace(SyntaxElement, Option<SyntaxElement>), |
| /// Replaces a single element with many elements. |
| ReplaceWithMany(SyntaxElement, Vec<SyntaxElement>), |
| /// Replaces a range of elements with another list of elements. |
| /// Range will always have start != end. |
| ReplaceAll(RangeInclusive<SyntaxElement>, Vec<SyntaxElement>), |
| } |
| |
| impl Change { |
| fn target_range(&self) -> TextRange { |
| match self { |
| Change::Insert(target, _) | Change::InsertAll(target, _) => match &target.repr { |
| PositionRepr::FirstChild(parent) => TextRange::at( |
| parent.first_child_or_token().unwrap().text_range().start(), |
| 0.into(), |
| ), |
| PositionRepr::After(child) => TextRange::at(child.text_range().end(), 0.into()), |
| }, |
| Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => target.text_range(), |
| Change::ReplaceAll(range, _) => { |
| range.start().text_range().cover(range.end().text_range()) |
| } |
| } |
| } |
| |
| fn target_parent(&self) -> SyntaxNode { |
| match self { |
| Change::Insert(target, _) | Change::InsertAll(target, _) => target.parent(), |
| Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => match target { |
| SyntaxElement::Node(target) => target.parent().unwrap_or_else(|| target.clone()), |
| SyntaxElement::Token(target) => target.parent().unwrap(), |
| }, |
| Change::ReplaceAll(target, _) => target.start().parent().unwrap(), |
| } |
| } |
| |
| fn change_kind(&self) -> ChangeKind { |
| match self { |
| Change::Insert(_, _) | Change::InsertAll(_, _) => ChangeKind::Insert, |
| Change::Replace(_, _) | Change::ReplaceWithMany(_, _) => ChangeKind::Replace, |
| Change::ReplaceAll(_, _) => ChangeKind::ReplaceRange, |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| enum ChangeKind { |
| Insert, |
| ReplaceRange, |
| Replace, |
| } |
| |
| impl fmt::Display for Change { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| Change::Insert(position, node_or_token) => { |
| let parent = position.parent(); |
| let mut parent_str = parent.to_string(); |
| let target_range = self.target_range().start() - parent.text_range().start(); |
| |
| parent_str.insert_str( |
| target_range.into(), |
| &format!("\x1b[42m{node_or_token}\x1b[0m\x1b[K"), |
| ); |
| f.write_str(&parent_str) |
| } |
| Change::InsertAll(position, vec) => { |
| let parent = position.parent(); |
| let mut parent_str = parent.to_string(); |
| let target_range = self.target_range().start() - parent.text_range().start(); |
| let insertion: String = vec.iter().map(|it| it.to_string()).collect(); |
| |
| parent_str |
| .insert_str(target_range.into(), &format!("\x1b[42m{insertion}\x1b[0m\x1b[K")); |
| f.write_str(&parent_str) |
| } |
| Change::Replace(old, new) => { |
| if let Some(new) = new { |
| write!(f, "\x1b[41m{old}\x1b[42m{new}\x1b[0m\x1b[K") |
| } else { |
| write!(f, "\x1b[41m{old}\x1b[0m\x1b[K") |
| } |
| } |
| Change::ReplaceWithMany(old, vec) => { |
| let new: String = vec.iter().map(|it| it.to_string()).collect(); |
| write!(f, "\x1b[41m{old}\x1b[42m{new}\x1b[0m\x1b[K") |
| } |
| Change::ReplaceAll(range, vec) => { |
| let parent = range.start().parent().unwrap(); |
| let parent_str = parent.to_string(); |
| let pre_range = |
| TextRange::new(parent.text_range().start(), range.start().text_range().start()); |
| let old_range = TextRange::new( |
| range.start().text_range().start(), |
| range.end().text_range().end(), |
| ); |
| let post_range = |
| TextRange::new(range.end().text_range().end(), parent.text_range().end()); |
| |
| let pre_str = &parent_str[pre_range - parent.text_range().start()]; |
| let old_str = &parent_str[old_range - parent.text_range().start()]; |
| let post_str = &parent_str[post_range - parent.text_range().start()]; |
| let new: String = vec.iter().map(|it| it.to_string()).collect(); |
| |
| write!(f, "{pre_str}\x1b[41m{old_str}\x1b[42m{new}\x1b[0m\x1b[K{post_str}") |
| } |
| } |
| } |
| } |
| |
| /// Utility trait to allow calling syntax editor functions with references or owned |
| /// nodes. Do not use outside of this module. |
| pub trait Element { |
| fn syntax_element(self) -> SyntaxElement; |
| } |
| |
| impl<E: Element + Clone> Element for &'_ E { |
| fn syntax_element(self) -> SyntaxElement { |
| self.clone().syntax_element() |
| } |
| } |
| |
| impl Element for SyntaxElement { |
| fn syntax_element(self) -> SyntaxElement { |
| self |
| } |
| } |
| |
| impl Element for SyntaxNode { |
| fn syntax_element(self) -> SyntaxElement { |
| self.into() |
| } |
| } |
| |
| impl Element for SyntaxToken { |
| fn syntax_element(self) -> SyntaxElement { |
| self.into() |
| } |
| } |
| |
| fn ws_before( |
| position: &Position, |
| new: &SyntaxElement, |
| factory: &SyntaxFactory, |
| ) -> Option<SyntaxToken> { |
| let prev = match &position.repr { |
| PositionRepr::FirstChild(_) => return None, |
| PositionRepr::After(it) => it, |
| }; |
| |
| if prev.kind() == T!['{'] |
| && new.kind() == SyntaxKind::USE |
| && let Some(item_list) = prev.parent().and_then(ast::ItemList::cast) |
| { |
| let mut indent = IndentLevel::from_element(&item_list.syntax().clone().into()); |
| indent.0 += 1; |
| return Some(factory.whitespace(&format!("\n{indent}"))); |
| } |
| |
| if prev.kind() == T!['{'] |
| && ast::Stmt::can_cast(new.kind()) |
| && let Some(stmt_list) = prev.parent().and_then(ast::StmtList::cast) |
| { |
| let mut indent = IndentLevel::from_element(&stmt_list.syntax().clone().into()); |
| indent.0 += 1; |
| return Some(factory.whitespace(&format!("\n{indent}"))); |
| } |
| |
| ws_between(prev, new, factory) |
| } |
| |
| fn ws_after( |
| position: &Position, |
| new: &SyntaxElement, |
| factory: &SyntaxFactory, |
| ) -> Option<SyntaxToken> { |
| let next = match &position.repr { |
| PositionRepr::FirstChild(parent) => parent.first_child_or_token()?, |
| PositionRepr::After(sibling) => sibling.next_sibling_or_token()?, |
| }; |
| ws_between(new, &next, factory) |
| } |
| |
| fn ws_between( |
| left: &SyntaxElement, |
| right: &SyntaxElement, |
| factory: &SyntaxFactory, |
| ) -> Option<SyntaxToken> { |
| if left.kind() == SyntaxKind::WHITESPACE || right.kind() == SyntaxKind::WHITESPACE { |
| return None; |
| } |
| if right.kind() == T![;] || right.kind() == T![,] { |
| return None; |
| } |
| if left.kind() == T![<] || right.kind() == T![>] { |
| return None; |
| } |
| if left.kind() == T![&] && right.kind() == SyntaxKind::LIFETIME { |
| return None; |
| } |
| if right.kind() == SyntaxKind::GENERIC_ARG_LIST { |
| return None; |
| } |
| if right.kind() == SyntaxKind::USE { |
| let mut indent = IndentLevel::from_element(left); |
| if left.kind() == SyntaxKind::USE { |
| indent.0 = IndentLevel::from_element(right).0.max(indent.0); |
| } |
| return Some(factory.whitespace(&format!("\n{indent}"))); |
| } |
| if left.kind() == SyntaxKind::ATTR { |
| let mut indent = IndentLevel::from_element(right); |
| if right.kind() == SyntaxKind::ATTR { |
| indent.0 = IndentLevel::from_element(left).0.max(indent.0); |
| } |
| return Some(factory.whitespace(&format!("\n{indent}"))); |
| } |
| Some(factory.whitespace(" ")) |
| } |
| |
| fn is_ancestor_or_self(node: &SyntaxNode, ancestor: &SyntaxNode) -> bool { |
| node == ancestor || node.ancestors().any(|it| &it == ancestor) |
| } |
| |
| fn is_ancestor_or_self_of_element(node: &SyntaxElement, ancestor: &SyntaxNode) -> bool { |
| matches!(node, SyntaxElement::Node(node) if node == ancestor) |
| || node.ancestors().any(|it| &it == ancestor) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use expect_test::expect; |
| |
| use crate::{ |
| AstNode, |
| ast::{self, make}, |
| }; |
| |
| use super::*; |
| |
| #[test] |
| fn basic_usage() { |
| let root = make::match_arm( |
| make::wildcard_pat().into(), |
| None, |
| make::expr_tuple([ |
| make::expr_bin_op( |
| make::expr_literal("2").into(), |
| ast::BinaryOp::ArithOp(ast::ArithOp::Add), |
| make::expr_literal("2").into(), |
| ), |
| make::expr_literal("true").into(), |
| ]) |
| .into(), |
| ); |
| |
| let (editor, root) = SyntaxEditor::with_ast_node(&root); |
| let make = editor.make(); |
| |
| let to_wrap = root.syntax().descendants().find_map(ast::TupleExpr::cast).unwrap(); |
| let to_replace = root.syntax().descendants().find_map(ast::BinExpr::cast).unwrap(); |
| |
| let name = make::name("var_name"); |
| let name_ref = make::name_ref("var_name").clone_for_update(); |
| |
| let placeholder_snippet = SyntaxAnnotation::default(); |
| editor.add_annotation(name.syntax(), placeholder_snippet); |
| editor.add_annotation(name_ref.syntax(), placeholder_snippet); |
| |
| let new_block = make.block_expr( |
| [editor |
| .make() |
| .let_stmt( |
| make.ident_pat(false, false, name.clone()).into(), |
| None, |
| Some(to_replace.clone().into()), |
| ) |
| .into()], |
| Some(to_wrap.clone().into()), |
| ); |
| |
| editor.replace(to_replace.syntax(), name_ref.syntax()); |
| editor.replace(to_wrap.syntax(), new_block.syntax()); |
| |
| let edit = editor.finish(); |
| |
| let expect = expect![[r#" |
| _ => { |
| let var_name = 2 + 2; |
| (var_name, true) |
| },"#]]; |
| expect.assert_eq(&edit.new_root.to_string()); |
| |
| assert_eq!(edit.find_annotation(placeholder_snippet).len(), 2); |
| assert!( |
| edit.annotations |
| .values() |
| .flatten() |
| .all(|element| element.ancestors().any(|it| &it == edit.new_root())) |
| ) |
| } |
| |
| #[test] |
| fn test_insert_independent() { |
| let root = make::block_expr( |
| [make::let_stmt( |
| make::ext::simple_ident_pat(make::name("second")).into(), |
| None, |
| Some(make::expr_literal("2").into()), |
| ) |
| .into()], |
| None, |
| ); |
| |
| let (editor, root) = SyntaxEditor::with_ast_node(&root); |
| let make = editor.make(); |
| let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap(); |
| |
| editor.insert( |
| Position::first_child_of(root.stmt_list().unwrap().syntax()), |
| make.let_stmt( |
| make::ext::simple_ident_pat(make::name("first")).into(), |
| None, |
| Some(make::expr_literal("1").into()), |
| ) |
| .syntax(), |
| ); |
| |
| editor.insert( |
| Position::after(second_let.syntax()), |
| make.let_stmt( |
| make::ext::simple_ident_pat(make::name("third")).into(), |
| None, |
| Some(make::expr_literal("3").into()), |
| ) |
| .syntax(), |
| ); |
| |
| let edit = editor.finish(); |
| |
| let expect = expect![[r#" |
| let first = 1;{ |
| let second = 2;let third = 3; |
| }"#]]; |
| expect.assert_eq(&edit.new_root.to_string()); |
| } |
| |
| #[test] |
| fn test_insert_dependent() { |
| let root = make::block_expr( |
| [], |
| Some( |
| make::block_expr( |
| [make::let_stmt( |
| make::ext::simple_ident_pat(make::name("second")).into(), |
| None, |
| Some(make::expr_literal("2").into()), |
| ) |
| .into()], |
| None, |
| ) |
| .into(), |
| ), |
| ); |
| |
| let (editor, root) = SyntaxEditor::with_ast_node(&root); |
| let make = editor.make(); |
| |
| let inner_block = |
| root.syntax().descendants().flat_map(ast::BlockExpr::cast).nth(1).unwrap(); |
| let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap(); |
| |
| let new_block_expr = make.block_expr([], Some(ast::Expr::BlockExpr(inner_block.clone()))); |
| |
| let first_let = make.let_stmt( |
| make::ext::simple_ident_pat(make::name("first")).into(), |
| None, |
| Some(make::expr_literal("1").into()), |
| ); |
| |
| let third_let = make.let_stmt( |
| make::ext::simple_ident_pat(make::name("third")).into(), |
| None, |
| Some(make::expr_literal("3").into()), |
| ); |
| |
| editor.insert( |
| Position::first_child_of(inner_block.stmt_list().unwrap().syntax()), |
| first_let.syntax(), |
| ); |
| editor.insert(Position::after(second_let.syntax()), third_let.syntax()); |
| editor.replace(inner_block.syntax(), new_block_expr.syntax()); |
| |
| let edit = editor.finish(); |
| |
| let expect = expect![[r#" |
| { |
| { |
| let first = 1;{ |
| let second = 2;let third = 3; |
| } |
| } |
| }"#]]; |
| expect.assert_eq(&edit.new_root.to_string()); |
| } |
| |
| #[test] |
| fn test_dependent_change_prefers_nearest_changed_ancestor() { |
| let root = make::block_expr( |
| [], |
| Some( |
| make::block_expr( |
| [make::let_stmt( |
| make::ext::simple_ident_pat(make::name("second")).into(), |
| None, |
| Some(make::expr_literal("2").into()), |
| ) |
| .into()], |
| None, |
| ) |
| .into(), |
| ), |
| ); |
| |
| let (editor, root) = SyntaxEditor::with_ast_node(&root); |
| let make = editor.make(); |
| |
| let inner_block = |
| root.syntax().descendants().flat_map(ast::BlockExpr::cast).nth(1).unwrap(); |
| |
| let outer_replacement = make.block_expr([], Some(ast::Expr::BlockExpr(root.clone()))); |
| let inner_replacement = |
| make.block_expr([], Some(ast::Expr::BlockExpr(inner_block.clone()))); |
| |
| let first_let = make.let_stmt( |
| make::ext::simple_ident_pat(make::name("first")).into(), |
| None, |
| Some(make::expr_literal("1").into()), |
| ); |
| |
| editor.insert( |
| Position::first_child_of(inner_block.stmt_list().unwrap().syntax()), |
| first_let.syntax(), |
| ); |
| editor.replace(inner_block.syntax(), inner_replacement.syntax()); |
| editor.replace(root.syntax(), outer_replacement.syntax()); |
| |
| let edit = editor.finish(); |
| |
| let expect = expect![[r#" |
| { |
| { |
| { |
| let first = 1;{ |
| let second = 2; |
| } |
| } |
| } |
| }"#]]; |
| expect.assert_eq(&edit.new_root.to_string()); |
| } |
| |
| #[test] |
| fn test_replace_root_with_dependent() { |
| let root = make::block_expr( |
| [make::let_stmt( |
| make::ext::simple_ident_pat(make::name("second")).into(), |
| None, |
| Some(make::expr_literal("2").into()), |
| ) |
| .into()], |
| None, |
| ); |
| |
| let (editor, root) = SyntaxEditor::with_ast_node(&root); |
| let make = editor.make(); |
| |
| let inner_block = root; |
| |
| let new_block_expr = make.block_expr([], Some(ast::Expr::BlockExpr(inner_block.clone()))); |
| |
| let first_let = make.let_stmt( |
| make::ext::simple_ident_pat(make::name("first")).into(), |
| None, |
| Some(make::expr_literal("1").into()), |
| ); |
| |
| editor.insert( |
| Position::first_child_of(inner_block.stmt_list().unwrap().syntax()), |
| first_let.syntax(), |
| ); |
| editor.replace(inner_block.syntax(), new_block_expr.syntax()); |
| |
| let edit = editor.finish(); |
| |
| let expect = expect![[r#" |
| { |
| let first = 1;{ |
| let second = 2; |
| } |
| }"#]]; |
| expect.assert_eq(&edit.new_root.to_string()); |
| } |
| |
| #[test] |
| fn test_replace_token_in_parent() { |
| let parent_fn = make::fn_( |
| None, |
| None, |
| make::name("it"), |
| None, |
| None, |
| make::param_list(None, []), |
| make::block_expr([], Some(make::ext::expr_unit())), |
| Some(make::ret_type(make::ty_unit())), |
| false, |
| false, |
| false, |
| false, |
| ); |
| |
| let (editor, parent_fn) = SyntaxEditor::with_ast_node(&parent_fn); |
| |
| if let Some(ret_ty) = parent_fn.ret_type() { |
| editor.delete(ret_ty.syntax().clone()); |
| |
| if let Some(SyntaxElement::Token(token)) = ret_ty.syntax().next_sibling_or_token() |
| && token.kind().is_trivia() |
| { |
| editor.delete(token); |
| } |
| } |
| |
| if let Some(tail) = parent_fn.body().unwrap().tail_expr() { |
| editor.delete(tail.syntax().clone()); |
| } |
| |
| let edit = editor.finish(); |
| |
| let expect = expect![["fn it() {\n \n}"]]; |
| expect.assert_eq(&edit.new_root.to_string()); |
| } |
| |
| #[test] |
| fn test_more_times_replace_node_to_mutable_token() { |
| let arg_list = |
| make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); |
| |
| let (editor, arg_list) = SyntaxEditor::with_ast_node(&arg_list); |
| |
| let target_expr = make::token(parser::SyntaxKind::UNDERSCORE); |
| |
| for arg in arg_list.args() { |
| editor.replace(arg.syntax(), &target_expr); |
| } |
| |
| let edit = editor.finish(); |
| |
| let expect = expect![["(_, _)"]]; |
| expect.assert_eq(&edit.new_root.to_string()); |
| } |
| |
| #[test] |
| fn test_more_times_replace_node_to_mutable() { |
| let arg_list = |
| make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); |
| |
| let (editor, arg_list) = SyntaxEditor::with_ast_node(&arg_list); |
| |
| let target_expr = make::expr_literal("3").clone_for_update(); |
| |
| for arg in arg_list.args() { |
| editor.replace(arg.syntax(), target_expr.syntax()); |
| } |
| |
| let edit = editor.finish(); |
| |
| let expect = expect![["(3, 3)"]]; |
| expect.assert_eq(&edit.new_root.to_string()); |
| } |
| |
| #[test] |
| fn test_more_times_insert_node_to_mutable() { |
| let arg_list = |
| make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); |
| |
| let (editor, arg_list) = SyntaxEditor::with_ast_node(&arg_list); |
| |
| let target_expr = make::ext::expr_unit().clone_for_update(); |
| |
| for arg in arg_list.args() { |
| editor.insert(Position::before(arg.syntax()), target_expr.syntax()); |
| } |
| |
| let edit = editor.finish(); |
| |
| let expect = expect![["(()1, ()2)"]]; |
| expect.assert_eq(&edit.new_root.to_string()); |
| } |
| } |