Skip to content

Commit d4b43d5

Browse files
committed
improve ordered use tree merging implementation
1 parent db3f0f1 commit d4b43d5

File tree

1 file changed

+50
-67
lines changed

1 file changed

+50
-67
lines changed

crates/ide-db/src/imports/merge_imports.rs

+50-67
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
//! Handle syntactic aspects of merging UseTrees.
22
use std::cmp::Ordering;
3-
use std::iter::{empty, successors};
3+
use std::iter::empty;
44

55
use itertools::{EitherOrBoth, Itertools};
6-
use parser::{SyntaxKind, T};
7-
use rustc_hash::{FxHashMap, FxHashSet};
6+
use parser::T;
87
use syntax::{
8+
algo,
99
ast::{self, make, AstNode, HasAttrs, HasName, HasVisibility, PathSegmentKind},
1010
ted::{self, Position},
11-
SyntaxElement, TokenText,
11+
Direction, TokenText,
1212
};
1313

1414
use crate::syntax_helpers::node_ext::vis_eq;
@@ -101,22 +101,6 @@ fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior)
101101
// same as a `filter` op).
102102
.map(|tree| merge.is_tree_allowed(&tree).then_some(tree))
103103
.collect::<Option<_>>()?;
104-
// Preserves some positional formatting info before sorting.
105-
let positional_formatting_map: FxHashMap<_, _> = use_trees
106-
.iter()
107-
.enumerate()
108-
.filter_map(|(idx, tree)| {
109-
formatting_whitespace(&SyntaxElement::from(tree.syntax().clone()))
110-
.map(|trivia| (idx, trivia))
111-
})
112-
.collect();
113-
let closing_formatting_trivia = if use_trees.len() > 0 {
114-
lhs.use_tree_list()
115-
.and_then(|list| list.r_curly_token())
116-
.and_then(|token| formatting_whitespace(&SyntaxElement::from(token)))
117-
} else {
118-
None
119-
};
120104
// Sorts the use trees similar to rustfmt's algorithm for ordering imports
121105
// (see `use_tree_cmp` doc).
122106
use_trees.sort_unstable_by(|a, b| use_tree_cmp(a, b));
@@ -184,60 +168,59 @@ fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior)
184168
// Creates a new use tree list with the item.
185169
None => lhs.get_or_create_use_tree_list().add_use_tree(rhs_t),
186170
// Recreates the use tree list with sorted items (see `use_tree_cmp` doc).
187-
// Also attempts to preserve formatting (but only in terms of index-based
188-
// "positions" of new lines and indents).
189-
Some(old_list) => {
190-
ted::remove(old_list.syntax());
171+
Some(use_tree_list) => {
172+
if use_tree_list.l_curly_token().is_none() {
173+
ted::insert_raw(
174+
Position::first_child_of(use_tree_list.syntax()),
175+
make::token(T!['{']),
176+
);
177+
}
178+
if use_tree_list.r_curly_token().is_none() {
179+
ted::insert_raw(
180+
Position::last_child_of(use_tree_list.syntax()),
181+
make::token(T!['}']),
182+
);
183+
}
191184

192-
let new_list = make::use_tree_list(empty()).clone_for_update();
193185
let mut elements = Vec::new();
194186
for (idx, tree) in use_trees.iter().enumerate() {
195187
if idx > 0 {
196188
elements.push(make::token(T![,]).into());
197-
}
198-
match positional_formatting_map.get(&idx) {
199-
None if idx > 0 => {
200-
elements.push(make::tokens::single_space().into())
201-
}
202-
Some(prev_trivia) => {
203-
elements.extend(prev_trivia.clone().into_iter())
204-
}
205-
_ => (),
189+
elements.push(make::tokens::single_space().into());
206190
}
207191
elements.push(tree.syntax().clone().into());
208192
}
209-
if let Some(ref trivia) = closing_formatting_trivia {
210-
elements.extend(trivia.clone().into_iter())
211-
}
212193

213-
let trees_pos = match new_list.l_curly_token() {
214-
Some(l_curly) => Position::after(l_curly),
215-
None => Position::last_child_of(new_list.syntax()),
216-
};
217-
ted::insert_all_raw(trees_pos, elements);
218-
219-
let list_pos = Position::last_child_of(lhs.syntax());
220-
ted::insert_raw(list_pos, new_list.syntax());
194+
let start = use_tree_list
195+
.l_curly_token()
196+
.and_then(|l_curly| {
197+
algo::non_trivia_sibling(l_curly.into(), Direction::Next)
198+
})
199+
.filter(|it| it.kind() != T!['}']);
200+
let end = use_tree_list
201+
.r_curly_token()
202+
.and_then(|r_curly| {
203+
algo::non_trivia_sibling(r_curly.into(), Direction::Prev)
204+
})
205+
.filter(|it| it.kind() != T!['{']);
206+
if let Some((start, end)) = start.zip(end) {
207+
// Attempt to insert elements while preserving preceding and trailing trivia.
208+
ted::replace_all(start..=end, elements);
209+
} else {
210+
let new_use_tree_list = make::use_tree_list(empty()).clone_for_update();
211+
let trees_pos = match new_use_tree_list.l_curly_token() {
212+
Some(l_curly) => Position::after(l_curly),
213+
None => Position::last_child_of(new_use_tree_list.syntax()),
214+
};
215+
ted::insert_all_raw(trees_pos, elements);
216+
ted::replace(use_tree_list.syntax(), new_use_tree_list.syntax());
217+
}
221218
}
222219
}
223220
}
224221
}
225222
}
226-
return Some(());
227-
228-
// Returns all trivia preceding a syntax element if it may be relevant to formatting
229-
// (i.e. includes at least one new line character).
230-
fn formatting_whitespace(elem: &SyntaxElement) -> Option<FxHashSet<SyntaxElement>> {
231-
let succ =
232-
|item: &SyntaxElement| item.prev_sibling_or_token().filter(|it| it.kind().is_trivia());
233-
let first = succ(elem);
234-
let trivia_set: FxHashSet<_> = successors(first, succ).collect();
235-
let contains_formatting_whitespace = trivia_set.iter().any(|it| {
236-
it.kind() == SyntaxKind::WHITESPACE
237-
&& it.as_token().is_some_and(|token| token.text().contains('\n'))
238-
});
239-
contains_formatting_whitespace.then_some(trivia_set)
240-
}
223+
Some(())
241224
}
242225

