Generalise syntax rewriting infrastructure to allow removal of nodes

This commit is contained in:
Aleksey Kladov 2020-03-24 17:03:05 +01:00
parent 3bd119a4c1
commit 062f6e3bbe
7 changed files with 253 additions and 122 deletions

View file

@ -1,6 +1,9 @@
//! FIXME: write short doc here
use std::ops::RangeInclusive;
use std::{
fmt,
ops::{self, RangeInclusive},
};
use itertools::Itertools;
use ra_text_edit::TextEditBuilder;
@ -222,42 +225,119 @@ fn _replace_children(
with_children(parent, new_children)
}
#[derive(Default)]
pub struct SyntaxRewriter<'a> {
f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>,
//FIXME: add debug_assertions that all elements are in fact from the same file.
replacements: FxHashMap<SyntaxElement, Replacement>,
}
impl fmt::Debug for SyntaxRewriter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SyntaxRewriter").field("replacements", &self.replacements).finish()
}
}
impl<'a> SyntaxRewriter<'a> {
pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> {
SyntaxRewriter { f: Some(Box::new(f)), replacements: FxHashMap::default() }
}
pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) {
let what = what.clone().into();
let replacement = Replacement::Delete;
self.replacements.insert(what, replacement);
}
pub fn replace<T: Clone + Into<SyntaxElement>>(&mut self, what: &T, with: &T) {
let what = what.clone().into();
let replacement = Replacement::Single(with.clone().into());
self.replacements.insert(what, replacement);
}
pub fn replace_ast<T: AstNode>(&mut self, what: &T, with: &T) {
self.replace(what.syntax(), with.syntax())
}
pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode {
if self.f.is_none() && self.replacements.is_empty() {
return node.clone();
}
self.rewrite_children(node)
}
pub fn rewrite_ast<N: AstNode>(self, node: &N) -> N {
N::cast(self.rewrite(node.syntax())).unwrap()
}
pub fn rewrite_root(&self) -> Option<SyntaxNode> {
assert!(self.f.is_none());
self.replacements
.keys()
.map(|element| match element {
SyntaxElement::Node(it) => it.clone(),
SyntaxElement::Token(it) => it.parent(),
})
.fold1(|a, b| least_common_ancestor(&a, &b).unwrap())
}
fn replacement(&self, element: &SyntaxElement) -> Option<Replacement> {
if let Some(f) = &self.f {
assert!(self.replacements.is_empty());
return f(element).map(Replacement::Single);
}
self.replacements.get(element).cloned()
}
fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode {
// FIXME: this could be made much faster.
let new_children =
node.children_with_tokens().flat_map(|it| self.rewrite_self(&it)).collect::<Vec<_>>();
with_children(node, new_children)
}
fn rewrite_self(
&self,
element: &SyntaxElement,
) -> Option<NodeOrToken<rowan::GreenNode, rowan::GreenToken>> {
if let Some(replacement) = self.replacement(&element) {
return match replacement {
Replacement::Single(NodeOrToken::Node(it)) => {
Some(NodeOrToken::Node(it.green().clone()))
}
Replacement::Single(NodeOrToken::Token(it)) => {
Some(NodeOrToken::Token(it.green().clone()))
}
Replacement::Delete => None,
};
}
let res = match element {
NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()),
};
Some(res)
}
}
impl<'a> ops::AddAssign for SyntaxRewriter<'_> {
fn add_assign(&mut self, rhs: SyntaxRewriter) {
assert!(rhs.f.is_none());
self.replacements.extend(rhs.replacements)
}
}
#[derive(Clone, Debug)]
enum Replacement {
Delete,
Single(SyntaxElement),
}
/// Replaces descendants in the node, according to the mapping.
///
/// This is a type-unsafe low-level editing API, if you need to use it, prefer
/// to create a type-safe abstraction on top of it instead.
pub fn replace_descendants(
pub fn _replace_descendants(
parent: &SyntaxNode,
map: impl Fn(&SyntaxElement) -> Option<SyntaxElement>,
) -> SyntaxNode {
_replace_descendants(parent, &map)
}
fn _replace_descendants(
parent: &SyntaxNode,
map: &dyn Fn(&SyntaxElement) -> Option<SyntaxElement>,
) -> SyntaxNode {
// FIXME: this could be made much faster.
let new_children = parent.children_with_tokens().map(|it| go(map, it)).collect::<Vec<_>>();
return with_children(parent, new_children);
fn go(
map: &dyn Fn(&SyntaxElement) -> Option<SyntaxElement>,
element: SyntaxElement,
) -> NodeOrToken<rowan::GreenNode, rowan::GreenToken> {
if let Some(replacement) = map(&element) {
return match replacement {
NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()),
NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
};
}
match element {
NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
NodeOrToken::Node(it) => {
NodeOrToken::Node(_replace_descendants(&it, map).green().clone())
}
}
}
SyntaxRewriter::from_fn(map).rewrite(parent)
}
fn with_children(

View file

@ -4,7 +4,6 @@
use std::{iter, ops::RangeInclusive};
use arrayvec::ArrayVec;
use rustc_hash::FxHashMap;
use crate::{
algo,
@ -17,6 +16,7 @@ use crate::{
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
SyntaxNode, SyntaxToken, T,
};
use algo::{neighbor, SyntaxRewriter};
impl ast::BinExpr {
#[must_use]
@ -255,6 +255,28 @@ impl ast::UseItem {
}
self.clone()
}
pub fn remove(&self) -> SyntaxRewriter<'static> {
let mut res = SyntaxRewriter::default();
res.delete(self.syntax());
let next_ws = self
.syntax()
.next_sibling_or_token()
.and_then(|it| it.into_token())
.and_then(ast::Whitespace::cast);
if let Some(next_ws) = next_ws {
let ws_text = next_ws.syntax().text();
if ws_text.starts_with('\n') {
let rest = &ws_text[1..];
if rest.is_empty() {
res.delete(next_ws.syntax())
} else {
res.replace(next_ws.syntax(), &make::tokens::whitespace(rest));
}
}
}
res
}
}
impl ast::UseTree {
@ -293,6 +315,22 @@ impl ast::UseTree {
Some(res)
}
}
pub fn remove(&self) -> SyntaxRewriter<'static> {
let mut res = SyntaxRewriter::default();
res.delete(self.syntax());
for &dir in [Direction::Next, Direction::Prev].iter() {
if let Some(nb) = neighbor(self, dir) {
self.syntax()
.siblings_with_tokens(dir)
.skip(1)
.take_while(|it| it.as_node() != Some(nb.syntax()))
.for_each(|el| res.delete(&el));
return res;
}
}
res
}
}
#[must_use]
@ -343,28 +381,24 @@ impl IndentLevel {
}
fn _increase_indent(self, node: SyntaxNode) -> SyntaxNode {
let replacements: FxHashMap<SyntaxElement, SyntaxElement> = node
.descendants_with_tokens()
let mut rewriter = SyntaxRewriter::default();
node.descendants_with_tokens()
.filter_map(|el| el.into_token())
.filter_map(ast::Whitespace::cast)
.filter(|ws| {
let text = ws.syntax().text();
text.contains('\n')
})
.map(|ws| {
(
ws.syntax().clone().into(),
make::tokens::whitespace(&format!(
"{}{:width$}",
ws.syntax().text(),
"",
width = self.0 as usize * 4
))
.into(),
)
})
.collect();
algo::replace_descendants(&node, |n| replacements.get(n).cloned())
.for_each(|ws| {
let new_ws = make::tokens::whitespace(&format!(
"{}{:width$}",
ws.syntax().text(),
"",
width = self.0 as usize * 4
));
rewriter.replace(ws.syntax(), &new_ws)
});
rewriter.rewrite(&node)
}
pub fn decrease_indent<N: AstNode>(self, node: N) -> N {
@ -372,27 +406,21 @@ impl IndentLevel {
}
fn _decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
let replacements: FxHashMap<SyntaxElement, SyntaxElement> = node
.descendants_with_tokens()
let mut rewriter = SyntaxRewriter::default();
node.descendants_with_tokens()
.filter_map(|el| el.into_token())
.filter_map(ast::Whitespace::cast)
.filter(|ws| {
let text = ws.syntax().text();
text.contains('\n')
})
.map(|ws| {
(
ws.syntax().clone().into(),
make::tokens::whitespace(
&ws.syntax()
.text()
.replace(&format!("\n{:1$}", "", self.0 as usize * 4), "\n"),
)
.into(),
)
})
.collect();
algo::replace_descendants(&node, |n| replacements.get(n).cloned())
.for_each(|ws| {
let new_ws = make::tokens::whitespace(
&ws.syntax().text().replace(&format!("\n{:1$}", "", self.0 as usize * 4), "\n"),
);
rewriter.replace(ws.syntax(), &new_ws)
});
rewriter.rewrite(&node)
}
}
@ -442,12 +470,11 @@ pub trait AstNodeEdit: AstNode + Sized {
&self,
replacement_map: impl IntoIterator<Item = (D, D)>,
) -> Self {
let map = replacement_map
.into_iter()
.map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into()))
.collect::<FxHashMap<SyntaxElement, _>>();
let new_syntax = algo::replace_descendants(self.syntax(), |n| map.get(n).cloned());
Self::cast(new_syntax).unwrap()
let mut rewriter = SyntaxRewriter::default();
for (from, to) in replacement_map {
rewriter.replace(from.syntax(), to.syntax())
}
rewriter.rewrite_ast(self)
}
}