diff options
author | Lukas Wirth <[email protected]> | 2020-09-12 18:18:14 +0100 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2020-09-12 18:19:19 +0100 |
commit | a898752881779a328462ad9f2db291073f2f134f (patch) | |
tree | 4b6894416bf4f2a875b5bd4a3bc20510a00821d5 /crates | |
parent | c8623461a57e7882ac47b5da13a1a03efa58f603 (diff) |
Reimplement import merging by making it recursive properly nesting all levels
Diffstat (limited to 'crates')
-rw-r--r-- | crates/assists/src/handlers/merge_imports.rs | 14 | ||||
-rw-r--r-- | crates/assists/src/handlers/replace_qualified_name_with_use.rs | 20 | ||||
-rw-r--r-- | crates/assists/src/utils/insert_use.rs | 312 | ||||
-rw-r--r-- | crates/syntax/src/ast/edit.rs | 1 |
4 files changed, 264 insertions, 83 deletions
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs index 0bd679260..fe33cee53 100644 --- a/crates/assists/src/handlers/merge_imports.rs +++ b/crates/assists/src/handlers/merge_imports.rs | |||
@@ -95,7 +95,7 @@ use std::fmt::Debug; | |||
95 | use std::fmt<|>::Display; | 95 | use std::fmt<|>::Display; |
96 | ", | 96 | ", |
97 | r" | 97 | r" |
98 | use std::fmt::{Display, Debug}; | 98 | use std::fmt::{Debug, Display}; |
99 | ", | 99 | ", |
100 | ); | 100 | ); |
101 | } | 101 | } |
@@ -122,7 +122,7 @@ use std::fmt::{self, Display}; | |||
122 | use std::{fmt, <|>fmt::Display}; | 122 | use std::{fmt, <|>fmt::Display}; |
123 | ", | 123 | ", |
124 | r" | 124 | r" |
125 | use std::{fmt::{Display, self}}; | 125 | use std::{fmt::{self, Display}}; |
126 | ", | 126 | ", |
127 | ); | 127 | ); |
128 | } | 128 | } |
@@ -210,13 +210,17 @@ use std::{fmt<|>::Debug, fmt::Display}; | |||
210 | use std::{fmt::{Debug, Display}}; | 210 | use std::{fmt::{Debug, Display}}; |
211 | ", | 211 | ", |
212 | ); | 212 | ); |
213 | } | ||
214 | |||
215 | #[test] | ||
216 | fn test_merge_nested2() { | ||
213 | check_assist( | 217 | check_assist( |
214 | merge_imports, | 218 | merge_imports, |
215 | r" | 219 | r" |
216 | use std::{fmt::Debug, fmt<|>::Display}; | 220 | use std::{fmt::Debug, fmt<|>::Display}; |
217 | ", | 221 | ", |
218 | r" | 222 | r" |
219 | use std::{fmt::{Display, Debug}}; | 223 | use std::{fmt::{Debug, Display}}; |
220 | ", | 224 | ", |
221 | ); | 225 | ); |
222 | } | 226 | } |
@@ -310,9 +314,7 @@ use foo::<|>{ | |||
310 | }; | 314 | }; |
311 | ", | 315 | ", |
312 | r" | 316 | r" |
313 | use foo::{ | 317 | use foo::{FooBar, bar::baz}; |
314 | FooBar, | ||
315 | bar::baz}; | ||
316 | ", | 318 | ", |
317 | ) | 319 | ) |
318 | } | 320 | } |
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs index 85c70d16b..093c3b101 100644 --- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs | |||
@@ -312,7 +312,7 @@ impl std::fmt<|> for Foo { | |||
312 | } | 312 | } |
313 | ", | 313 | ", |
314 | r" | 314 | r" |
315 | use std::fmt::{Debug, self}; | 315 | use std::fmt::{self, Debug}; |
316 | 316 | ||
317 | impl fmt for Foo { | 317 | impl fmt for Foo { |
318 | } | 318 | } |
@@ -330,9 +330,8 @@ use std::fmt::{Debug, nested::{Display}}; | |||
330 | impl std::fmt::nested<|> for Foo { | 330 | impl std::fmt::nested<|> for Foo { |
331 | } | 331 | } |
332 | ", | 332 | ", |
333 | // FIXME(veykril): should be nested::{self, Display} here | ||
334 | r" | 333 | r" |
335 | use std::fmt::{Debug, nested::{Display}, nested}; | 334 | use std::fmt::{Debug, nested::{self, Display}}; |
336 | 335 | ||
337 | impl nested for Foo { | 336 | impl nested for Foo { |
338 | } | 337 | } |
@@ -350,9 +349,8 @@ use std::fmt::{Debug, nested::{self, Display}}; | |||
350 | impl std::fmt::nested<|> for Foo { | 349 | impl std::fmt::nested<|> for Foo { |
351 | } | 350 | } |
352 | ", | 351 | ", |
353 | // FIXME(veykril): nested is duplicated now | ||
354 | r" | 352 | r" |
355 | use std::fmt::{Debug, nested::{self, Display}, nested}; | 353 | use std::fmt::{Debug, nested::{self, Display}}; |
356 | 354 | ||
357 | impl nested for Foo { | 355 | impl nested for Foo { |
358 | } | 356 | } |
@@ -371,7 +369,7 @@ impl std::fmt::nested::Debug<|> for Foo { | |||
371 | } | 369 | } |
372 | ", | 370 | ", |
373 | r" | 371 | r" |
374 | use std::fmt::{Debug, nested::{Display}, nested::Debug}; | 372 | use std::fmt::{Debug, nested::{Debug, Display}}; |
375 | 373 | ||
376 | impl Debug for Foo { | 374 | impl Debug for Foo { |
377 | } | 375 | } |
@@ -409,7 +407,7 @@ impl std::fmt::Display<|> for Foo { | |||
409 | } | 407 | } |
410 | ", | 408 | ", |
411 | r" | 409 | r" |
412 | use std::fmt::{nested::Debug, Display}; | 410 | use std::fmt::{Display, nested::Debug}; |
413 | 411 | ||
414 | impl Display for Foo { | 412 | impl Display for Foo { |
415 | } | 413 | } |
@@ -429,12 +427,8 @@ use crate::{ | |||
429 | 427 | ||
430 | fn foo() { crate::ty::lower<|>::trait_env() } | 428 | fn foo() { crate::ty::lower<|>::trait_env() } |
431 | ", | 429 | ", |
432 | // FIXME(veykril): formatting broke here | ||
433 | r" | 430 | r" |
434 | use crate::{ | 431 | use crate::{AssocItem, ty::{Substs, Ty, lower}}; |
435 | ty::{Substs, Ty}, | ||
436 | AssocItem, | ||
437 | ty::lower}; | ||
438 | 432 | ||
439 | fn foo() { lower::trait_env() } | 433 | fn foo() { lower::trait_env() } |
440 | ", | 434 | ", |
@@ -633,7 +627,7 @@ fn main() { | |||
633 | } | 627 | } |
634 | ", | 628 | ", |
635 | r" | 629 | r" |
636 | use std::fmt::{Display, self}; | 630 | use std::fmt::{self, Display}; |
637 | 631 | ||
638 | fn main() { | 632 | fn main() { |
639 | fmt; | 633 | fmt; |
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs index 98553b2e0..4f5fd317a 100644 --- a/crates/assists/src/utils/insert_use.rs +++ b/crates/assists/src/utils/insert_use.rs | |||
@@ -1,7 +1,9 @@ | |||
1 | //! Handle syntactic aspects of inserting a new `use`. | 1 | //! Handle syntactic aspects of inserting a new `use`. |
2 | use std::iter::{self, successors}; | 2 | use std::{ |
3 | cmp::Ordering, | ||
4 | iter::{self, successors}, | ||
5 | }; | ||
3 | 6 | ||
4 | use algo::skip_trivia_token; | ||
5 | use ast::{ | 7 | use ast::{ |
6 | edit::{AstNodeEdit, IndentLevel}, | 8 | edit::{AstNodeEdit, IndentLevel}, |
7 | PathSegmentKind, VisibilityOwner, | 9 | PathSegmentKind, VisibilityOwner, |
@@ -9,9 +11,8 @@ use ast::{ | |||
9 | use syntax::{ | 11 | use syntax::{ |
10 | algo, | 12 | algo, |
11 | ast::{self, make, AstNode}, | 13 | ast::{self, make, AstNode}, |
12 | Direction, InsertPosition, SyntaxElement, SyntaxNode, T, | 14 | InsertPosition, SyntaxElement, SyntaxNode, |
13 | }; | 15 | }; |
14 | use test_utils::mark; | ||
15 | 16 | ||
16 | #[derive(Debug)] | 17 | #[derive(Debug)] |
17 | pub enum ImportScope { | 18 | pub enum ImportScope { |
@@ -163,18 +164,48 @@ pub(crate) fn try_merge_imports( | |||
163 | Some(old.with_use_tree(merged)) | 164 | Some(old.with_use_tree(merged)) |
164 | } | 165 | } |
165 | 166 | ||
166 | /// Simple function that checks if a UseTreeList is deeper than one level | 167 | /// Orders paths in the following way: |
167 | fn use_tree_list_is_nested(tl: &ast::UseTreeList) -> bool { | 168 | /// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers |
168 | tl.use_trees().any(|use_tree| { | 169 | /// FIXME: rustfmt sort lowercase idents before uppercase |
169 | use_tree.use_tree_list().is_some() || use_tree.path().and_then(|p| p.qualifier()).is_some() | 170 | fn path_cmp(a: &ast::Path, b: &ast::Path) -> Ordering { |
170 | }) | 171 | let a = segment_iter(a); |
172 | let b = segment_iter(b); | ||
173 | let mut a_clone = a.clone(); | ||
174 | let mut b_clone = b.clone(); | ||
175 | match ( | ||
176 | a_clone.next().and_then(|ps| ps.self_token()).is_some() && a_clone.next().is_none(), | ||
177 | b_clone.next().and_then(|ps| ps.self_token()).is_some() && b_clone.next().is_none(), | ||
178 | ) { | ||
179 | (true, true) => Ordering::Equal, | ||
180 | (true, false) => Ordering::Less, | ||
181 | (false, true) => Ordering::Greater, | ||
182 | (false, false) => { | ||
183 | // cmp_by would be useful for us here but that is currently unstable | ||
184 | // cmp doesnt work due the lifetimes on text's return type | ||
185 | a.zip(b) | ||
186 | .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref())) | ||
187 | .find_map(|(a, b)| match a.text().cmp(b.text()) { | ||
188 | ord @ Ordering::Greater | ord @ Ordering::Less => Some(ord), | ||
189 | Ordering::Equal => None, | ||
190 | }) | ||
191 | .unwrap_or(Ordering::Equal) | ||
192 | } | ||
193 | } | ||
194 | } | ||
195 | |||
196 | fn path_cmp_opt(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering { | ||
197 | match (a, b) { | ||
198 | (None, None) => Ordering::Equal, | ||
199 | (None, Some(_)) => Ordering::Less, | ||
200 | (Some(_), None) => Ordering::Greater, | ||
201 | (Some(a), Some(b)) => path_cmp(&a, &b), | ||
202 | } | ||
171 | } | 203 | } |
172 | 204 | ||
173 | // FIXME: currently this merely prepends the new tree into old, ideally it would insert the items in a sorted fashion | ||
174 | pub(crate) fn try_merge_trees( | 205 | pub(crate) fn try_merge_trees( |
175 | old: &ast::UseTree, | 206 | old: &ast::UseTree, |
176 | new: &ast::UseTree, | 207 | new: &ast::UseTree, |
177 | merge_behaviour: MergeBehaviour, | 208 | merge: MergeBehaviour, |
178 | ) -> Option<ast::UseTree> { | 209 | ) -> Option<ast::UseTree> { |
179 | let lhs_path = old.path()?; | 210 | let lhs_path = old.path()?; |
180 | let rhs_path = new.path()?; | 211 | let rhs_path = new.path()?; |
@@ -182,33 +213,89 @@ pub(crate) fn try_merge_trees( | |||
182 | let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; | 213 | let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; |
183 | let lhs = old.split_prefix(&lhs_prefix); | 214 | let lhs = old.split_prefix(&lhs_prefix); |
184 | let rhs = new.split_prefix(&rhs_prefix); | 215 | let rhs = new.split_prefix(&rhs_prefix); |
185 | let lhs_tl = lhs.use_tree_list()?; | 216 | recursive_merge(&lhs, &rhs, merge).map(|(merged, _)| merged) |
186 | let rhs_tl = rhs.use_tree_list()?; | 217 | } |
187 | |||
188 | // if we are only allowed to merge the last level check if the split off paths are only one level deep | ||
189 | if merge_behaviour == MergeBehaviour::Last | ||
190 | && (use_tree_list_is_nested(&lhs_tl) || use_tree_list_is_nested(&rhs_tl)) | ||
191 | { | ||
192 | mark::hit!(test_last_merge_too_long); | ||
193 | return None; | ||
194 | } | ||
195 | 218 | ||
196 | let should_insert_comma = lhs_tl | 219 | /// Returns the merged tree and the number of children it has, which is required to check if the tree is allowed to be used for MergeBehaviour::Last |
197 | .r_curly_token() | 220 | fn recursive_merge( |
198 | .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev)) | 221 | lhs: &ast::UseTree, |
199 | .map(|it| it.kind()) | 222 | rhs: &ast::UseTree, |
200 | != Some(T![,]); | 223 | merge: MergeBehaviour, |
201 | let mut to_insert: Vec<SyntaxElement> = Vec::new(); | 224 | ) -> Option<(ast::UseTree, usize)> { |
202 | if should_insert_comma { | 225 | let mut use_trees = lhs |
203 | to_insert.push(make::token(T![,]).into()); | 226 | .use_tree_list() |
204 | to_insert.push(make::tokens::single_space().into()); | 227 | .into_iter() |
205 | } | 228 | .flat_map(|list| list.use_trees()) |
206 | to_insert.extend( | 229 | // check if any of the use trees are nested, if they are and the behaviour is last only we are not allowed to merge this |
207 | rhs_tl.syntax().children_with_tokens().filter(|it| !matches!(it.kind(), T!['{'] | T!['}'])), | 230 | .map(|tree| match merge == MergeBehaviour::Last && tree.use_tree_list().is_some() { |
208 | ); | 231 | true => None, |
209 | let pos = InsertPosition::Before(lhs_tl.r_curly_token()?.into()); | 232 | false => Some(tree), |
210 | let use_tree_list = lhs_tl.insert_children(pos, to_insert); | 233 | }) |
211 | Some(lhs.with_use_tree_list(use_tree_list)) | 234 | .collect::<Option<Vec<_>>>()?; |
235 | use_trees.sort_unstable_by(|a, b| path_cmp_opt(a.path(), b.path())); | ||
236 | for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) { | ||
237 | let rhs_path = rhs_t.path(); | ||
238 | match use_trees.binary_search_by(|p| path_cmp_opt(p.path(), rhs_path.clone())) { | ||
239 | Ok(idx) => { | ||
240 | let lhs_path = use_trees[idx].path()?; | ||
241 | let rhs_path = rhs_path?; | ||
242 | let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; | ||
243 | if lhs_prefix == lhs_path && rhs_prefix == rhs_path { | ||
244 | let tree_is_self = | ||
245 | |tree: ast::UseTree| tree.path().map(path_is_self).unwrap_or(false); | ||
246 | // check if only one of the two trees has a tree list, and whether that then contains `self` or not. | ||
247 | // If this is the case we can skip this iteration since the path without the list is already included in the other one via `self` | ||
248 | if use_trees[idx] | ||
249 | .use_tree_list() | ||
250 | .xor(rhs_t.use_tree_list()) | ||
251 | .map(|tree_list| tree_list.use_trees().any(tree_is_self)) | ||
252 | .unwrap_or(false) | ||
253 | { | ||
254 | continue; | ||
255 | } | ||
256 | |||
257 | // glob imports arent part of the use-tree lists so we need to special handle them here as well | ||
258 | // this special handling is only required for when we merge a module import into a glob import of said module | ||
259 | // see the `merge_self_glob` or `merge_mod_into_glob` tests | ||
260 | if use_trees[idx].star_token().is_some() || rhs_t.star_token().is_some() { | ||
261 | use_trees[idx] = make::use_tree( | ||
262 | make::path_unqualified(make::path_segment_self()), | ||
263 | None, | ||
264 | None, | ||
265 | false, | ||
266 | ); | ||
267 | use_trees.insert( | ||
268 | idx, | ||
269 | make::use_tree( | ||
270 | make::path_unqualified(make::path_segment_self()), | ||
271 | None, | ||
272 | None, | ||
273 | true, | ||
274 | ), | ||
275 | ); | ||
276 | continue; | ||
277 | } | ||
278 | } | ||
279 | let lhs = use_trees[idx].split_prefix(&lhs_prefix); | ||
280 | let rhs = rhs_t.split_prefix(&rhs_prefix); | ||
281 | match recursive_merge(&lhs, &rhs, merge) { | ||
282 | Some((_, count)) | ||
283 | if merge == MergeBehaviour::Last && use_trees.len() > 1 && count > 1 => | ||
284 | { | ||
285 | return None | ||
286 | } | ||
287 | Some((use_tree, _)) => use_trees[idx] = use_tree, | ||
288 | None => use_trees.insert(idx, rhs_t), | ||
289 | } | ||
290 | } | ||
291 | Err(idx) => { | ||
292 | use_trees.insert(idx, rhs_t); | ||
293 | } | ||
294 | } | ||
295 | } | ||
296 | let count = use_trees.len(); | ||
297 | let tl = make::use_tree_list(use_trees); | ||
298 | Some((lhs.with_use_tree_list(tl), count)) | ||
212 | } | 299 | } |
213 | 300 | ||
214 | /// Traverses both paths until they differ, returning the common prefix of both. | 301 | /// Traverses both paths until they differ, returning the common prefix of both. |
@@ -235,6 +322,23 @@ fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Pa | |||
235 | res | 322 | res |
236 | } | 323 | } |
237 | 324 | ||
325 | fn path_is_self(path: ast::Path) -> bool { | ||
326 | path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none() | ||
327 | } | ||
328 | |||
329 | fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> { | ||
330 | first_path(path).segment() | ||
331 | } | ||
332 | |||
333 | fn first_path(path: &ast::Path) -> ast::Path { | ||
334 | successors(Some(path.clone()), ast::Path::qualifier).last().unwrap() | ||
335 | } | ||
336 | |||
337 | fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone { | ||
338 | // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone | ||
339 | successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment())) | ||
340 | } | ||
341 | |||
238 | /// What type of merges are allowed. | 342 | /// What type of merges are allowed. |
239 | #[derive(Copy, Clone, PartialEq, Eq)] | 343 | #[derive(Copy, Clone, PartialEq, Eq)] |
240 | pub enum MergeBehaviour { | 344 | pub enum MergeBehaviour { |
@@ -279,19 +383,6 @@ impl ImportGroup { | |||
279 | } | 383 | } |
280 | } | 384 | } |
281 | 385 | ||
282 | fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> { | ||
283 | first_path(path).segment() | ||
284 | } | ||
285 | |||
286 | fn first_path(path: &ast::Path) -> ast::Path { | ||
287 | successors(Some(path.clone()), ast::Path::qualifier).last().unwrap() | ||
288 | } | ||
289 | |||
290 | fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone { | ||
291 | // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone | ||
292 | successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment())) | ||
293 | } | ||
294 | |||
295 | #[derive(PartialEq, Eq)] | 386 | #[derive(PartialEq, Eq)] |
296 | enum AddBlankLine { | 387 | enum AddBlankLine { |
297 | Before, | 388 | Before, |
@@ -594,7 +685,7 @@ use std::io;", | |||
594 | check_full( | 685 | check_full( |
595 | "std::foo::bar::Baz", | 686 | "std::foo::bar::Baz", |
596 | r"use std::foo::bar::Qux;", | 687 | r"use std::foo::bar::Qux;", |
597 | r"use std::foo::bar::{Qux, Baz};", | 688 | r"use std::foo::bar::{Baz, Qux};", |
598 | ) | 689 | ) |
599 | } | 690 | } |
600 | 691 | ||
@@ -603,7 +694,7 @@ use std::io;", | |||
603 | check_last( | 694 | check_last( |
604 | "std::foo::bar::Baz", | 695 | "std::foo::bar::Baz", |
605 | r"use std::foo::bar::Qux;", | 696 | r"use std::foo::bar::Qux;", |
606 | r"use std::foo::bar::{Qux, Baz};", | 697 | r"use std::foo::bar::{Baz, Qux};", |
607 | ) | 698 | ) |
608 | } | 699 | } |
609 | 700 | ||
@@ -612,7 +703,7 @@ use std::io;", | |||
612 | check_full( | 703 | check_full( |
613 | "std::foo::bar::Baz", | 704 | "std::foo::bar::Baz", |
614 | r"use std::foo::bar::{Qux, Quux};", | 705 | r"use std::foo::bar::{Qux, Quux};", |
615 | r"use std::foo::bar::{Qux, Quux, Baz};", | 706 | r"use std::foo::bar::{Baz, Quux, Qux};", |
616 | ) | 707 | ) |
617 | } | 708 | } |
618 | 709 | ||
@@ -621,7 +712,7 @@ use std::io;", | |||
621 | check_last( | 712 | check_last( |
622 | "std::foo::bar::Baz", | 713 | "std::foo::bar::Baz", |
623 | r"use std::foo::bar::{Qux, Quux};", | 714 | r"use std::foo::bar::{Qux, Quux};", |
624 | r"use std::foo::bar::{Qux, Quux, Baz};", | 715 | r"use std::foo::bar::{Baz, Quux, Qux};", |
625 | ) | 716 | ) |
626 | } | 717 | } |
627 | 718 | ||
@@ -630,7 +721,7 @@ use std::io;", | |||
630 | check_full( | 721 | check_full( |
631 | "std::foo::bar::Baz", | 722 | "std::foo::bar::Baz", |
632 | r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", | 723 | r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", |
633 | r"use std::foo::bar::{Qux, quux::{Fez, Fizz}, Baz};", | 724 | r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};", |
634 | ) | 725 | ) |
635 | } | 726 | } |
636 | 727 | ||
@@ -645,6 +736,15 @@ use std::foo::bar::{Qux, quux::{Fez, Fizz}};", | |||
645 | } | 736 | } |
646 | 737 | ||
647 | #[test] | 738 | #[test] |
739 | fn merge_groups_full_nested_deep() { | ||
740 | check_full( | ||
741 | "std::foo::bar::quux::Baz", | ||
742 | r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", | ||
743 | r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};", | ||
744 | ) | ||
745 | } | ||
746 | |||
747 | #[test] | ||
648 | fn merge_groups_skip_pub() { | 748 | fn merge_groups_skip_pub() { |
649 | check_full( | 749 | check_full( |
650 | "std::io", | 750 | "std::io", |
@@ -670,34 +770,63 @@ use std::io;", | |||
670 | check_last( | 770 | check_last( |
671 | "std::fmt::Result", | 771 | "std::fmt::Result", |
672 | r"use std::{fmt, io};", | 772 | r"use std::{fmt, io};", |
673 | r"use std::{self, fmt::Result}; | 773 | r"use std::fmt::{self, Result}; |
674 | use std::io;", | 774 | use std::io;", |
675 | ) | 775 | ) |
676 | } | 776 | } |
677 | 777 | ||
678 | #[test] | 778 | #[test] |
779 | fn merge_into_module_import() { | ||
780 | check_full( | ||
781 | "std::fmt::Result", | ||
782 | r"use std::{fmt, io};", | ||
783 | r"use std::{fmt::{self, Result}, io};", | ||
784 | ) | ||
785 | } | ||
786 | |||
787 | #[test] | ||
679 | fn merge_groups_self() { | 788 | fn merge_groups_self() { |
680 | check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};") | 789 | check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};") |
681 | } | 790 | } |
682 | 791 | ||
683 | #[test] | 792 | #[test] |
684 | fn merge_self_glob() { | 793 | fn merge_mod_into_glob() { |
685 | check_full( | 794 | check_full( |
686 | "token::TokenKind", | 795 | "token::TokenKind", |
687 | r"use token::TokenKind::*;", | 796 | r"use token::TokenKind::*;", |
688 | r"use token::TokenKind::{self::*, self};", | 797 | r"use token::TokenKind::{self::*, self};", |
689 | ) | 798 | ) |
799 | // FIXME: have it emit `use token::TokenKind::{self, *}`? | ||
800 | } | ||
801 | |||
802 | #[test] | ||
803 | fn merge_self_glob() { | ||
804 | check_full("self", r"use self::*;", r"use self::{self::*, self};") | ||
805 | // FIXME: have it emit `use {self, *}`? | ||
806 | } | ||
807 | |||
808 | #[test] | ||
809 | #[ignore] // FIXME: Support this | ||
810 | fn merge_partial_path() { | ||
811 | check_full( | ||
812 | "ast::Foo", | ||
813 | r"use syntax::{ast, algo};", | ||
814 | r"use syntax::{ast::{self, Foo}, algo};", | ||
815 | ) | ||
816 | } | ||
817 | |||
818 | #[test] | ||
819 | fn merge_glob_nested() { | ||
820 | check_full( | ||
821 | "foo::bar::quux::Fez", | ||
822 | r"use foo::bar::{Baz, quux::*;", | ||
823 | r"use foo::bar::{Baz, quux::{self::*, Fez}}", | ||
824 | ) | ||
690 | } | 825 | } |
691 | 826 | ||
692 | #[test] | 827 | #[test] |
693 | fn merge_last_too_long() { | 828 | fn merge_last_too_long() { |
694 | mark::check!(test_last_merge_too_long); | 829 | check_last("foo::bar", r"use foo::bar::baz::Qux;", r"use foo::bar::{self, baz::Qux};"); |
695 | check_last( | ||
696 | "foo::bar", | ||
697 | r"use foo::bar::baz::Qux;", | ||
698 | r"use foo::bar; | ||
699 | use foo::bar::baz::Qux;", | ||
700 | ); | ||
701 | } | 830 | } |
702 | 831 | ||
703 | #[test] | 832 | #[test] |
@@ -710,6 +839,42 @@ use foo::bar::baz::Qux;", | |||
710 | ); | 839 | ); |
711 | } | 840 | } |
712 | 841 | ||
842 | #[test] | ||
843 | fn merge_last_fail() { | ||
844 | check_merge_only_fail( | ||
845 | r"use foo::bar::{baz::{Qux, Fez}};", | ||
846 | r"use foo::bar::{baaz::{Quux, Feez}};", | ||
847 | MergeBehaviour::Last, | ||
848 | ); | ||
849 | } | ||
850 | |||
851 | #[test] | ||
852 | fn merge_last_fail1() { | ||
853 | check_merge_only_fail( | ||
854 | r"use foo::bar::{baz::{Qux, Fez}};", | ||
855 | r"use foo::bar::baaz::{Quux, Feez};", | ||
856 | MergeBehaviour::Last, | ||
857 | ); | ||
858 | } | ||
859 | |||
860 | #[test] | ||
861 | fn merge_last_fail2() { | ||
862 | check_merge_only_fail( | ||
863 | r"use foo::bar::baz::{Qux, Fez};", | ||
864 | r"use foo::bar::{baaz::{Quux, Feez}};", | ||
865 | MergeBehaviour::Last, | ||
866 | ); | ||
867 | } | ||
868 | |||
869 | #[test] | ||
870 | fn merge_last_fail3() { | ||
871 | check_merge_only_fail( | ||
872 | r"use foo::bar::baz::{Qux, Fez};", | ||
873 | r"use foo::bar::baaz::{Quux, Feez};", | ||
874 | MergeBehaviour::Last, | ||
875 | ); | ||
876 | } | ||
877 | |||
713 | fn check( | 878 | fn check( |
714 | path: &str, | 879 | path: &str, |
715 | ra_fixture_before: &str, | 880 | ra_fixture_before: &str, |
@@ -742,4 +907,23 @@ use foo::bar::baz::Qux;", | |||
742 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 907 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
743 | check(path, ra_fixture_before, ra_fixture_after, None) | 908 | check(path, ra_fixture_before, ra_fixture_after, None) |
744 | } | 909 | } |
910 | |||
911 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) { | ||
912 | let use0 = ast::SourceFile::parse(ra_fixture0) | ||
913 | .tree() | ||
914 | .syntax() | ||
915 | .descendants() | ||
916 | .find_map(ast::Use::cast) | ||
917 | .unwrap(); | ||
918 | |||
919 | let use1 = ast::SourceFile::parse(ra_fixture1) | ||
920 | .tree() | ||
921 | .syntax() | ||
922 | .descendants() | ||
923 | .find_map(ast::Use::cast) | ||
924 | .unwrap(); | ||
925 | |||
926 | let result = try_merge_imports(&use0, &use1, mb); | ||
927 | assert_eq!(result, None); | ||
928 | } | ||
745 | } | 929 | } |
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs index 8b1c65dd6..45cf31f13 100644 --- a/crates/syntax/src/ast/edit.rs +++ b/crates/syntax/src/ast/edit.rs | |||
@@ -347,6 +347,7 @@ impl ast::UseTree { | |||
347 | self.clone() | 347 | self.clone() |
348 | } | 348 | } |
349 | 349 | ||
350 | /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items. | ||
350 | #[must_use] | 351 | #[must_use] |
351 | pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree { | 352 | pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree { |
352 | let suffix = if self.path().as_ref() == Some(prefix) && self.use_tree_list().is_none() { | 353 | let suffix = if self.path().as_ref() == Some(prefix) && self.use_tree_list().is_none() { |