]> git.lizzy.rs Git - rust.git/commitdiff
Add Unmerge Use assist
authorunexge <unexge@gmail.com>
Fri, 15 Jan 2021 19:14:51 +0000 (22:14 +0300)
committerunexge <unexge@gmail.com>
Fri, 15 Jan 2021 19:14:51 +0000 (22:14 +0300)
crates/assists/src/handlers/unmerge_use.rs [new file with mode: 0644]
crates/assists/src/lib.rs
crates/assists/src/tests/generated.rs
crates/ide_db/src/helpers/insert_use.rs
crates/syntax/src/ast/make.rs

diff --git a/crates/assists/src/handlers/unmerge_use.rs b/crates/assists/src/handlers/unmerge_use.rs
new file mode 100644 (file)
index 0000000..d7dfe70
--- /dev/null
@@ -0,0 +1,213 @@
+use syntax::{
+    algo::SyntaxRewriter,
+    ast::{self, edit::AstNodeEdit, VisibilityOwner},
+    AstNode, SyntaxKind,
+};
+
+use crate::{
+    assist_context::{AssistContext, Assists},
+    AssistId, AssistKind,
+};
+
+// Assist: unmerge_use
+//
+// Extracts single use item from use list.
+//
+// ```
+// use std::fmt::{Debug, Display$0};
+// ```
+// ->
+// ```
+// use std::fmt::{Debug};
+// use std::fmt::Display;
+// ```
+pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    let tree: ast::UseTree = ctx.find_node_at_offset()?;
+
+    let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
+    if tree_list.use_trees().count() < 2 {
+        return None;
+    }
+
+    let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
+    let path = resolve_full_path(&tree)?;
+
+    let new_use = ast::make::use_(
+        use_.visibility(),
+        ast::make::use_tree(path, None, tree.rename(), tree.star_token().is_some()),
+    );
+
+    let mut rewriter = SyntaxRewriter::default();
+    rewriter += tree.remove();
+    rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline());
+    if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize {
+        rewriter.insert_after(
+            use_.syntax(),
+            &ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)),
+        );
+    }
+    rewriter.insert_after(use_.syntax(), new_use.syntax());
+
+    let target = tree.syntax().text_range();
+    acc.add(
+        AssistId("unmerge_use", AssistKind::RefactorRewrite),
+        "Unmerge use",
+        target,
+        |builder| {
+            builder.rewrite(rewriter);
+        },
+    )
+}
+
+fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
+    let mut paths = tree
+        .syntax()
+        .ancestors()
+        .take_while(|n| n.kind() != SyntaxKind::USE_KW)
+        .filter_map(ast::UseTree::cast)
+        .filter_map(|t| t.path());
+
+    let mut final_path = paths.next()?;
+    for path in paths {
+        final_path = ast::make::path_concat(path, final_path)
+    }
+    Some(final_path)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn skip_single_use_item() {
+        check_assist_not_applicable(
+            unmerge_use,
+            r"
+use std::fmt::Debug$0;
+",
+        );
+        check_assist_not_applicable(
+            unmerge_use,
+            r"
+use std::fmt::{Debug$0};
+",
+        );
+        check_assist_not_applicable(
+            unmerge_use,
+            r"
+use std::fmt::Debug as Dbg$0;
+",
+        );
+    }
+
+    #[test]
+    fn skip_single_glob_import() {
+        check_assist_not_applicable(
+            unmerge_use,
+            r"
+use std::fmt::*$0;
+",
+        );
+    }
+
+    #[test]
+    fn unmerge_use_item() {
+        check_assist(
+            unmerge_use,
+            r"
+use std::fmt::{Debug, Display$0};
+",
+            r"
+use std::fmt::{Debug};
+use std::fmt::Display;
+",
+        );
+
+        check_assist(
+            unmerge_use,
+            r"
+use std::fmt::{Debug, format$0, Display};
+",
+            r"
+use std::fmt::{Debug, Display};
+use std::fmt::format;
+",
+        );
+    }
+
+    #[test]
+    fn unmerge_glob_import() {
+        check_assist(
+            unmerge_use,
+            r"
+use std::fmt::{*$0, Display};
+",
+            r"
+use std::fmt::{Display};
+use std::fmt::*;
+",
+        );
+    }
+
+    #[test]
+    fn unmerge_renamed_use_item() {
+        check_assist(
+            unmerge_use,
+            r"
+use std::fmt::{Debug, Display as Disp$0};
+",
+            r"
+use std::fmt::{Debug};
+use std::fmt::Display as Disp;
+",
+        );
+    }
+
+    #[test]
+    fn unmerge_indented_use_item() {
+        check_assist(
+            unmerge_use,
+            r"
+mod format {
+    use std::fmt::{Debug, Display$0 as Disp, format};
+}
+",
+            r"
+mod format {
+    use std::fmt::{Debug, format};
+    use std::fmt::Display as Disp;
+}
+",
+        );
+    }
+
+    #[test]
+    fn unmerge_nested_use_item() {
+        check_assist(
+            unmerge_use,
+            r"
+use foo::bar::{baz::{qux$0, foobar}, barbaz};
+",
+            r"
+use foo::bar::{baz::{foobar}, barbaz};
+use foo::bar::baz::qux;
+",
+        );
+    }
+
+    #[test]
+    fn unmerge_use_item_with_visibility() {
+        check_assist(
+            unmerge_use,
+            r"
+pub use std::fmt::{Debug, Display$0};
+",
+            r"
+pub use std::fmt::{Debug};
+pub use std::fmt::Display;
+",
+        );
+    }
+}
index 1080294abc83f39078be8f8b3e6d106e5ef19e4d..3d79718060a4eaac54fe93854dcec3251c6124f6 100644 (file)
@@ -156,6 +156,7 @@ mod handlers {
     mod replace_unwrap_with_match;
     mod split_import;
     mod toggle_ignore;
+    mod unmerge_use;
     mod unwrap_block;
     mod wrap_return_type_in_result;
 
@@ -213,6 +214,7 @@ pub(crate) fn all() -> &'static [Handler] {
             replace_unwrap_with_match::replace_unwrap_with_match,
             split_import::split_import,
             toggle_ignore::toggle_ignore,
+            unmerge_use::unmerge_use,
             unwrap_block::unwrap_block,
             wrap_return_type_in_result::wrap_return_type_in_result,
             // These are manually sorted for better priorities
index 217f577eba99c97f88de63ddd104deecb25ce4a5..d48d063b407621b701737f4cedd3915421f792a9 100644 (file)
@@ -1137,6 +1137,20 @@ fn arithmetics {
     )
 }
 
