1 //! Handle syntactic aspects of inserting a new `use`.
4 iter::{self, successors},
8 edit::{AstNodeEdit, IndentLevel},
9 PathSegmentKind, VisibilityOwner,
13 ast::{self, make, AstNode},
14 InsertPosition, SyntaxElement, SyntaxNode,
18 pub enum ImportScope {
19 File(ast::SourceFile),
20 Module(ast::ItemList),
24 pub fn from(syntax: SyntaxNode) -> Option<Self> {
25 if let Some(module) = ast::Module::cast(syntax.clone()) {
26 module.item_list().map(ImportScope::Module)
27 } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) {
28 this.map(ImportScope::File)
30 ast::ItemList::cast(syntax).map(ImportScope::Module)
34 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
35 pub(crate) fn find_insert_use_container(
36 position: &SyntaxNode,
37 ctx: &crate::assist_context::AssistContext,
39 ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from)
42 pub(crate) fn as_syntax_node(&self) -> &SyntaxNode {
44 ImportScope::File(file) => file.syntax(),
45 ImportScope::Module(item_list) => item_list.syntax(),
49 fn indent_level(&self) -> IndentLevel {
51 ImportScope::File(file) => file.indent_level(),
52 ImportScope::Module(item_list) => item_list.indent_level() + 1,
56 fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
58 ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice),
59 // don't insert the imports before the item list's opening curly brace
60 ImportScope::Module(item_list) => item_list
62 .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around))
63 .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)),
67 fn insert_pos_after_inner_attribute(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
68 // check if the scope has inner attributes, we dont want to insert in front of them
72 // no flat_map here cause we want to short circuit the iterator
75 attr.as_ref().map(|attr| attr.kind() == ast::AttrKind::Inner).unwrap_or(false)
81 (InsertPosition::After(attr.syntax().clone().into()), AddBlankLine::BeforeTwice)
83 None => self.first_insert_pos(),
88 /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
89 pub(crate) fn insert_use(
92 merge: Option<MergeBehaviour>,
94 let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
95 // merge into existing imports if possible
96 if let Some(mb) = merge {
97 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
98 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
99 let to_delete: SyntaxElement = existing_use.syntax().clone().into();
100 let to_delete = to_delete.clone()..=to_delete;
101 let to_insert = iter::once(merged.syntax().clone().into());
102 return algo::replace_children(scope.as_syntax_node(), to_delete, to_insert);
107 // either we weren't allowed to merge or there is no import that fits the merge conditions
108 // so look for the place we have to insert to
109 let (insert_position, add_blank) = find_insert_position(scope, path);
111 let to_insert: Vec<SyntaxElement> = {
112 let mut buf = Vec::new();
115 AddBlankLine::Before | AddBlankLine::Around => {
116 buf.push(make::tokens::single_newline().into())
118 AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()),
122 if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
123 buf.push(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into());
125 buf.push(use_item.syntax().clone().into());
128 AddBlankLine::After | AddBlankLine::Around => {
129 buf.push(make::tokens::single_newline().into())
131 AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()),
138 algo::insert_children(scope.as_syntax_node(), insert_position, to_insert)
141 fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
143 (None, None) => true,
144 // FIXME: Don't use the string representation to check for equality
145 // spaces inside of the node would break this comparison
146 (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(),
151 pub(crate) fn try_merge_imports(
154 merge_behaviour: MergeBehaviour,
155 ) -> Option<ast::Use> {
156 // don't merge imports with different visibilities
157 if !eq_visibility(lhs.visibility(), rhs.visibility()) {
160 let lhs_tree = lhs.use_tree()?;
161 let rhs_tree = rhs.use_tree()?;
162 let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behaviour)?;
163 Some(lhs.with_use_tree(merged))
166 pub(crate) fn try_merge_trees(
169 merge: MergeBehaviour,
170 ) -> Option<ast::UseTree> {
171 let lhs_path = lhs.path()?;
172 let rhs_path = rhs.path()?;
174 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
175 let lhs = lhs.split_prefix(&lhs_prefix);
176 let rhs = rhs.split_prefix(&rhs_prefix);
177 recursive_merge(&lhs, &rhs, merge).map(|(merged, _)| merged)
180 /// Recursively "zips" together lhs and rhs.
184 merge: MergeBehaviour,
185 ) -> Option<(ast::UseTree, bool)> {
186 let mut use_trees = lhs
189 .flat_map(|list| list.use_trees())
190 // check if any of the use trees are nested, if they are and the behaviour is `last` we are not allowed to merge this
191 // so early exit the iterator by using Option's Intoiterator impl
192 .map(|tree| match merge == MergeBehaviour::Last && tree.use_tree_list().is_some() {
196 .collect::<Option<Vec<_>>>()?;
197 use_trees.sort_unstable_by(|a, b| path_cmp_opt(a.path(), b.path()));
198 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
199 let rhs_path = rhs_t.path();
200 match use_trees.binary_search_by(|p| path_cmp_opt(p.path(), rhs_path.clone())) {
202 let lhs_t = &mut use_trees[idx];
203 let lhs_path = lhs_t.path()?;
204 let rhs_path = rhs_path?;
205 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
206 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
207 let tree_is_self = |tree: ast::UseTree| {
208 tree.path().as_ref().map(path_is_self).unwrap_or(false)
210 // check if only one of the two trees has a tree list, and whether that then contains `self` or not.
211 // 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`
212 let tree_contains_self = |tree: &ast::UseTree| {
214 .map(|tree_list| tree_list.use_trees().any(tree_is_self))
217 match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) {
218 (true, false) => continue,
226 // glob imports arent part of the use-tree lists so we need to special handle them here as well
227 // this special handling is only required for when we merge a module import into a glob import of said module
228 // see the `merge_self_glob` or `merge_mod_into_glob` tests
229 if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() {
230 *lhs_t = make::use_tree(
231 make::path_unqualified(make::path_segment_self()),
236 use_trees.insert(idx, make::glob_use_tree());
240 let lhs = lhs_t.split_prefix(&lhs_prefix);
241 let rhs = rhs_t.split_prefix(&rhs_prefix);
242 let this_has_children = use_trees.len() > 0;
243 match recursive_merge(&lhs, &rhs, merge) {
244 Some((_, has_multiple_children))
245 if merge == MergeBehaviour::Last
247 && has_multiple_children =>
251 Some((use_tree, _)) => use_trees[idx] = use_tree,
252 None => use_trees.insert(idx, rhs_t),
256 if merge == MergeBehaviour::Last
257 && use_trees.len() > 0
258 && rhs_t.use_tree_list().is_some() =>
263 use_trees.insert(idx, rhs_t);
267 let has_multiple_children = use_trees.len() > 1;
268 Some((lhs.with_use_tree_list(make::use_tree_list(use_trees)), has_multiple_children))
271 /// Traverses both paths until they differ, returning the common prefix of both.
272 fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
274 let mut lhs_curr = first_path(&lhs);
275 let mut rhs_curr = first_path(&rhs);
277 match (lhs_curr.segment(), rhs_curr.segment()) {
278 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
281 res = Some((lhs_curr.clone(), rhs_curr.clone()));
283 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
284 Some((lhs, rhs)) => {
293 fn path_is_self(path: &ast::Path) -> bool {
294 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
298 fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> {
299 first_path(path).segment()
302 fn first_path(path: &ast::Path) -> ast::Path {
303 successors(Some(path.clone()), ast::Path::qualifier).last().unwrap()
306 fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
307 // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone
308 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment()))
311 /// Orders paths in the following way:
312 /// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
313 // FIXME: rustfmt sort lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
314 // which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
315 // Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
316 fn path_cmp(a: &ast::Path, b: &ast::Path) -> Ordering {
317 match (path_is_self(a), path_is_self(b)) {
318 (true, true) => Ordering::Equal,
319 (true, false) => Ordering::Less,
320 (false, true) => Ordering::Greater,
322 let a = segment_iter(a);
323 let b = segment_iter(b);
324 // cmp_by would be useful for us here but that is currently unstable
325 // cmp doesnt work due the lifetimes on text's return type
327 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
328 .find_map(|(a, b)| match a.text().cmp(b.text()) {
329 ord @ Ordering::Greater | ord @ Ordering::Less => Some(ord),
330 Ordering::Equal => None,
332 .unwrap_or(Ordering::Equal)
337 fn path_cmp_opt(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
339 (None, None) => Ordering::Equal,
340 (None, Some(_)) => Ordering::Less,
341 (Some(_), None) => Ordering::Greater,
342 (Some(a), Some(b)) => path_cmp(&a, &b),
346 /// What type of merges are allowed.
347 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
348 pub enum MergeBehaviour {
349 /// Merge everything together creating deeply nested imports.
351 /// Only merge the last import level, doesn't allow import nesting.
355 #[derive(Eq, PartialEq, PartialOrd, Ord)]
357 // the order here defines the order of new group inserts
366 fn new(path: &ast::Path) -> ImportGroup {
367 let default = ImportGroup::ExternCrate;
369 let first_segment = match first_segment(path) {
371 None => return default,
374 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
376 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
377 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
378 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
379 PathSegmentKind::Name(name) => match name.text().as_str() {
380 "std" => ImportGroup::Std,
381 "core" => ImportGroup::Std,
382 // FIXME: can be ThisModule as well
383 _ => ImportGroup::ExternCrate,
385 PathSegmentKind::Type { .. } => unreachable!(),
390 #[derive(PartialEq, Eq)]
399 fn find_insert_position(
401 insert_path: ast::Path,
402 ) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
403 let group = ImportGroup::new(&insert_path);
404 let path_node_iter = scope
407 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
408 .flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node)));
409 // Iterator that discards anything thats not in the required grouping
410 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
411 let group_iter = path_node_iter
413 .skip_while(|(path, _)| ImportGroup::new(path) != group)
414 .take_while(|(path, _)| ImportGroup::new(path) == group);
416 let segments = segment_iter(&insert_path);
417 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
419 // find the element that would come directly after our new import
421 group_iter.inspect(|(_, node)| last = Some(node.clone())).find(|(path, _)| {
422 let check_segments = segment_iter(&path);
426 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
427 .all(|(l, r)| l.text() <= r.text())
430 // insert our import before that element
431 Some((_, node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
432 // there is no element after our new import, so append it to the end of the group
434 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
435 // the group we were looking for actually doesnt exist, so insert
437 // similar concept here to the `last` from above
439 // find the group that comes after where we want to insert
440 let post_group = path_node_iter
441 .inspect(|(_, node)| last = Some(node.clone()))
442 .find(|(p, _)| ImportGroup::new(p) > group);
445 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
447 // there is no such group, so append after the last one
450 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
452 // there are no imports in this file at all
453 None => scope.insert_pos_after_inner_attribute(),
465 use test_utils::assert_eq_text;
522 fn insert_middle_nested() {
527 use std::bar::{D, Z}; // example of weird imports due to user
533 use std::bar::{D, Z}; // example of weird imports due to user
540 fn insert_middle_groups() {
560 fn insert_first_matching_group() {
584 fn insert_missing_group_std() {
599 fn insert_missing_group_self() {
614 fn insert_no_imports() {
625 fn insert_empty_file() {
626 // empty files will get two trailing newlines
627 // this is due to the test case insert_no_imports above
638 fn insert_after_inner_attr() {
641 r"#![allow(unused_imports)]",
642 r"#![allow(unused_imports)]
649 fn insert_after_inner_attr2() {
652 r"#![allow(unused_imports)]
655 r"#![allow(unused_imports)]
665 check_last("std::io", r"use std::fmt;", r"use std::{fmt, io};")
669 fn merge_groups_last() {
672 r"use std::fmt::{Result, Display};",
673 r"use std::fmt::{Result, Display};
679 fn merge_groups_full() {
682 r"use std::fmt::{Result, Display};",
683 r"use std::{fmt::{Result, Display}, io};",
688 fn merge_groups_long_full() {
690 "std::foo::bar::Baz",
691 r"use std::foo::bar::Qux;",
692 r"use std::foo::bar::{Baz, Qux};",
697 fn merge_groups_long_last() {
699 "std::foo::bar::Baz",
700 r"use std::foo::bar::Qux;",
701 r"use std::foo::bar::{Baz, Qux};",
706 fn merge_groups_long_full_list() {
708 "std::foo::bar::Baz",
709 r"use std::foo::bar::{Qux, Quux};",
710 r"use std::foo::bar::{Baz, Quux, Qux};",
715 fn merge_groups_long_last_list() {
717 "std::foo::bar::Baz",
718 r"use std::foo::bar::{Qux, Quux};",
719 r"use std::foo::bar::{Baz, Quux, Qux};",
724 fn merge_groups_long_full_nested() {
726 "std::foo::bar::Baz",
727 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
728 r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};",
733 fn merge_groups_long_last_nested() {
735 "std::foo::bar::Baz",
736 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
737 r"use std::foo::bar::Baz;
738 use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
743 fn merge_groups_full_nested_deep() {
745 "std::foo::bar::quux::Baz",
746 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
747 r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};",
752 fn merge_groups_skip_pub() {
755 r"pub use std::fmt::{Result, Display};",
756 r"pub use std::fmt::{Result, Display};
762 fn merge_groups_skip_pub_crate() {
765 r"pub(crate) use std::fmt::{Result, Display};",
766 r"pub(crate) use std::fmt::{Result, Display};
772 #[ignore] // FIXME: Support this
773 fn split_out_merge() {
776 r"use std::{fmt, io};",
777 r"use std::fmt::{self, Result};
783 fn merge_into_module_import() {
786 r"use std::{fmt, io};",
787 r"use std::{fmt::{self, Result}, io};",
792 fn merge_groups_self() {
793 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
797 fn merge_mod_into_glob() {
800 r"use token::TokenKind::*;",
801 r"use token::TokenKind::{*, self};",
803 // FIXME: have it emit `use token::TokenKind::{self, *}`?
807 fn merge_self_glob() {
808 check_full("self", r"use self::*;", r"use self::{*, self};")
809 // FIXME: have it emit `use {self, *}`?
813 #[ignore] // FIXME: Support this
814 fn merge_partial_path() {
817 r"use syntax::{ast, algo};",
818 r"use syntax::{ast::{self, Foo}, algo};",
823 fn merge_glob_nested() {
825 "foo::bar::quux::Fez",
826 r"use foo::bar::{Baz, quux::*};",
827 r"use foo::bar::{Baz, quux::{self::*, Fez}};",
832 fn merge_last_too_long() {
833 check_last("foo::bar", r"use foo::bar::baz::Qux;", r"use foo::bar::{self, baz::Qux};");
837 fn insert_short_before_long() {
840 r"use foo::bar::baz::Qux;",
842 use foo::bar::baz::Qux;",
847 fn merge_last_fail() {
848 check_merge_only_fail(
849 r"use foo::bar::{baz::{Qux, Fez}};",
850 r"use foo::bar::{baaz::{Quux, Feez}};",
851 MergeBehaviour::Last,
856 fn merge_last_fail1() {
857 check_merge_only_fail(
858 r"use foo::bar::{baz::{Qux, Fez}};",
859 r"use foo::bar::baaz::{Quux, Feez};",
860 MergeBehaviour::Last,
865 fn merge_last_fail2() {
866 check_merge_only_fail(
867 r"use foo::bar::baz::{Qux, Fez};",
868 r"use foo::bar::{baaz::{Quux, Feez}};",
869 MergeBehaviour::Last,
874 fn merge_last_fail3() {
875 check_merge_only_fail(
876 r"use foo::bar::baz::{Qux, Fez};",
877 r"use foo::bar::baaz::{Quux, Feez};",
878 MergeBehaviour::Last,
884 ra_fixture_before: &str,
885 ra_fixture_after: &str,
886 mb: Option<MergeBehaviour>,
888 let file = super::ImportScope::from(
889 ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(),
892 let path = ast::SourceFile::parse(&format!("use {};", path))
896 .find_map(ast::Path::cast)
899 let result = insert_use(&file, path, mb).to_string();
900 assert_eq_text!(&result, ra_fixture_after);
903 fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
904 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Full))
907 fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
908 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Last))
911 fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
912 check(path, ra_fixture_before, ra_fixture_after, None)
915 fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) {
916 let use0 = ast::SourceFile::parse(ra_fixture0)
920 .find_map(ast::Use::cast)
923 let use1 = ast::SourceFile::parse(ra_fixture1)
927 .find_map(ast::Use::cast)
930 let result = try_merge_imports(&use0, &use1, mb);
931 assert_eq!(result.map(|u| u.to_string()), None);