243226
/// Traverses both paths until they differ, returning the common prefix of both.
@@ -383,13 +366,13 @@ fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
383366
}
384367
}
385368

386-
// Orders for use trees with equal paths (see `use_tree_cmp` for details about use tree ordering).
387-
//
388-
// If the `strict` parameter is set to true and both trees have tree lists, the tree lists are
389-
// ordered by calling `use_tree_cmp` on their "sub-tree" pairs until either the tie is broken
390-
// or tree list equality is confirmed, otherwise (i.e. if either `strict` is false or at least
391-
// one of the trees does *not* have tree list), this potentially recursive step is skipped,
392-
// and only the presence of a glob pattern or an alias is used to determine the ordering.
369+
/// Orders for use trees with equal paths (see `use_tree_cmp` for details about use tree ordering).
370+
///
371+
/// If the `strict` parameter is set to true and both trees have tree lists, the tree lists are
372+
/// ordered by calling `use_tree_cmp` on their "sub-tree" pairs until either the tie is broken
373+
/// or tree list equality is confirmed, otherwise (i.e. if either `strict` is false or at least
374+
/// one of the trees does *not* have tree list), this potentially recursive step is skipped,
375+
/// and only the presence of a glob pattern or an alias is used to determine the ordering.
393376
fn use_tree_cmp_by_tree_list_glob_or_alias(
394377
a: &ast::UseTree,
395378
b: &ast::UseTree,

0 commit comments

Comments
 (0)