+#[test]
+fn doctest_unmerge_use() {
+    check_doc_test(
+        "unmerge_use",
+        r#####"
+use std::fmt::{Debug, Display$0};
+"#####,
+        r#####"
+use std::fmt::{Debug};
+use std::fmt::Display;
+"#####,
+    )
+}
+
 #[test]
 fn doctest_unwrap_block() {
     check_doc_test(
index 0c180e9bcd1944135b7f35ceb01dc57ae82735c7..d2f9f5d25c193a8705b57d0e5ad658353dad45f5 100644 (file)
@@ -97,7 +97,7 @@ pub fn insert_use<'a>(
 ) -> 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));
+    let use_item = make::use_(None, 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) {
index 1ed8a96e5fcb7381ade83af86f3db05558bb2b0d..9ffc3ae110ec91e5de87ed20930e6032798de90e 100644 (file)
@@ -108,8 +108,12 @@ pub fn use_tree_list(use_trees: impl IntoIterator<Item = ast::UseTree>) -> ast::
     ast_from_text(&format!("use {{{}}};", use_trees))
 }
 
-pub fn use_(use_tree: ast::UseTree) -> ast::Use {
-    ast_from_text(&format!("use {};", use_tree))
+pub fn use_(visibility: Option<ast::Visibility>, use_tree: ast::UseTree) -> ast::Use {
+    let visibility = match visibility {
+        None => String::new(),
+        Some(it) => format!("{} ", it),
+    };
+    ast_from_text(&format!("{}use {};", visibility, use_tree))
 }
 
 pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField {