Skip to content

Commit 9690f6b

Browse files
bors[bot]matklad
andauthored
Merge #3708
3708: Generalise syntax rewriting infrastructure to allow removal of nodes r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
2 parents 7c2cc85 + 062f6e3 commit 9690f6b

File tree

7 files changed

+270
-121
lines changed

7 files changed

+270
-121
lines changed

crates/ra_assists/src/assist_ctx.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use ra_syntax::{
1111
use ra_text_edit::TextEditBuilder;
1212

1313
use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
14+
use algo::SyntaxRewriter;
1415

1516
#[derive(Clone, Debug)]
1617
pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
@@ -234,6 +235,11 @@ impl ActionBuilder {
234235
pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
235236
algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
236237
}
238+
pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
239+
let node = rewriter.rewrite_root().unwrap();
240+
let new = rewriter.rewrite(&node);
241+
algo::diff(&node, &new).into_text_edit(&mut self.edit)
242+
}
237243

238244
fn build(self) -> AssistAction {
239245
AssistAction {

crates/ra_assists/src/ast_transform.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use rustc_hash::FxHashMap;
33

44
use hir::{PathResolution, SemanticsScope};
55
use ra_ide_db::RootDatabase;
6-
use ra_syntax::ast::{self, AstNode};
6+
use ra_syntax::{
7+
algo::SyntaxRewriter,
8+
ast::{self, AstNode},
9+
};
710

811
pub trait AstTransform<'a> {
912
fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode>;
@@ -153,15 +156,14 @@ impl<'a> QualifyPaths<'a> {
153156
}
154157

155158
pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
156-
let syntax = node.syntax();
157-
let result = ra_syntax::algo::replace_descendants(syntax, |element| match element {
159+
SyntaxRewriter::from_fn(|element| match element {
158160
ra_syntax::SyntaxElement::Node(n) => {
159161
let replacement = transformer.get_substitution(&n)?;
160162
Some(replacement.into())
161163
}
162164
_ => None,
163-
});
164-
N::cast(result).unwrap()
165+
})
166+
.rewrite_ast(&node)
165167
}
166168

167169
impl<'a> AstTransform<'a> for QualifyPaths<'a> {

crates/ra_assists/src/handlers/merge_imports.rs

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use std::iter::successors;
22

33
use ra_syntax::{
4-
algo::neighbor,
4+
algo::{neighbor, SyntaxRewriter},
55
ast::{self, edit::AstNodeEdit, make},
6-
AstNode, AstToken, Direction, InsertPosition, SyntaxElement, TextRange, T,
6+
AstNode, Direction, InsertPosition, SyntaxElement, T,
77
};
88

99
use crate::{Assist, AssistCtx, AssistId};
@@ -22,52 +22,39 @@ use crate::{Assist, AssistCtx, AssistId};
2222
// ```
2323
pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
2424
let tree: ast::UseTree = ctx.find_node_at_offset()?;
25-
let (new_tree, to_delete) = if let Some(use_item) =
26-
tree.syntax().parent().and_then(ast::UseItem::cast)
27-
{
25+
let mut rewriter = SyntaxRewriter::default();
26+
let mut offset = ctx.frange.range.start();
27+
28+
if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) {
2829
let (merged, to_delete) = next_prev()
2930
.filter_map(|dir| neighbor(&use_item, dir))
3031
.filter_map(|it| Some((it.clone(), it.use_tree()?)))
3132
.find_map(|(use_item, use_tree)| {
3233
Some((try_merge_trees(&tree, &use_tree)?, use_item.clone()))
3334
})?;
3435

35-
let mut range = to_delete.syntax().text_range();
36-
let next_ws = to_delete
37-
.syntax()
38-
.next_sibling_or_token()
39-
.and_then(|it| it.into_token())
40-
.and_then(ast::Whitespace::cast);
41-
if let Some(ws) = next_ws {
42-
range = range.extend_to(&ws.syntax().text_range())
36+
rewriter.replace_ast(&tree, &merged);
37+
rewriter += to_delete.remove();
38+
39+
if to_delete.syntax().text_range().end() < offset {
40+
offset -= to_delete.syntax().text_range().len();
4341
}
44-
(merged, range)
4542
} else {
4643
let (merged, to_delete) = next_prev()
4744
.filter_map(|dir| neighbor(&tree, dir))
4845
.find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?;
4946

50-
let mut range = to_delete.syntax().text_range();
51-
if let Some((dir, nb)) = next_prev().find_map(|dir| Some((dir, neighbor(&to_delete, dir)?)))
52-
{
53-
let nb_range = nb.syntax().text_range();
54-
if dir == Direction::Prev {
55-
range = TextRange::from_to(nb_range.end(), range.end());
56-
} else {
57-
range = TextRange::from_to(range.start(), nb_range.start());
58-
}
47+
rewriter.replace_ast(&tree, &merged);
48+
rewriter += to_delete.remove();
49+
50+
if to_delete.syntax().text_range().end() < offset {
51+
offset -= to_delete.syntax().text_range().len();
5952
}
60-
(merged, range)
6153
};
6254

63-
let mut offset = ctx.frange.range.start();
6455
ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| {
65-
edit.replace_ast(tree, new_tree);
66-
edit.delete(to_delete);
67-
68-
if to_delete.end() <= offset {
69-
offset -= to_delete.len();
70-
}
56+
edit.rewrite(rewriter);
57+
// FIXME: we only need because our diff is imprecise
7158
edit.set_cursor(offset);
7259
})
7360
}
@@ -156,7 +143,7 @@ use std::fmt::Debug;
156143
use std::fmt<|>::Display;
157144
",
158145
r"
159-
use std::fmt<|>::{Display, Debug};
146+
use std::fmt:<|>:{Display, Debug};
160147
",
161148
);
162149
}
@@ -178,7 +165,57 @@ use std::{fmt<|>::{Debug, Display}};
178165
use std::{fmt::Debug, fmt<|>::Display};
179166
",
180167
r"
181-
use std::{fmt<|>::{Display, Debug}};
168+
use std::{fmt::<|>{Display, Debug}};
169+
",
170+
);
171+
}
172+
173+
#[test]
174+
fn removes_just_enough_whitespace() {
175+
check_assist(
176+
merge_imports,
177+
r"
178+
use foo<|>::bar;
179+
use foo::baz;
180+
181+
/// Doc comment
182+
",
183+
r"
184+
use foo<|>::{bar, baz};
185+
186+
/// Doc comment
187+
",
188+
);
189+
}
190+
191+
#[test]
192+
fn works_with_trailing_comma() {
193+
check_assist(
194+
merge_imports,
195+
r"
196+
use {
197+
foo<|>::bar,
198+
foo::baz,
199+
};
200+
",
201+
r"
202+
use {
203+
foo<|>::{bar, baz},
204+
};
205+
",
206+
);
207+
check_assist(
208+
merge_imports,
209+
r"
210+
use {
211+
foo::baz,
212+
foo<|>::bar,
213+
};
214+
",
215+
r"
216+
use {
217+
foo::{bar<|>, baz},
218+
};
182219
",
183220
);
184221
}

crates/ra_hir_expand/src/eager.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ use crate::{
2626
};
2727

2828
use ra_parser::FragmentKind;
29-
use ra_syntax::{algo::replace_descendants, SyntaxElement, SyntaxNode};
30-
use std::{collections::HashMap, sync::Arc};
29+
use ra_syntax::{algo::SyntaxRewriter, SyntaxNode};
30+
use std::sync::Arc;
3131

3232
pub fn expand_eager_macro(
3333
db: &dyn AstDatabase,
@@ -95,10 +95,10 @@ fn eager_macro_recur(
9595
curr: InFile<SyntaxNode>,
9696
macro_resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>,
9797
) -> Option<SyntaxNode> {
98-
let mut original = curr.value.clone();
98+
let original = curr.value.clone();
9999

100100
let children = curr.value.descendants().filter_map(ast::MacroCall::cast);
101-
let mut replaces: HashMap<SyntaxElement, SyntaxElement> = HashMap::default();
101+
let mut rewriter = SyntaxRewriter::default();
102102

103103
// Collect replacement
104104
for child in children {
@@ -119,12 +119,9 @@ fn eager_macro_recur(
119119
}
120120
};
121121

122-
replaces.insert(child.syntax().clone().into(), insert.into());
122+
rewriter.replace(child.syntax(), &insert);
123123
}
124124

125-
if !replaces.is_empty() {
126-
original = replace_descendants(&original, |n| replaces.get(n).cloned());
127-
}
128-
129-
Some(original)
125+
let res = rewriter.rewrite(&original);
126+
Some(res)
130127
}

crates/ra_ide/src/expand_macro.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
use hir::Semantics;
44
use ra_ide_db::RootDatabase;
55
use ra_syntax::{
6-
algo::{find_node_at_offset, replace_descendants},
7-
ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, WalkEvent, T,
6+
algo::{find_node_at_offset, SyntaxRewriter},
7+
ast, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T,
88
};
9-
use rustc_hash::FxHashMap;
109

1110
use crate::FilePosition;
1211

@@ -37,7 +36,7 @@ fn expand_macro_recur(
3736
let mut expanded = sema.expand(macro_call)?;
3837

3938
let children = expanded.descendants().filter_map(ast::MacroCall::cast);
40-
let mut replaces: FxHashMap<SyntaxElement, SyntaxElement> = FxHashMap::default();
39+
let mut rewriter = SyntaxRewriter::default();
4140

4241
for child in children.into_iter() {
4342
if let Some(new_node) = expand_macro_recur(sema, &child) {
@@ -47,12 +46,13 @@ fn expand_macro_recur(
4746
if expanded == *child.syntax() {
4847
expanded = new_node;
4948
} else {
50-
replaces.insert(child.syntax().clone().into(), new_node.into());
49+
rewriter.replace(child.syntax(), &new_node)
5150
}
5251
}
5352
}
5453

55-
Some(replace_descendants(&expanded, |n| replaces.get(n).cloned()))
54+
let res = rewriter.rewrite(&expanded);
55+
Some(res)
5656
}
5757

5858
// FIXME: It would also be cool to share logic here and in the mbe tests,

0 commit comments

Comments
 (0)