diff options
Diffstat (limited to 'crates/ide_db/src/helpers')
-rw-r--r-- | crates/ide_db/src/helpers/insert_use.rs | 78 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use/tests.rs | 165 |
2 files changed, 181 insertions, 62 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; | |||
5 | use syntax::{ | 5 | use 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 | ||
11 | use crate::{ | 11 | use 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)] |
42 | pub enum ImportScope { | 43 | pub 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 | ||
47 | impl ImportScope { | 49 | impl 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. |
156 | pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { | 176 | pub 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..01894630a 100644 --- a/crates/ide_db/src/helpers/insert_use/tests.rs +++ b/crates/ide_db/src/helpers/insert_use/tests.rs | |||
@@ -1,12 +1,63 @@ | |||
1 | use super::*; | 1 | use super::*; |
2 | 2 | ||
3 | use hir::PrefixKind; | 3 | use hir::PrefixKind; |
4 | use test_utils::assert_eq_text; | 4 | use test_utils::{assert_eq_text, extract_range_or_offset, CURSOR_MARKER}; |
5 | |||
6 | #[test] | ||
7 | fn respects_cfg_attr_fn() { | ||
8 | check( | ||
9 | r"bar::Bar", | ||
10 | r#" | ||
11 | #[cfg(test)] | ||
12 | fn foo() {$0} | ||
13 | "#, | ||
14 | r#" | ||
15 | #[cfg(test)] | ||
16 | fn foo() { | ||
17 | use bar::Bar; | ||
18 | } | ||
19 | "#, | ||
20 | ImportGranularity::Crate, | ||
21 | ); | ||
22 | } | ||
23 | |||
24 | #[test] | ||
25 | fn respects_cfg_attr_const() { | ||
26 | check( | ||
27 | r"bar::Bar", | ||
28 | r#" | ||
29 | #[cfg(test)] | ||
30 | const FOO: Bar = {$0}; | ||
31 | "#, | ||
32 | r#" | ||
33 | #[cfg(test)] | ||
34 | const FOO: Bar = { | ||
35 | use bar::Bar; | ||
36 | }; | ||
37 | "#, | ||
38 | ImportGranularity::Crate, | ||
39 | ); | ||
40 | } | ||
41 | |||
42 | #[test] | ||
43 | fn insert_skips_lone_glob_imports() { | ||
44 | check( | ||
45 | "use foo::baz::A", | ||
46 | r" | ||
47 | use foo::bar::*; | ||
48 | ", | ||
49 | r" | ||
50 | use foo::bar::*; | ||
51 | use foo::baz::A; | ||
52 | ", | ||
53 | ImportGranularity::Crate, | ||
54 | ); | ||
55 | } | ||
5 | 56 | ||
6 | #[test] | 57 | #[test] |
7 | fn insert_not_group() { | 58 | fn insert_not_group() { |
8 | cov_mark::check!(insert_no_grouping_last); | 59 | cov_mark::check!(insert_no_grouping_last); |
9 | check( | 60 | check_with_config( |
10 | "use external_crate2::bar::A", | 61 | "use external_crate2::bar::A", |
11 | r" | 62 | r" |
12 | use std::bar::B; | 63 | use std::bar::B; |
@@ -21,24 +72,32 @@ use crate::bar::A; | |||
21 | use self::bar::A; | 72 | use self::bar::A; |
22 | use super::bar::A; | 73 | use super::bar::A; |
23 | use external_crate2::bar::A;", | 74 | use external_crate2::bar::A;", |
24 | ImportGranularity::Item, | 75 | &InsertUseConfig { |
25 | false, | 76 | granularity: ImportGranularity::Item, |
26 | false, | 77 | enforce_granularity: true, |
78 | prefix_kind: PrefixKind::Plain, | ||
79 | group: false, | ||
80 | skip_glob_imports: true, | ||
81 | }, | ||
27 | ); | 82 | ); |
28 | } | 83 | } |
29 | 84 | ||
30 | #[test] | 85 | #[test] |
31 | fn insert_not_group_empty() { | 86 | fn insert_not_group_empty() { |
32 | cov_mark::check!(insert_no_grouping_last2); | 87 | cov_mark::check!(insert_no_grouping_last2); |
33 | check( | 88 | check_with_config( |
34 | "use external_crate2::bar::A", | 89 | "use external_crate2::bar::A", |
35 | r"", | 90 | r"", |
36 | r"use external_crate2::bar::A; | 91 | r"use external_crate2::bar::A; |
37 | 92 | ||
38 | ", | 93 | ", |
39 | ImportGranularity::Item, | 94 | &InsertUseConfig { |
40 | false, | 95 | granularity: ImportGranularity::Item, |
41 | false, | 96 | enforce_granularity: true, |
97 | prefix_kind: PrefixKind::Plain, | ||
98 | group: false, | ||
99 | skip_glob_imports: true, | ||
100 | }, | ||
42 | ); | 101 | ); |
43 | } | 102 | } |
44 | 103 | ||
@@ -277,13 +336,15 @@ fn insert_empty_module() { | |||
277 | cov_mark::check!(insert_group_empty_module); | 336 | cov_mark::check!(insert_group_empty_module); |
278 | check( | 337 | check( |
279 | "foo::bar", | 338 | "foo::bar", |
280 | "mod x {}", | 339 | r" |
281 | r"{ | 340 | mod x {$0} |
341 | ", | ||
342 | r" | ||
343 | mod x { | ||
282 | use foo::bar; | 344 | use foo::bar; |
283 | }", | 345 | } |
346 | ", | ||
284 | ImportGranularity::Item, | 347 | ImportGranularity::Item, |
285 | true, | ||
286 | true, | ||
287 | ) | 348 | ) |
288 | } | 349 | } |
289 | 350 | ||
@@ -534,17 +595,35 @@ fn merge_groups_self() { | |||
534 | 595 | ||
535 | #[test] | 596 | #[test] |
536 | fn merge_mod_into_glob() { | 597 | fn merge_mod_into_glob() { |
537 | check_crate( | 598 | check_with_config( |
538 | "token::TokenKind", | 599 | "token::TokenKind", |
539 | r"use token::TokenKind::*;", | 600 | r"use token::TokenKind::*;", |
540 | r"use token::TokenKind::{*, self};", | 601 | r"use token::TokenKind::{*, self};", |
602 | &InsertUseConfig { | ||
603 | granularity: ImportGranularity::Crate, | ||
604 | enforce_granularity: true, | ||
605 | prefix_kind: PrefixKind::Plain, | ||
606 | group: false, | ||
607 | skip_glob_imports: false, | ||
608 | }, | ||
541 | ) | 609 | ) |
542 | // FIXME: have it emit `use token::TokenKind::{self, *}`? | 610 | // FIXME: have it emit `use token::TokenKind::{self, *}`? |
543 | } | 611 | } |
544 | 612 | ||
545 | #[test] | 613 | #[test] |
546 | fn merge_self_glob() { | 614 | fn merge_self_glob() { |
547 | check_crate("self", r"use self::*;", r"use self::{*, self};") | 615 | check_with_config( |
616 | "self", | ||
617 | r"use self::*;", | ||
618 | r"use self::{*, self};", | ||
619 | &InsertUseConfig { | ||
620 | granularity: ImportGranularity::Crate, | ||
621 | enforce_granularity: true, | ||
622 | prefix_kind: PrefixKind::Plain, | ||
623 | group: false, | ||
624 | skip_glob_imports: false, | ||
625 | }, | ||
626 | ) | ||
548 | // FIXME: have it emit `use {self, *}`? | 627 | // FIXME: have it emit `use {self, *}`? |
549 | } | 628 | } |
550 | 629 | ||
@@ -757,19 +836,24 @@ use foo::bar::qux; | |||
757 | ); | 836 | ); |
758 | } | 837 | } |
759 | 838 | ||
760 | fn check( | 839 | fn check_with_config( |
761 | path: &str, | 840 | path: &str, |
762 | ra_fixture_before: &str, | 841 | ra_fixture_before: &str, |
763 | ra_fixture_after: &str, | 842 | ra_fixture_after: &str, |
764 | granularity: ImportGranularity, | 843 | config: &InsertUseConfig, |
765 | module: bool, | ||
766 | group: bool, | ||
767 | ) { | 844 | ) { |
768 | let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(); | 845 | let (text, pos) = if ra_fixture_before.contains(CURSOR_MARKER) { |
769 | if module { | 846 | let (range_or_offset, text) = extract_range_or_offset(ra_fixture_before); |
770 | syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone(); | 847 | (text, Some(range_or_offset)) |
771 | } | 848 | } else { |
772 | let file = super::ImportScope::from(syntax.clone_for_update()).unwrap(); | 849 | (ra_fixture_before.to_owned(), None) |
850 | }; | ||
851 | let syntax = ast::SourceFile::parse(&text).tree().syntax().clone_for_update(); | ||
852 | let file = pos | ||
853 | .and_then(|pos| syntax.token_at_offset(pos.expect_offset()).next()?.parent()) | ||
854 | .and_then(|it| super::ImportScope::find_insert_use_container(&it)) | ||
855 | .or_else(|| super::ImportScope::from(syntax)) | ||
856 | .unwrap(); | ||
773 | let path = ast::SourceFile::parse(&format!("use {};", path)) | 857 | let path = ast::SourceFile::parse(&format!("use {};", path)) |
774 | .tree() | 858 | .tree() |
775 | .syntax() | 859 | .syntax() |
@@ -777,30 +861,41 @@ fn check( | |||
777 | .find_map(ast::Path::cast) | 861 | .find_map(ast::Path::cast) |
778 | .unwrap(); | 862 | .unwrap(); |
779 | 863 | ||
780 | insert_use( | 864 | insert_use(&file, path, config); |
781 | &file, | 865 | let result = file.as_syntax_node().ancestors().last().unwrap().to_string(); |
866 | assert_eq_text!(ra_fixture_after, &result); | ||
867 | } | ||
868 | |||
869 | fn check( | ||
870 | path: &str, | ||
871 | ra_fixture_before: &str, | ||
872 | ra_fixture_after: &str, | ||
873 | granularity: ImportGranularity, | ||
874 | ) { | ||
875 | check_with_config( | ||
782 | path, | 876 | path, |
783 | InsertUseConfig { | 877 | ra_fixture_before, |
878 | ra_fixture_after, | ||
879 | &InsertUseConfig { | ||
784 | granularity, | 880 | granularity, |
785 | enforce_granularity: true, | 881 | enforce_granularity: true, |
786 | prefix_kind: PrefixKind::Plain, | 882 | prefix_kind: PrefixKind::Plain, |
787 | group, | 883 | group: true, |
884 | skip_glob_imports: true, | ||
788 | }, | 885 | }, |
789 | ); | 886 | ) |
790 | let result = file.as_syntax_node().to_string(); | ||
791 | assert_eq_text!(ra_fixture_after, &result); | ||
792 | } | 887 | } |
793 | 888 | ||
794 | fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 889 | fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
795 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Crate, false, true) | 890 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Crate) |
796 | } | 891 | } |
797 | 892 | ||
798 | fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 893 | fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
799 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Module, false, true) | 894 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Module) |
800 | } | 895 | } |
801 | 896 | ||
802 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 897 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
803 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Item, false, true) | 898 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Item) |
804 | } | 899 | } |
805 | 900 | ||
806 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { | 901 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { |