From f4ae3650d855554575f866264a3c8197dfd12835 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 24 Nov 2020 23:25:13 +0200 Subject: Extract the import code into the shared module --- crates/assists/Cargo.toml | 3 +- crates/assists/src/assist_config.rs | 3 +- crates/assists/src/ast_transform.rs | 3 +- crates/assists/src/handlers/auto_import.rs | 5 +- .../handlers/extract_struct_from_enum_variant.rs | 16 +- crates/assists/src/handlers/fill_match_arms.rs | 9 +- .../src/handlers/generate_from_impl_for_enum.rs | 3 +- crates/assists/src/handlers/merge_imports.rs | 6 +- crates/assists/src/handlers/qualify_path.rs | 2 +- .../handlers/replace_derive_with_manual_impl.rs | 4 +- .../handlers/replace_qualified_name_with_use.rs | 8 +- crates/assists/src/utils.rs | 200 +--- crates/assists/src/utils/insert_use.rs | 1204 -------------------- 13 files changed, 32 insertions(+), 1434 deletions(-) delete mode 100644 crates/assists/src/utils/insert_use.rs (limited to 'crates/assists') diff --git a/crates/assists/Cargo.toml b/crates/assists/Cargo.toml index 108f656e9..91d9b6e3f 100644 --- a/crates/assists/Cargo.toml +++ b/crates/assists/Cargo.toml @@ -12,7 +12,7 @@ doctest = false [dependencies] rustc-hash = "1.1.0" itertools = "0.9.0" -either = "1.5.3" +either = "1.6.1" stdx = { path = "../stdx", version = "0.0.0" } syntax = { path = "../syntax", version = "0.0.0" } @@ -21,3 +21,4 @@ profile = { path = "../profile", version = "0.0.0" } ide_db = { path = "../ide_db", version = "0.0.0" } hir = { path = "../hir", version = "0.0.0" } test_utils = { path = "../test_utils", version = "0.0.0" } +ide_helpers = { path = "../ide_helpers", version = "0.0.0" } diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs index b24527ec4..6d9934e93 100644 --- a/crates/assists/src/assist_config.rs +++ b/crates/assists/src/assist_config.rs @@ -5,8 +5,9 @@ //! assists if we are allowed to. use hir::PrefixKind; +use ide_helpers::insert_use::MergeBehaviour; -use crate::{utils::MergeBehaviour, AssistKind}; +use crate::AssistKind; #[derive(Clone, Debug, PartialEq, Eq)] pub struct AssistConfig { diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs index ac72f3f02..95b060599 100644 --- a/crates/assists/src/ast_transform.rs +++ b/crates/assists/src/ast_transform.rs @@ -1,5 +1,6 @@ //! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined. use hir::{HirDisplay, PathResolution, SemanticsScope}; +use ide_helpers::mod_path_to_ast; use rustc_hash::FxHashMap; use syntax::{ algo::SyntaxRewriter, @@ -7,8 +8,6 @@ use syntax::{ SyntaxNode, }; -use crate::utils::mod_path_to_ast; - pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { SyntaxRewriter::from_fn(|element| match element { syntax::SyntaxElement::Node(n) => { diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index d665837a2..0b2d508d5 100644 --- a/crates/assists/src/handlers/auto_import.rs +++ b/crates/assists/src/handlers/auto_import.rs @@ -1,8 +1,11 @@ +use ide_helpers::{ + insert_use::{insert_use, ImportScope}, + mod_path_to_ast, +}; use syntax::ast; use crate::{ utils::import_assets::{ImportAssets, ImportCandidate}, - utils::{insert_use, mod_path_to_ast, ImportScope}, AssistContext, AssistId, AssistKind, Assists, GroupLabel, }; diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs index cac77c49b..fddd5354a 100644 --- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs @@ -3,6 +3,10 @@ use std::iter; use either::Either; use hir::{AsName, EnumVariant, Module, ModuleDef, Name}; use ide_db::{defs::Definition, search::Reference, RootDatabase}; +use ide_helpers::{ + insert_use::{insert_use, ImportScope}, + mod_path_to_ast, +}; use rustc_hash::{FxHashMap, FxHashSet}; use syntax::{ algo::{find_node_at_offset, SyntaxRewriter}, @@ -10,10 +14,7 @@ use syntax::{ SourceFile, SyntaxElement, SyntaxNode, T, }; -use crate::{ - utils::{insert_use, mod_path_to_ast, ImportScope}, - AssistContext, AssistId, AssistKind, Assists, -}; +use crate::{AssistContext, AssistId, AssistKind, Assists}; // Assist: extract_struct_from_enum_variant // @@ -236,10 +237,9 @@ fn update_reference( #[cfg(test)] mod tests { - use crate::{ - tests::{check_assist, check_assist_not_applicable}, - utils::FamousDefs, - }; + use ide_helpers::FamousDefs; + + use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; diff --git a/crates/assists/src/handlers/fill_match_arms.rs b/crates/assists/src/handlers/fill_match_arms.rs index eda45f5b3..bd42e0f16 100644 --- a/crates/assists/src/handlers/fill_match_arms.rs +++ b/crates/assists/src/handlers/fill_match_arms.rs @@ -2,12 +2,13 @@ use std::iter; use hir::{Adt, HasSource, ModuleDef, Semantics}; use ide_db::RootDatabase; +use ide_helpers::{mod_path_to_ast, FamousDefs}; use itertools::Itertools; use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; use test_utils::mark; use crate::{ - utils::{mod_path_to_ast, render_snippet, Cursor, FamousDefs}, + utils::{render_snippet, Cursor}, AssistContext, AssistId, AssistKind, Assists, }; @@ -212,12 +213,10 @@ fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> O #[cfg(test)] mod tests { + use ide_helpers::FamousDefs; use test_utils::mark; - use crate::{ - tests::{check_assist, check_assist_not_applicable, check_assist_target}, - utils::FamousDefs, - }; + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; use super::fill_match_arms; diff --git a/crates/assists/src/handlers/generate_from_impl_for_enum.rs b/crates/assists/src/handlers/generate_from_impl_for_enum.rs index 674e5a175..01b14d94d 100644 --- a/crates/assists/src/handlers/generate_from_impl_for_enum.rs +++ b/crates/assists/src/handlers/generate_from_impl_for_enum.rs @@ -1,8 +1,9 @@ use ide_db::RootDatabase; +use ide_helpers::FamousDefs; use syntax::ast::{self, AstNode, NameOwner}; use test_utils::mark; -use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists}; +use crate::{AssistContext, AssistId, AssistKind, Assists}; // Assist: generate_from_impl_for_enum // diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs index fd9c9e03c..8207f0e6e 100644 --- a/crates/assists/src/handlers/merge_imports.rs +++ b/crates/assists/src/handlers/merge_imports.rs @@ -1,3 +1,4 @@ +use ide_helpers::insert_use::{try_merge_imports, try_merge_trees, MergeBehaviour}; use syntax::{ algo::{neighbor, SyntaxRewriter}, ast, AstNode, @@ -5,10 +6,7 @@ use syntax::{ use crate::{ assist_context::{AssistContext, Assists}, - utils::{ - insert_use::{try_merge_imports, try_merge_trees}, - next_prev, MergeBehaviour, - }, + utils::next_prev, AssistId, AssistKind, }; diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs index d5bc4e574..c0ee7ea0b 100644 --- a/crates/assists/src/handlers/qualify_path.rs +++ b/crates/assists/src/handlers/qualify_path.rs @@ -2,6 +2,7 @@ use std::iter; use hir::AsName; use ide_db::RootDatabase; +use ide_helpers::mod_path_to_ast; use syntax::{ ast, ast::{make, ArgListOwner}, @@ -12,7 +13,6 @@ use test_utils::mark; use crate::{ assist_context::{AssistContext, Assists}, utils::import_assets::{ImportAssets, ImportCandidate}, - utils::mod_path_to_ast, AssistId, AssistKind, GroupLabel, }; diff --git a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs index 453a6cebf..fe262377c 100644 --- a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs @@ -1,4 +1,5 @@ use ide_db::imports_locator; +use ide_helpers::mod_path_to_ast; use itertools::Itertools; use syntax::{ ast::{self, make, AstNode}, @@ -10,8 +11,7 @@ use syntax::{ use crate::{ assist_context::{AssistBuilder, AssistContext, Assists}, utils::{ - add_trait_assoc_items_to_impl, filter_assoc_items, mod_path_to_ast, render_snippet, Cursor, - DefaultMethods, + add_trait_assoc_items_to_impl, filter_assoc_items, render_snippet, Cursor, DefaultMethods, }, AssistId, AssistKind, }; 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 a66db9ae3..9b8caacd9 100644 --- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs @@ -1,10 +1,8 @@ +use ide_helpers::insert_use::{insert_use, ImportScope}; use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode}; use test_utils::mark; -use crate::{ - utils::{insert_use, ImportScope}, - AssistContext, AssistId, AssistKind, Assists, -}; +use crate::{AssistContext, AssistId, AssistKind, Assists}; // Assist: replace_qualified_name_with_use // @@ -53,7 +51,7 @@ pub(crate) fn replace_qualified_name_with_use( ) } -/// Adds replacements to `re` that shorten `path` in all descendants of `node`. +/// Adds replacements to `re` that shorten `path` in all descendants of `node`.g fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) { for child in node.children() { match_ast! { diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 048746587..01f5c291f 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs @@ -1,10 +1,9 @@ //! Assorted functions shared by several assists. -pub(crate) mod insert_use; pub(crate) mod import_assets; use std::ops; -use hir::{Crate, Enum, HasSource, Module, ScopeDef, Semantics, Trait}; +use hir::HasSource; use ide_db::RootDatabase; use itertools::Itertools; use syntax::{ @@ -22,30 +21,6 @@ use crate::{ ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, }; -pub use insert_use::{insert_use, ImportScope, MergeBehaviour}; - -pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { - let _p = profile::span("mod_path_to_ast"); - let mut segments = Vec::new(); - let mut is_abs = false; - match path.kind { - hir::PathKind::Plain => {} - hir::PathKind::Super(0) => segments.push(make::path_segment_self()), - hir::PathKind::Super(n) => segments.extend((0..n).map(|_| make::path_segment_super())), - hir::PathKind::DollarCrate(_) | hir::PathKind::Crate => { - segments.push(make::path_segment_crate()) - } - hir::PathKind::Abs => is_abs = true, - } - - segments.extend( - path.segments - .iter() - .map(|segment| make::path_segment(make::name_ref(&segment.to_string()))), - ); - make::path_from_segments(segments, is_abs) -} - pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { extract_trivial_expression(&block) .filter(|expr| !expr.syntax().text().contains_char('\n')) @@ -260,179 +235,6 @@ fn invert_special_case(expr: &ast::Expr) -> Option { } } -/// Helps with finding well-know things inside the standard library. This is -/// somewhat similar to the known paths infra inside hir, but it different; We -/// want to make sure that IDE specific paths don't become interesting inside -/// the compiler itself as well. -pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option); - -#[allow(non_snake_case)] -impl FamousDefs<'_, '_> { - pub const FIXTURE: &'static str = r#"//- /libcore.rs crate:core -pub mod convert { - pub trait From { - fn from(t: T) -> Self; - } -} - -pub mod default { - pub trait Default { - fn default() -> Self; - } -} - -pub mod iter { - pub use self::traits::{collect::IntoIterator, iterator::Iterator}; - mod traits { - pub(crate) mod iterator { - use crate::option::Option; - pub trait Iterator { - type Item; - fn next(&mut self) -> Option; - fn by_ref(&mut self) -> &mut Self { - self - } - fn take(self, n: usize) -> crate::iter::Take { - crate::iter::Take { inner: self } - } - } - - impl Iterator for &mut I { - type Item = I::Item; - fn next(&mut self) -> Option { - (**self).next() - } - } - } - pub(crate) mod collect { - pub trait IntoIterator { - type Item; - } - } - } - - pub use self::sources::*; - pub(crate) mod sources { - use super::Iterator; - use crate::option::Option::{self, *}; - pub struct Repeat { - element: A, - } - - pub fn repeat(elt: T) -> Repeat { - Repeat { element: elt } - } - - impl Iterator for Repeat { - type Item = A; - - fn next(&mut self) -> Option { - None - } - } - } - - pub use self::adapters::*; - pub(crate) mod adapters { - use super::Iterator; - use crate::option::Option::{self, *}; - pub struct Take { pub(crate) inner: I } - impl Iterator for Take where I: Iterator { - type Item = ::Item; - fn next(&mut self) -> Option<::Item> { - None - } - } - } -} - -pub mod option { - pub enum Option { None, Some(T)} -} - -pub mod prelude { - pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}, default::Default}; -} -#[prelude_import] -pub use prelude::*; -"#; - - pub fn core(&self) -> Option { - self.find_crate("core") - } - - pub(crate) fn core_convert_From(&self) -> Option { - self.find_trait("core:convert:From") - } - - pub(crate) fn core_option_Option(&self) -> Option { - self.find_enum("core:option:Option") - } - - pub fn core_default_Default(&self) -> Option { - self.find_trait("core:default:Default") - } - - pub fn core_iter_Iterator(&self) -> Option { - self.find_trait("core:iter:traits:iterator:Iterator") - } - - pub fn core_iter(&self) -> Option { - self.find_module("core:iter") - } - - fn find_trait(&self, path: &str) -> Option { - match self.find_def(path)? { - hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), - _ => None, - } - } - - fn find_enum(&self, path: &str) -> Option { - match self.find_def(path)? { - hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it), - _ => None, - } - } - - fn find_module(&self, path: &str) -> Option { - match self.find_def(path)? { - hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(it)) => Some(it), - _ => None, - } - } - - fn find_crate(&self, name: &str) -> Option { - let krate = self.1?; - let db = self.0.db; - let res = - krate.dependencies(db).into_iter().find(|dep| dep.name.to_string() == name)?.krate; - Some(res) - } - - fn find_def(&self, path: &str) -> Option { - let db = self.0.db; - let mut path = path.split(':'); - let trait_ = path.next_back()?; - let std_crate = path.next()?; - let std_crate = self.find_crate(std_crate)?; - let mut module = std_crate.root_module(db); - for segment in path { - module = module.children(db).find_map(|child| { - let name = child.name(db)?; - if name.to_string() == segment { - Some(child) - } else { - None - } - })?; - } - let def = - module.scope(db, None).into_iter().find(|(name, _def)| name.to_string() == trait_)?.1; - Some(def) - } -} - pub(crate) fn next_prev() -> impl Iterator { [Direction::Next, Direction::Prev].iter().copied() } diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs deleted file mode 100644 index 304adb93d..000000000 --- a/crates/assists/src/utils/insert_use.rs +++ /dev/null @@ -1,1204 +0,0 @@ -//! Handle syntactic aspects of inserting a new `use`. -use std::{cmp::Ordering, iter::successors}; - -use hir::Semantics; -use ide_db::RootDatabase; -use itertools::{EitherOrBoth, Itertools}; -use syntax::{ - algo::SyntaxRewriter, - ast::{ - self, - edit::{AstNodeEdit, IndentLevel}, - make, AstNode, PathSegmentKind, VisibilityOwner, - }, - AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken, -}; -use test_utils::mark; - -#[derive(Debug, Clone)] -pub enum ImportScope { - File(ast::SourceFile), - Module(ast::ItemList), -} - -impl ImportScope { - pub(crate) fn from(syntax: SyntaxNode) -> Option { - if let Some(module) = ast::Module::cast(syntax.clone()) { - module.item_list().map(ImportScope::Module) - } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) { - this.map(ImportScope::File) - } else { - ast::ItemList::cast(syntax).map(ImportScope::Module) - } - } - - /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. - pub fn find_insert_use_container( - position: &SyntaxNode, - sema: &Semantics<'_, RootDatabase>, - ) -> Option { - sema.ancestors_with_macros(position.clone()).find_map(Self::from) - } - - pub fn as_syntax_node(&self) -> &SyntaxNode { - match self { - ImportScope::File(file) => file.syntax(), - ImportScope::Module(item_list) => item_list.syntax(), - } - } - - fn indent_level(&self) -> IndentLevel { - match self { - ImportScope::File(file) => file.indent_level(), - ImportScope::Module(item_list) => item_list.indent_level() + 1, - } - } - - fn first_insert_pos(&self) -> (InsertPosition, AddBlankLine) { - match self { - ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice), - // don't insert the imports before the item list's opening curly brace - ImportScope::Module(item_list) => item_list - .l_curly_token() - .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around)) - .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)), - } - } - - fn insert_pos_after_last_inner_element(&self) -> (InsertPosition, AddBlankLine) { - self.as_syntax_node() - .children_with_tokens() - .filter(|child| match child { - NodeOrToken::Node(node) => is_inner_attribute(node.clone()), - NodeOrToken::Token(token) => is_inner_comment(token.clone()), - }) - .last() - .map(|last_inner_element| { - (InsertPosition::After(last_inner_element.into()), AddBlankLine::BeforeTwice) - }) - .unwrap_or_else(|| self.first_insert_pos()) - } -} - -fn is_inner_attribute(node: SyntaxNode) -> bool { - ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) -} - -fn is_inner_comment(token: SyntaxToken) -> bool { - ast::Comment::cast(token).and_then(|comment| comment.kind().doc) - == Some(ast::CommentPlacement::Inner) -} - -/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. -pub fn insert_use<'a>( - scope: &ImportScope, - path: ast::Path, - merge: Option, -) -> SyntaxRewriter<'a> { - let _p = profile::span("insert_use"); - let mut rewriter = SyntaxRewriter::default(); - let use_item = make::use_(make::use_tree(path.clone(), None, None, false)); - // merge into existing imports if possible - if let Some(mb) = merge { - for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { - if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { - rewriter.replace(existing_use.syntax(), merged.syntax()); - return rewriter; - } - } - } - - // either we weren't allowed to merge or there is no import that fits the merge conditions - // so look for the place we have to insert to - let (insert_position, add_blank) = find_insert_position(scope, path); - - let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize { - Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into()) - } else { - None - }; - - let to_insert: Vec = { - let mut buf = Vec::new(); - - match add_blank { - AddBlankLine::Before | AddBlankLine::Around => { - buf.push(make::tokens::single_newline().into()) - } - AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()), - _ => (), - } - - if add_blank.has_before() { - if let Some(indent) = indent.clone() { - mark::hit!(insert_use_indent_before); - buf.push(indent); - } - } - - buf.push(use_item.syntax().clone().into()); - - match add_blank { - AddBlankLine::After | AddBlankLine::Around => { - buf.push(make::tokens::single_newline().into()) - } - AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()), - _ => (), - } - - // only add indentation *after* our stuff if there's another node directly after it - if add_blank.has_after() && matches!(insert_position, InsertPosition::Before(_)) { - if let Some(indent) = indent { - mark::hit!(insert_use_indent_after); - buf.push(indent); - } - } else if add_blank.has_after() && matches!(insert_position, InsertPosition::After(_)) { - mark::hit!(insert_use_no_indent_after); - } - - buf - }; - - match insert_position { - InsertPosition::First => { - rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert) - } - InsertPosition::Last => return rewriter, // actually unreachable - InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert), - InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert), - } - rewriter -} - -fn eq_visibility(vis0: Option, vis1: Option) -> bool { - match (vis0, vis1) { - (None, None) => true, - // FIXME: Don't use the string representation to check for equality - // spaces inside of the node would break this comparison - (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(), - _ => false, - } -} - -pub(crate) fn try_merge_imports( - lhs: &ast::Use, - rhs: &ast::Use, - merge_behaviour: MergeBehaviour, -) -> Option { - // don't merge imports with different visibilities - if !eq_visibility(lhs.visibility(), rhs.visibility()) { - return None; - } - let lhs_tree = lhs.use_tree()?; - let rhs_tree = rhs.use_tree()?; - let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behaviour)?; - Some(lhs.with_use_tree(merged)) -} - -pub(crate) fn try_merge_trees( - lhs: &ast::UseTree, - rhs: &ast::UseTree, - merge: MergeBehaviour, -) -> Option { - let lhs_path = lhs.path()?; - let rhs_path = rhs.path()?; - - let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; - let (lhs, rhs) = if is_simple_path(lhs) - && is_simple_path(rhs) - && lhs_path == lhs_prefix - && rhs_path == rhs_prefix - { - (lhs.clone(), rhs.clone()) - } else { - (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix)) - }; - recursive_merge(&lhs, &rhs, merge) -} - -/// Recursively "zips" together lhs and rhs. -fn recursive_merge( - lhs: &ast::UseTree, - rhs: &ast::UseTree, - merge: MergeBehaviour, -) -> Option { - let mut use_trees = lhs - .use_tree_list() - .into_iter() - .flat_map(|list| list.use_trees()) - // we use Option here to early return from this function(this is not the same as a `filter` op) - .map(|tree| match merge.is_tree_allowed(&tree) { - true => Some(tree), - false => None, - }) - .collect::>>()?; - use_trees.sort_unstable_by(|a, b| path_cmp_for_sort(a.path(), b.path())); - for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) { - if !merge.is_tree_allowed(&rhs_t) { - return None; - } - let rhs_path = rhs_t.path(); - match use_trees.binary_search_by(|lhs_t| { - let (lhs_t, rhs_t) = match lhs_t - .path() - .zip(rhs_path.clone()) - .and_then(|(lhs, rhs)| common_prefix(&lhs, &rhs)) - { - Some((lhs_p, rhs_p)) => (lhs_t.split_prefix(&lhs_p), rhs_t.split_prefix(&rhs_p)), - None => (lhs_t.clone(), rhs_t.clone()), - }; - - path_cmp_bin_search(lhs_t.path(), rhs_t.path()) - }) { - Ok(idx) => { - let lhs_t = &mut use_trees[idx]; - let lhs_path = lhs_t.path()?; - let rhs_path = rhs_path?; - let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; - if lhs_prefix == lhs_path && rhs_prefix == rhs_path { - let tree_is_self = |tree: ast::UseTree| { - tree.path().as_ref().map(path_is_self).unwrap_or(false) - }; - // check if only one of the two trees has a tree list, and whether that then contains `self` or not. - // 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` - let tree_contains_self = |tree: &ast::UseTree| { - tree.use_tree_list() - .map(|tree_list| tree_list.use_trees().any(tree_is_self)) - .unwrap_or(false) - }; - match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) { - (true, false) => continue, - (false, true) => { - *lhs_t = rhs_t; - continue; - } - _ => (), - } - - // glob imports arent part of the use-tree lists so we need to special handle them here as well - // this special handling is only required for when we merge a module import into a glob import of said module - // see the `merge_self_glob` or `merge_mod_into_glob` tests - if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() { - *lhs_t = make::use_tree( - make::path_unqualified(make::path_segment_self()), - None, - None, - false, - ); - use_trees.insert(idx, make::glob_use_tree()); - continue; - } - - if lhs_t.use_tree_list().is_none() && rhs_t.use_tree_list().is_none() { - continue; - } - } - let lhs = lhs_t.split_prefix(&lhs_prefix); - let rhs = rhs_t.split_prefix(&rhs_prefix); - match recursive_merge(&lhs, &rhs, merge) { - Some(use_tree) => use_trees[idx] = use_tree, - None => return None, - } - } - Err(_) - if merge == MergeBehaviour::Last - && use_trees.len() > 0 - && rhs_t.use_tree_list().is_some() => - { - return None - } - Err(idx) => { - use_trees.insert(idx, rhs_t); - } - } - } - Some(lhs.with_use_tree_list(make::use_tree_list(use_trees))) -} - -/// Traverses both paths until they differ, returning the common prefix of both. -fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> { - let mut res = None; - let mut lhs_curr = first_path(&lhs); - let mut rhs_curr = first_path(&rhs); - loop { - match (lhs_curr.segment(), rhs_curr.segment()) { - (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (), - _ => break res, - } - res = Some((lhs_curr.clone(), rhs_curr.clone())); - - match lhs_curr.parent_path().zip(rhs_curr.parent_path()) { - Some((lhs, rhs)) => { - lhs_curr = lhs; - rhs_curr = rhs; - } - _ => break res, - } - } -} - -fn is_simple_path(use_tree: &ast::UseTree) -> bool { - use_tree.use_tree_list().is_none() && use_tree.star_token().is_none() -} - -fn path_is_self(path: &ast::Path) -> bool { - path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none() -} - -#[inline] -fn first_segment(path: &ast::Path) -> Option { - first_path(path).segment() -} - -fn first_path(path: &ast::Path) -> ast::Path { - successors(Some(path.clone()), ast::Path::qualifier).last().unwrap() -} - -fn segment_iter(path: &ast::Path) -> impl Iterator + Clone { - // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone - successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment())) -} - -fn path_len(path: ast::Path) -> usize { - segment_iter(&path).count() -} - -/// Orders paths in the following way: -/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers -// FIXME: rustfmt sorts lowercase idents before uppercase, in general we want to have the same ordering rustfmt has -// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports. -// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}} -fn path_cmp_for_sort(a: Option, b: Option) -> Ordering { - match (a, b) { - (None, None) => Ordering::Equal, - (None, Some(_)) => Ordering::Less, - (Some(_), None) => Ordering::Greater, - (Some(ref a), Some(ref b)) => match (path_is_self(a), path_is_self(b)) { - (true, true) => Ordering::Equal, - (true, false) => Ordering::Less, - (false, true) => Ordering::Greater, - (false, false) => path_cmp_short(a, b), - }, - } -} - -/// Path comparison func for binary searching for merging. -fn path_cmp_bin_search(lhs: Option, rhs: Option) -> Ordering { - match (lhs.and_then(|path| path.segment()), rhs.and_then(|path| path.segment())) { - (None, None) => Ordering::Equal, - (None, Some(_)) => Ordering::Less, - (Some(_), None) => Ordering::Greater, - (Some(ref a), Some(ref b)) => path_segment_cmp(a, b), - } -} - -/// Short circuiting comparison, if both paths are equal until one of them ends they are considered -/// equal -fn path_cmp_short(a: &ast::Path, b: &ast::Path) -> Ordering { - let a = segment_iter(a); - let b = segment_iter(b); - // cmp_by would be useful for us here but that is currently unstable - // cmp doesnt work due the lifetimes on text's return type - a.zip(b) - .find_map(|(a, b)| match path_segment_cmp(&a, &b) { - Ordering::Equal => None, - ord => Some(ord), - }) - .unwrap_or(Ordering::Equal) -} - -/// Compares to paths, if one ends earlier than the other the has_tl parameters decide which is -/// greater as a a path that has a tree list should be greater, while one that just ends without -/// a tree list should be considered less. -fn use_tree_path_cmp(a: &ast::Path, a_has_tl: bool, b: &ast::Path, b_has_tl: bool) -> Ordering { - let a_segments = segment_iter(a); - let b_segments = segment_iter(b); - // cmp_by would be useful for us here but that is currently unstable - // cmp doesnt work due the lifetimes on text's return type - a_segments - .zip_longest(b_segments) - .find_map(|zipped| match zipped { - EitherOrBoth::Both(ref a, ref b) => match path_segment_cmp(a, b) { - Ordering::Equal => None, - ord => Some(ord), - }, - EitherOrBoth::Left(_) if !b_has_tl => Some(Ordering::Greater), - EitherOrBoth::Left(_) => Some(Ordering::Less), - EitherOrBoth::Right(_) if !a_has_tl => Some(Ordering::Less), - EitherOrBoth::Right(_) => Some(Ordering::Greater), - }) - .unwrap_or(Ordering::Equal) -} - -fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering { - let a = a.name_ref(); - let b = b.name_ref(); - a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text)) -} - -/// What type of merges are allowed. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum MergeBehaviour { - /// Merge everything together creating deeply nested imports. - Full, - /// Only merge the last import level, doesn't allow import nesting. - Last, -} - -impl MergeBehaviour { - #[inline] - fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool { - match self { - MergeBehaviour::Full => true, - // only simple single segment paths are allowed - MergeBehaviour::Last => { - tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1) - } - } - } -} - -#[derive(Eq, PartialEq, PartialOrd, Ord)] -enum ImportGroup { - // the order here defines the order of new group inserts - Std, - ExternCrate, - ThisCrate, - ThisModule, - SuperModule, -} - -impl ImportGroup { - fn new(path: &ast::Path) -> ImportGroup { - let default = ImportGroup::ExternCrate; - - let first_segment = match first_segment(path) { - Some(it) => it, - None => return default, - }; - - let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw); - match kind { - PathSegmentKind::SelfKw => ImportGroup::ThisModule, - PathSegmentKind::SuperKw => ImportGroup::SuperModule, - PathSegmentKind::CrateKw => ImportGroup::ThisCrate, - PathSegmentKind::Name(name) => match name.text().as_str() { - "std" => ImportGroup::Std, - "core" => ImportGroup::Std, - _ => ImportGroup::ExternCrate, - }, - PathSegmentKind::Type { .. } => unreachable!(), - } - } -} - -#[derive(PartialEq, Eq)] -enum AddBlankLine { - Before, - BeforeTwice, - Around, - After, - AfterTwice, -} - -impl AddBlankLine { - fn has_before(&self) -> bool { - matches!(self, AddBlankLine::Before | AddBlankLine::BeforeTwice | AddBlankLine::Around) - } - fn has_after(&self) -> bool { - matches!(self, AddBlankLine::After | AddBlankLine::AfterTwice | AddBlankLine::Around) - } -} - -fn find_insert_position( - scope: &ImportScope, - insert_path: ast::Path, -) -> (InsertPosition, AddBlankLine) { - let group = ImportGroup::new(&insert_path); - let path_node_iter = scope - .as_syntax_node() - .children() - .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) - .flat_map(|(use_, node)| { - let tree = use_.use_tree()?; - let path = tree.path()?; - let has_tl = tree.use_tree_list().is_some(); - Some((path, has_tl, node)) - }); - // Iterator that discards anything thats not in the required grouping - // This implementation allows the user to rearrange their import groups as this only takes the first group that fits - let group_iter = path_node_iter - .clone() - .skip_while(|(path, ..)| ImportGroup::new(path) != group) - .take_while(|(path, ..)| ImportGroup::new(path) == group); - - // 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 - let mut last = None; - // find the element that would come directly after our new import - let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find( - |&(ref path, has_tl, _)| { - use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater - }, - ); - match post_insert { - // insert our import before that element - Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After), - // there is no element after our new import, so append it to the end of the group - None => match last { - Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before), - // the group we were looking for actually doesnt exist, so insert - None => { - // similar concept here to the `last` from above - let mut last = None; - // find the group that comes after where we want to insert - let post_group = path_node_iter - .inspect(|(.., node)| last = Some(node.clone())) - .find(|(p, ..)| ImportGroup::new(p) > group); - match post_group { - Some((.., node)) => { - (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice) - } - // there is no such group, so append after the last one - None => match last { - Some(node) => { - (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice) - } - // there are no imports in this file at all - None => scope.insert_pos_after_last_inner_element(), - }, - } - } - }, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use test_utils::assert_eq_text; - - #[test] - fn insert_existing() { - check_full("std::fs", "use std::fs;", "use std::fs;") - } - - #[test] - fn insert_start() { - check_none( - "std::bar::AA", - r" -use std::bar::B; -use std::bar::D; -use std::bar::F; -use std::bar::G;", - r" -use std::bar::AA; -use std::bar::B; -use std::bar::D; -use std::bar::F; -use std::bar::G;", - ) - } - - #[test] - fn insert_start_indent() { - mark::check!(insert_use_indent_after); - check_none( - "std::bar::AA", - r" - use std::bar::B; - use std::bar::D;", - r" - use std::bar::AA; - use std::bar::B; - use std::bar::D;", - ) - } - - #[test] - fn insert_middle() { - check_none( - "std::bar::EE", - r" -use std::bar::A; -use std::bar::D; -use std::bar::F; -use std::bar::G;", - r" -use std::bar::A; -use std::bar::D; -use std::bar::EE; -use std::bar::F; -use std::bar::G;", - ) - } - - #[test] - fn insert_middle_indent() { - check_none( - "std::bar::EE", - r" - use std::bar::A; - use std::bar::D; - use std::bar::F; - use std::bar::G;", - r" - use std::bar::A; - use std::bar::D; - use std::bar::EE; - use std::bar::F; - use std::bar::G;", - ) - } - - #[test] - fn insert_end() { - check_none( - "std::bar::ZZ", - r" -use std::bar::A; -use std::bar::D; -use std::bar::F; -use std::bar::G;", - r" -use std::bar::A; -use std::bar::D; -use std::bar::F; -use std::bar::G; -use std::bar::ZZ;", - ) - } - - #[test] - fn insert_end_indent() { - mark::check!(insert_use_indent_before); - check_none( - "std::bar::ZZ", - r" - use std::bar::A; - use std::bar::D; - use std::bar::F; - use std::bar::G;", - r" - use std::bar::A; - use std::bar::D; - use std::bar::F; - use std::bar::G; - use std::bar::ZZ;", - ) - } - - #[test] - fn insert_middle_nested() { - check_none( - "std::bar::EE", - r" -use std::bar::A; -use std::bar::{D, Z}; // example of weird imports due to user -use std::bar::F; -use std::bar::G;", - r" -use std::bar::A; -use std::bar::EE; -use std::bar::{D, Z}; // example of weird imports due to user -use std::bar::F; -use std::bar::G;", - ) - } - - #[test] - fn insert_middle_groups() { - check_none( - "foo::bar::GG", - r" - use std::bar::A; - use std::bar::D; - - use foo::bar::F; - use foo::bar::H;", - r" - use std::bar::A; - use std::bar::D; - - use foo::bar::F; - use foo::bar::GG; - use foo::bar::H;", - ) - } - - #[test] - fn insert_first_matching_group() { - check_none( - "foo::bar::GG", - r" - use foo::bar::A; - use foo::bar::D; - - use std; - - use foo::bar::F; - use foo::bar::H;", - r" - use foo::bar::A; - use foo::bar::D; - use foo::bar::GG; - - use std; - - use foo::bar::F; - use foo::bar::H;", - ) - } - - #[test] - fn insert_missing_group_std() { - check_none( - "std::fmt", - r" - use foo::bar::A; - use foo::bar::D;", - r" - use std::fmt; - - use foo::bar::A; - use foo::bar::D;", - ) - } - - #[test] - fn insert_missing_group_self() { - check_none( - "self::fmt", - r" -use foo::bar::A; -use foo::bar::D;", - r" -use foo::bar::A; -use foo::bar::D; - -use self::fmt;", - ) - } - - #[test] - fn insert_no_imports() { - check_full( - "foo::bar", - "fn main() {}", - r"use foo::bar; - -fn main() {}", - ) - } - - #[test] - fn insert_empty_file() { - // empty files will get two trailing newlines - // this is due to the test case insert_no_imports above - check_full( - "foo::bar", - "", - r"use foo::bar; - -", - ) - } - - #[test] - fn insert_empty_module() { - mark::check!(insert_use_no_indent_after); - check( - "foo::bar", - "mod x {}", - r"{ - use foo::bar; -}", - None, - true, - ) - } - - #[test] - fn insert_after_inner_attr() { - check_full( - "foo::bar", - r"#![allow(unused_imports)]", - r"#![allow(unused_imports)] - -use foo::bar;", - ) - } - - #[test] - fn insert_after_inner_attr2() { - check_full( - "foo::bar", - r"#![allow(unused_imports)] - -#![no_std] -fn main() {}", - r"#![allow(unused_imports)] - -#![no_std] - -use foo::bar; -fn main() {}", - ); - } - - #[test] - fn inserts_after_single_line_inner_comments() { - check_none( - "foo::bar::Baz", - "//! Single line inner comments do not allow any code before them.", - r#"//! Single line inner comments do not allow any code before them. - -use foo::bar::Baz;"#, - ); - } - - #[test] - fn inserts_after_multiline_inner_comments() { - check_none( - "foo::bar::Baz", - r#"/*! Multiline inner comments do not allow any code before them. */ - -/*! Still an inner comment, cannot place any code before. */ -fn main() {}"#, - r#"/*! Multiline inner comments do not allow any code before them. */ - -/*! Still an inner comment, cannot place any code before. */ - -use foo::bar::Baz; -fn main() {}"#, - ) - } - - #[test] - fn inserts_after_all_inner_items() { - check_none( - "foo::bar::Baz", - r#"#![allow(unused_imports)] -/*! Multiline line comment 2 */ - - -//! Single line comment 1 -#![no_std] -//! Single line comment 2 -fn main() {}"#, - r#"#![allow(unused_imports)] -/*! Multiline line comment 2 */ - - -//! Single line comment 1 -#![no_std] -//! Single line comment 2 - -use foo::bar::Baz; -fn main() {}"#, - ) - } - - #[test] - fn merge_groups() { - check_last("std::io", r"use std::fmt;", r"use std::{fmt, io};") - } - - #[test] - fn merge_groups_last() { - check_last( - "std::io", - r"use std::fmt::{Result, Display};", - r"use std::fmt::{Result, Display}; -use std::io;", - ) - } - - #[test] - fn merge_last_into_self() { - check_last("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};"); - } - - #[test] - fn merge_groups_full() { - check_full( - "std::io", - r"use std::fmt::{Result, Display};", - r"use std::{fmt::{Result, Display}, io};", - ) - } - - #[test] - fn merge_groups_long_full() { - check_full( - "std::foo::bar::Baz", - r"use std::foo::bar::Qux;", - r"use std::foo::bar::{Baz, Qux};", - ) - } - - #[test] - fn merge_groups_long_last() { - check_last( - "std::foo::bar::Baz", - r"use std::foo::bar::Qux;", - r"use std::foo::bar::{Baz, Qux};", - ) - } - - #[test] - fn merge_groups_long_full_list() { - check_full( - "std::foo::bar::Baz", - r"use std::foo::bar::{Qux, Quux};", - r"use std::foo::bar::{Baz, Quux, Qux};", - ) - } - - #[test] - fn merge_groups_long_last_list() { - check_last( - "std::foo::bar::Baz", - r"use std::foo::bar::{Qux, Quux};", - r"use std::foo::bar::{Baz, Quux, Qux};", - ) - } - - #[test] - fn merge_groups_long_full_nested() { - check_full( - "std::foo::bar::Baz", - r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", - r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};", - ) - } - - #[test] - fn merge_groups_long_last_nested() { - check_last( - "std::foo::bar::Baz", - r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", - r"use std::foo::bar::Baz; -use std::foo::bar::{Qux, quux::{Fez, Fizz}};", - ) - } - - #[test] - fn merge_groups_full_nested_deep() { - check_full( - "std::foo::bar::quux::Baz", - r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", - r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};", - ) - } - - #[test] - fn merge_groups_full_nested_long() { - check_full( - "std::foo::bar::Baz", - r"use std::{foo::bar::Qux};", - r"use std::{foo::bar::{Baz, Qux}};", - ); - } - - #[test] - fn merge_groups_last_nested_long() { - check_full( - "std::foo::bar::Baz", - r"use std::{foo::bar::Qux};", - r"use std::{foo::bar::{Baz, Qux}};", - ); - } - - #[test] - fn merge_groups_skip_pub() { - check_full( - "std::io", - r"pub use std::fmt::{Result, Display};", - r"pub use std::fmt::{Result, Display}; -use std::io;", - ) - } - - #[test] - fn merge_groups_skip_pub_crate() { - check_full( - "std::io", - r"pub(crate) use std::fmt::{Result, Display};", - r"pub(crate) use std::fmt::{Result, Display}; -use std::io;", - ) - } - - #[test] - #[ignore] // FIXME: Support this - fn split_out_merge() { - check_last( - "std::fmt::Result", - r"use std::{fmt, io};", - r"use std::fmt::{self, Result}; -use std::io;", - ) - } - - #[test] - fn merge_into_module_import() { - check_full( - "std::fmt::Result", - r"use std::{fmt, io};", - r"use std::{fmt::{self, Result}, io};", - ) - } - - #[test] - fn merge_groups_self() { - check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};") - } - - #[test] - fn merge_mod_into_glob() { - check_full( - "token::TokenKind", - r"use token::TokenKind::*;", - r"use token::TokenKind::{*, self};", - ) - // FIXME: have it emit `use token::TokenKind::{self, *}`? - } - - #[test] - fn merge_self_glob() { - check_full("self", r"use self::*;", r"use self::{*, self};") - // FIXME: have it emit `use {self, *}`? - } - - #[test] - fn merge_glob_nested() { - check_full( - "foo::bar::quux::Fez", - r"use foo::bar::{Baz, quux::*};", - r"use foo::bar::{Baz, quux::{self::*, Fez}};", - ) - } - - #[test] - fn skip_merge_last_too_long() { - check_last( - "foo::bar", - r"use foo::bar::baz::Qux;", - r"use foo::bar; -use foo::bar::baz::Qux;", - ); - } - - #[test] - fn skip_merge_last_too_long2() { - check_last( - "foo::bar::baz::Qux", - r"use foo::bar;", - r"use foo::bar; -use foo::bar::baz::Qux;", - ); - } - - #[test] - fn insert_short_before_long() { - check_none( - "foo::bar", - r"use foo::bar::baz::Qux;", - r"use foo::bar; -use foo::bar::baz::Qux;", - ); - } - - #[test] - fn merge_last_fail() { - check_merge_only_fail( - r"use foo::bar::{baz::{Qux, Fez}};", - r"use foo::bar::{baaz::{Quux, Feez}};", - MergeBehaviour::Last, - ); - } - - #[test] - fn merge_last_fail1() { - check_merge_only_fail( - r"use foo::bar::{baz::{Qux, Fez}};", - r"use foo::bar::baaz::{Quux, Feez};", - MergeBehaviour::Last, - ); - } - - #[test] - fn merge_last_fail2() { - check_merge_only_fail( - r"use foo::bar::baz::{Qux, Fez};", - r"use foo::bar::{baaz::{Quux, Feez}};", - MergeBehaviour::Last, - ); - } - - #[test] - fn merge_last_fail3() { - check_merge_only_fail( - r"use foo::bar::baz::{Qux, Fez};", - r"use foo::bar::baaz::{Quux, Feez};", - MergeBehaviour::Last, - ); - } - - fn check( - path: &str, - ra_fixture_before: &str, - ra_fixture_after: &str, - mb: Option, - module: bool, - ) { - let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(); - if module { - syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone(); - } - let file = super::ImportScope::from(syntax).unwrap(); - let path = ast::SourceFile::parse(&format!("use {};", path)) - .tree() - .syntax() - .descendants() - .find_map(ast::Path::cast) - .unwrap(); - - let rewriter = insert_use(&file, path, mb); - let result = rewriter.rewrite(file.as_syntax_node()).to_string(); - assert_eq_text!(&result, ra_fixture_after); - } - - fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { - check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Full), false) - } - - fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { - check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Last), false) - } - - fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { - check(path, ra_fixture_before, ra_fixture_after, None, false) - } - - fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) { - let use0 = ast::SourceFile::parse(ra_fixture0) - .tree() - .syntax() - .descendants() - .find_map(ast::Use::cast) - .unwrap(); - - let use1 = ast::SourceFile::parse(ra_fixture1) - .tree() - .syntax() - .descendants() - .find_map(ast::Use::cast) - .unwrap(); - - let result = try_merge_imports(&use0, &use1, mb); - assert_eq!(result.map(|u| u.to_string()), None); - } -} -- cgit v1.2.3