aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db/src/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_db/src/helpers')
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs78
-rw-r--r--crates/ide_db/src/helpers/insert_use/tests.rs72
2 files changed, 112 insertions, 38 deletions
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index 10bbafe77..e6b4832e7 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -5,7 +5,7 @@ use hir::Semantics;
5use syntax::{ 5use syntax::{
6 algo, 6 algo,
7 ast::{self, make, AstNode, AttrsOwner, ModuleItemOwner, PathSegmentKind, VisibilityOwner}, 7 ast::{self, make, AstNode, AttrsOwner, ModuleItemOwner, PathSegmentKind, VisibilityOwner},
8 ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken, 8 match_ast, ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken,
9}; 9};
10 10
11use crate::{ 11use crate::{
@@ -36,22 +36,39 @@ pub struct InsertUseConfig {
36 pub enforce_granularity: bool, 36 pub enforce_granularity: bool,
37 pub prefix_kind: PrefixKind, 37 pub prefix_kind: PrefixKind,
38 pub group: bool, 38 pub group: bool,
39 pub skip_glob_imports: bool,
39} 40}
40 41
41#[derive(Debug, Clone)] 42#[derive(Debug, Clone)]
42pub enum ImportScope { 43pub enum ImportScope {
43 File(ast::SourceFile), 44 File(ast::SourceFile),
44 Module(ast::ItemList), 45 Module(ast::ItemList),
46 Block(ast::BlockExpr),
45} 47}
46 48
47impl ImportScope { 49impl ImportScope {
48 pub fn from(syntax: SyntaxNode) -> Option<Self> { 50 fn from(syntax: SyntaxNode) -> Option<Self> {
49 if let Some(module) = ast::Module::cast(syntax.clone()) { 51 fn contains_cfg_attr(attrs: &dyn AttrsOwner) -> bool {
50 module.item_list().map(ImportScope::Module) 52 attrs
51 } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) { 53 .attrs()
52 this.map(ImportScope::File) 54 .any(|attr| attr.as_simple_call().map_or(false, |(ident, _)| ident == "cfg"))
53 } else { 55 }
54 ast::ItemList::cast(syntax).map(ImportScope::Module) 56 match_ast! {
57 match syntax {
58 ast::Module(module) => module.item_list().map(ImportScope::Module),
59 ast::SourceFile(file) => Some(ImportScope::File(file)),
60 ast::Fn(func) => contains_cfg_attr(&func).then(|| func.body().map(ImportScope::Block)).flatten(),
61 ast::Const(konst) => contains_cfg_attr(&konst).then(|| match konst.body()? {
62 ast::Expr::BlockExpr(block) => Some(block),
63 _ => None,
64 }).flatten().map(ImportScope::Block),
65 ast::Static(statik) => contains_cfg_attr(&statik).then(|| match statik.body()? {
66 ast::Expr::BlockExpr(block) => Some(block),
67 _ => None,
68 }).flatten().map(ImportScope::Block),
69 _ => None,
70
71 }
55 } 72 }
56 } 73 }
57 74
@@ -72,6 +89,7 @@ impl ImportScope {
72 match self { 89 match self {
73 ImportScope::File(file) => file.syntax(), 90 ImportScope::File(file) => file.syntax(),
74 ImportScope::Module(item_list) => item_list.syntax(), 91 ImportScope::Module(item_list) => item_list.syntax(),
92 ImportScope::Block(block) => block.syntax(),
75 } 93 }
76 } 94 }
77 95
@@ -79,6 +97,7 @@ impl ImportScope {
79 match self { 97 match self {
80 ImportScope::File(file) => ImportScope::File(file.clone_for_update()), 98 ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
81 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()), 99 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
100 ImportScope::Block(block) => ImportScope::Block(block.clone_for_update()),
82 } 101 }
83 } 102 }
84 103
@@ -95,6 +114,7 @@ impl ImportScope {
95 let mut use_stmts = match self { 114 let mut use_stmts = match self {
96 ImportScope::File(f) => f.items(), 115 ImportScope::File(f) => f.items(),
97 ImportScope::Module(m) => m.items(), 116 ImportScope::Module(m) => m.items(),
117 ImportScope::Block(b) => b.items(),
98 } 118 }
99 .filter_map(use_stmt); 119 .filter_map(use_stmt);
100 let mut res = ImportGranularityGuess::Unknown; 120 let mut res = ImportGranularityGuess::Unknown;
@@ -153,7 +173,7 @@ enum ImportGranularityGuess {
153} 173}
154 174
155/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 175/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
156pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { 176pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
157 let _p = profile::span("insert_use"); 177 let _p = profile::span("insert_use");
158 let mut mb = match cfg.granularity { 178 let mut mb = match cfg.granularity {
159 ImportGranularity::Crate => Some(MergeBehavior::Crate), 179 ImportGranularity::Crate => Some(MergeBehavior::Crate),
@@ -175,7 +195,10 @@ pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig
175 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update(); 195 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
176 // merge into existing imports if possible 196 // merge into existing imports if possible
177 if let Some(mb) = mb { 197 if let Some(mb) = mb {
178 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 198 let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
199 for existing_use in
200 scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
201 {
179 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { 202 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
180 ted::replace(existing_use.syntax(), merged.syntax()); 203 ted::replace(existing_use.syntax(), merged.syntax());
181 return; 204 return;
@@ -315,28 +338,29 @@ fn insert_use_(
315 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline()); 338 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
316 return; 339 return;
317 } 340 }
318 match scope { 341 let l_curly = match scope {
319 ImportScope::File(_) => { 342 ImportScope::File(_) => {
320 cov_mark::hit!(insert_group_empty_file); 343 cov_mark::hit!(insert_group_empty_file);
321 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line()); 344 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
322 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()) 345 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
346 return;
323 } 347 }
348 // don't insert the imports before the item list/block expr's opening curly brace
349 ImportScope::Module(item_list) => item_list.l_curly_token(),
324 // don't insert the imports before the item list's opening curly brace 350 // don't insert the imports before the item list's opening curly brace
325 ImportScope::Module(item_list) => match item_list.l_curly_token() { 351 ImportScope::Block(block) => block.l_curly_token(),
326 Some(b) => { 352 };
327 cov_mark::hit!(insert_group_empty_module); 353 match l_curly {
328 ted::insert(ted::Position::after(&b), make::tokens::single_newline()); 354 Some(b) => {
329 ted::insert(ted::Position::after(&b), use_item.syntax()); 355 cov_mark::hit!(insert_group_empty_module);
330 } 356 ted::insert(ted::Position::after(&b), make::tokens::single_newline());
331 None => { 357 ted::insert(ted::Position::after(&b), use_item.syntax());
332 // This should never happens, broken module syntax node 358 }
333 ted::insert( 359 None => {
334 ted::Position::first_child_of(scope_syntax), 360 // This should never happens, broken module syntax node
335 make::tokens::blank_line(), 361 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
336 ); 362 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
337 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()); 363 }
338 }
339 },
340 } 364 }
341} 365}
342 366
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 5a88ec742..263edcdc9 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -4,6 +4,23 @@ use hir::PrefixKind;
4use test_utils::assert_eq_text; 4use test_utils::assert_eq_text;
5 5
6#[test] 6#[test]
7fn insert_skips_lone_glob_imports() {
8 check(
9 "use foo::baz::A",
10 r"
11use foo::bar::*;
12",
13 r"
14use foo::bar::*;
15use foo::baz::A;
16",
17 ImportGranularity::Crate,
18 false,
19 false,
20 );
21}
22
23#[test]
7fn insert_not_group() { 24fn insert_not_group() {
8 cov_mark::check!(insert_no_grouping_last); 25 cov_mark::check!(insert_no_grouping_last);
9 check( 26 check(
@@ -534,17 +551,37 @@ fn merge_groups_self() {
534 551
535#[test] 552#[test]
536fn merge_mod_into_glob() { 553fn merge_mod_into_glob() {
537 check_crate( 554 check_with_config(
538 "token::TokenKind", 555 "token::TokenKind",
539 r"use token::TokenKind::*;", 556 r"use token::TokenKind::*;",
540 r"use token::TokenKind::{*, self};", 557 r"use token::TokenKind::{*, self};",
558 false,
559 &InsertUseConfig {
560 granularity: ImportGranularity::Crate,
561 enforce_granularity: true,
562 prefix_kind: PrefixKind::Plain,
563 group: false,
564 skip_glob_imports: false,
565 },
541 ) 566 )
542 // FIXME: have it emit `use token::TokenKind::{self, *}`? 567 // FIXME: have it emit `use token::TokenKind::{self, *}`?
543} 568}
544 569
545#[test] 570#[test]
546fn merge_self_glob() { 571fn merge_self_glob() {
547 check_crate("self", r"use self::*;", r"use self::{*, self};") 572 check_with_config(
573 "self",
574 r"use self::*;",
575 r"use self::{*, self};",
576 false,
577 &InsertUseConfig {
578 granularity: ImportGranularity::Crate,
579 enforce_granularity: true,
580 prefix_kind: PrefixKind::Plain,
581 group: false,
582 skip_glob_imports: false,
583 },
584 )
548 // FIXME: have it emit `use {self, *}`? 585 // FIXME: have it emit `use {self, *}`?
549} 586}
550 587
@@ -757,13 +794,12 @@ use foo::bar::qux;
757 ); 794 );
758} 795}
759 796
760fn check( 797fn check_with_config(
761 path: &str, 798 path: &str,
762 ra_fixture_before: &str, 799 ra_fixture_before: &str,
763 ra_fixture_after: &str, 800 ra_fixture_after: &str,
764 granularity: ImportGranularity,
765 module: bool, 801 module: bool,
766 group: bool, 802 config: &InsertUseConfig,
767) { 803) {
768 let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(); 804 let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone();
769 if module { 805 if module {
@@ -777,18 +813,32 @@ fn check(
777 .find_map(ast::Path::cast) 813 .find_map(ast::Path::cast)
778 .unwrap(); 814 .unwrap();
779 815
780 insert_use( 816 insert_use(&file, path, config);
781 &file, 817 let result = file.as_syntax_node().to_string();
818 assert_eq_text!(ra_fixture_after, &result);
819}
820
821fn check(
822 path: &str,
823 ra_fixture_before: &str,
824 ra_fixture_after: &str,
825 granularity: ImportGranularity,
826 module: bool,
827 group: bool,
828) {
829 check_with_config(
782 path, 830 path,
783 InsertUseConfig { 831 ra_fixture_before,
832 ra_fixture_after,
833 module,
834 &InsertUseConfig {
784 granularity, 835 granularity,
785 enforce_granularity: true, 836 enforce_granularity: true,
786 prefix_kind: PrefixKind::Plain, 837 prefix_kind: PrefixKind::Plain,
787 group, 838 group,
839 skip_glob_imports: true,
788 }, 840 },
789 ); 841 )
790 let result = file.as_syntax_node().to_string();
791 assert_eq_text!(ra_fixture_after, &result);
792} 842}
793 843
794fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 844fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {