aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorLukas Tobias Wirth <[email protected]>2021-05-18 19:21:47 +0100
committerLukas Tobias Wirth <[email protected]>2021-05-19 14:57:10 +0100
commitb4fe479236f592fcbfa1422dda54253b77d8b0e1 (patch)
tree0ce721228484783514b8948a0a64e3e4ca38ea95 /crates
parent5fd9f6c7b9944638e4781e3d9384638942f84456 (diff)
Replace ImportGranularity::Guess with guessing boolean flag
Diffstat (limited to 'crates')
-rw-r--r--crates/ide_assists/src/tests.rs1
-rw-r--r--crates/ide_completion/src/test_utils.rs1
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs85
-rw-r--r--crates/ide_db/src/helpers/insert_use/tests.rs115
-rw-r--r--crates/ide_db/src/helpers/merge_imports.rs4
-rw-r--r--crates/rust-analyzer/src/config.rs25
-rw-r--r--crates/rust-analyzer/src/integrated_benchmarks.rs2
-rw-r--r--crates/rust-analyzer/src/to_proto.rs1
8 files changed, 206 insertions, 28 deletions
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index bb1ca0b3d..1739302bf 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -26,6 +26,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
26 insert_use: InsertUseConfig { 26 insert_use: InsertUseConfig {
27 granularity: ImportGranularity::Crate, 27 granularity: ImportGranularity::Crate,
28 prefix_kind: hir::PrefixKind::Plain, 28 prefix_kind: hir::PrefixKind::Plain,
29 enforce_granularity: true,
29 group: true, 30 group: true,
30 }, 31 },
31}; 32};
diff --git a/crates/ide_completion/src/test_utils.rs b/crates/ide_completion/src/test_utils.rs
index b150a5c3f..37be575e5 100644
--- a/crates/ide_completion/src/test_utils.rs
+++ b/crates/ide_completion/src/test_utils.rs
@@ -25,6 +25,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
25 insert_use: InsertUseConfig { 25 insert_use: InsertUseConfig {
26 granularity: ImportGranularity::Crate, 26 granularity: ImportGranularity::Crate,
27 prefix_kind: PrefixKind::Plain, 27 prefix_kind: PrefixKind::Plain,
28 enforce_granularity: true,
28 group: true, 29 group: true,
29 }, 30 },
30}; 31};
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index 1fdd110cc..e7ae69302 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -4,12 +4,14 @@ use std::cmp::Ordering;
4use hir::Semantics; 4use hir::Semantics;
5use syntax::{ 5use syntax::{
6 algo, 6 algo,
7 ast::{self, make, AstNode, ModuleItemOwner, PathSegmentKind}, 7 ast::{self, make, AstNode, ModuleItemOwner, PathSegmentKind, VisibilityOwner},
8 ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken, 8 ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken,
9}; 9};
10 10
11use crate::{ 11use crate::{
12 helpers::merge_imports::{try_merge_imports, use_tree_path_cmp, MergeBehavior}, 12 helpers::merge_imports::{
13 common_prefix, eq_visibility, try_merge_imports, use_tree_path_cmp, MergeBehavior,
14 },
13 RootDatabase, 15 RootDatabase,
14}; 16};
15 17
@@ -18,8 +20,6 @@ pub use hir::PrefixKind;
18/// How imports should be grouped into use statements. 20/// How imports should be grouped into use statements.
19#[derive(Copy, Clone, Debug, PartialEq, Eq)] 21#[derive(Copy, Clone, Debug, PartialEq, Eq)]
20pub enum ImportGranularity { 22pub enum ImportGranularity {
21 /// Try to guess the granularity of imports on a per module basis by observing the existing imports.
22 Guess,
23 /// Do not change the granularity of any imports and preserve the original structure written by the developer. 23 /// Do not change the granularity of any imports and preserve the original structure written by the developer.
24 Preserve, 24 Preserve,
25 /// Merge imports from the same crate into a single use statement. 25 /// Merge imports from the same crate into a single use statement.
@@ -33,6 +33,7 @@ pub enum ImportGranularity {
33#[derive(Clone, Copy, Debug, PartialEq, Eq)] 33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct InsertUseConfig { 34pub struct InsertUseConfig {
35 pub granularity: ImportGranularity, 35 pub granularity: ImportGranularity,
36 pub enforce_granularity: bool,
36 pub prefix_kind: PrefixKind, 37 pub prefix_kind: PrefixKind,
37 pub group: bool, 38 pub group: bool,
38} 39}
@@ -81,40 +82,88 @@ impl ImportScope {
81 } 82 }
82 } 83 }
83 84
84 fn guess_merge_behavior_from_scope(&self) -> Option<MergeBehavior> { 85 fn guess_granularity_from_scope(&self) -> ImportGranularityGuess {
86 // The idea is simple, just check each import as well as the import and its precedent together for
87 // whether they fulfill a granularity criteria.
85 let use_stmt = |item| match item { 88 let use_stmt = |item| match item {
86 ast::Item::Use(use_) => use_.use_tree(), 89 ast::Item::Use(use_) => {
90 let use_tree = use_.use_tree()?;
91 Some((use_tree, use_.visibility()))
92 }
87 _ => None, 93 _ => None,
88 }; 94 };
89 let use_stmts = match self { 95 let mut use_stmts = match self {
90 ImportScope::File(f) => f.items(), 96 ImportScope::File(f) => f.items(),
91 ImportScope::Module(m) => m.items(), 97 ImportScope::Module(m) => m.items(),
92 } 98 }
93 .filter_map(use_stmt); 99 .filter_map(use_stmt);
94 let mut res = None; 100 let mut res = ImportGranularityGuess::Unknown;
95 for tree in use_stmts { 101 let (mut prev, mut prev_vis) = match use_stmts.next() {
96 if let Some(list) = tree.use_tree_list() { 102 Some(it) => it,
97 if list.use_trees().any(|tree| tree.use_tree_list().is_some()) { 103 None => return res,
98 // double nested tree list, can only be a crate style import at this point 104 };
99 return Some(MergeBehavior::Crate); 105 loop {
106 if let Some(use_tree_list) = prev.use_tree_list() {
107 if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
108 // Nested tree lists can only occur in crate style, or with no proper style being enforced in the file.
109 break ImportGranularityGuess::Crate;
110 } else {
111 // Could still be crate-style so continue looking.
112 res = ImportGranularityGuess::CrateOrModule;
100 } 113 }
101 // has to be at least a module style based import, might be crate style tho so look further
102 res = Some(MergeBehavior::Module);
103 } 114 }
115
116 let (curr, curr_vis) = match use_stmts.next() {
117 Some(it) => it,
118 None => break res,
119 };
120 if eq_visibility(prev_vis, curr_vis.clone()) {
121 if let Some((prev_path, curr_path)) = prev.path().zip(curr.path()) {
122 if let Some(_) = common_prefix(&prev_path, &curr_path) {
123 if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
124 // Same prefix but no use tree lists so this has to be of item style.
125 break ImportGranularityGuess::Item; // this overwrites CrateOrModule, technically the file doesn't adhere to anything here.
126 } else {
127 // Same prefix with item tree lists, has to be module style as it
128 // can't be crate style since the trees wouldn't share a prefix then.
129 break ImportGranularityGuess::Module;
130 }
131 }
132 }
133 }
134 prev = curr;
135 prev_vis = curr_vis;
104 } 136 }
105 res
106 } 137 }
107} 138}
108 139
140#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
141enum ImportGranularityGuess {
142 Unknown,
143 Item,
144 Module,
145 Crate,
146 CrateOrModule,
147}
148
109/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 149/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
110pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { 150pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) {
111 let _p = profile::span("insert_use"); 151 let _p = profile::span("insert_use");
112 let mb = match cfg.granularity { 152 let mut mb = match cfg.granularity {
113 ImportGranularity::Guess => scope.guess_merge_behavior_from_scope(),
114 ImportGranularity::Crate => Some(MergeBehavior::Crate), 153 ImportGranularity::Crate => Some(MergeBehavior::Crate),
115 ImportGranularity::Module => Some(MergeBehavior::Module), 154 ImportGranularity::Module => Some(MergeBehavior::Module),
116 ImportGranularity::Item | ImportGranularity::Preserve => None, 155 ImportGranularity::Item | ImportGranularity::Preserve => None,
117 }; 156 };
157 if !cfg.enforce_granularity {
158 let file_granularity = scope.guess_granularity_from_scope();
159 mb = match file_granularity {
160 ImportGranularityGuess::Unknown => mb,
161 ImportGranularityGuess::Item => None,
162 ImportGranularityGuess::Module => Some(MergeBehavior::Module),
163 ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
164 ImportGranularityGuess::CrateOrModule => mb.or(Some(MergeBehavior::Crate)),
165 };
166 }
118 167
119 let use_item = 168 let use_item =
120 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update(); 169 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index f99857a89..f795bbf00 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -631,6 +631,104 @@ fn merge_last_fail3() {
631 ); 631 );
632} 632}
633 633
634#[test]
635fn guess_empty() {
636 check_guess("", ImportGranularityGuess::Unknown);
637}
638
639#[test]
640fn guess_single() {
641 check_guess(r"use foo::{baz::{qux, quux}, bar};", ImportGranularityGuess::Crate);
642 check_guess(r"use foo::bar;", ImportGranularityGuess::Unknown);
643 check_guess(r"use foo::bar::{baz, qux};", ImportGranularityGuess::CrateOrModule);
644}
645
646#[test]
647fn guess_unknown() {
648 check_guess(
649 r"
650use foo::bar::baz;
651use oof::rab::xuq;
652",
653 ImportGranularityGuess::Unknown,
654 );
655}
656
657#[test]
658fn guess_item() {
659 check_guess(
660 r"
661use foo::bar::baz;
662use foo::bar::qux;
663",
664 ImportGranularityGuess::Item,
665 );
666}
667
668#[test]
669fn guess_module() {
670 check_guess(
671 r"
672use foo::bar::baz;
673use foo::bar::{qux, quux};
674",
675 ImportGranularityGuess::Module,
676 );
677 // this is a rather odd case, technically this file isn't following any style properly.
678 check_guess(
679 r"
680use foo::bar::baz;
681use foo::{baz::{qux, quux}, bar};
682",
683 ImportGranularityGuess::Module,
684 );
685}
686
687#[test]
688fn guess_crate_or_module() {
689 check_guess(
690 r"
691use foo::bar::baz;
692use oof::bar::{qux, quux};
693",
694 ImportGranularityGuess::CrateOrModule,
695 );
696}
697
698#[test]
699fn guess_crate() {
700 check_guess(
701 r"
702use frob::bar::baz;
703use foo::{baz::{qux, quux}, bar};
704",
705 ImportGranularityGuess::Crate,
706 );
707}
708
709#[test]
710fn guess_skips_differing_vis() {
711 check_guess(
712 r"
713use foo::bar::baz;
714pub use foo::bar::qux;
715",
716 ImportGranularityGuess::Unknown,
717 );
718}
719
720#[test]
721fn guess_grouping_matters() {
722 check_guess(
723 r"
724use foo::bar::baz;
725use oof::bar::baz;
726use foo::bar::qux;
727",
728 ImportGranularityGuess::Unknown,
729 );
730}
731
634fn check( 732fn check(
635 path: &str, 733 path: &str,
636 ra_fixture_before: &str, 734 ra_fixture_before: &str,
@@ -651,7 +749,16 @@ fn check(
651 .find_map(ast::Path::cast) 749 .find_map(ast::Path::cast)
652 .unwrap(); 750 .unwrap();
653 751
654 insert_use(&file, path, InsertUseConfig { granularity, prefix_kind: PrefixKind::Plain, group }); 752 insert_use(
753 &file,
754 path,
755 InsertUseConfig {
756 granularity,
757 enforce_granularity: true,
758 prefix_kind: PrefixKind::Plain,
759 group,
760 },
761 );
655 let result = file.as_syntax_node().to_string(); 762 let result = file.as_syntax_node().to_string();
656 assert_eq_text!(ra_fixture_after, &result); 763 assert_eq_text!(ra_fixture_after, &result);
657} 764}
@@ -686,3 +793,9 @@ fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior
686 let result = try_merge_imports(&use0, &use1, mb); 793 let result = try_merge_imports(&use0, &use1, mb);
687 assert_eq!(result.map(|u| u.to_string()), None); 794 assert_eq!(result.map(|u| u.to_string()), None);
688} 795}
796
797fn check_guess(ra_fixture: &str, expected: ImportGranularityGuess) {
798 let syntax = ast::SourceFile::parse(ra_fixture).tree().syntax().clone();
799 let file = super::ImportScope::from(syntax).unwrap();
800 assert_eq!(file.guess_granularity_from_scope(), expected);
801}
diff --git a/crates/ide_db/src/helpers/merge_imports.rs b/crates/ide_db/src/helpers/merge_imports.rs
index 8fb40e837..546f2d0c4 100644
--- a/crates/ide_db/src/helpers/merge_imports.rs
+++ b/crates/ide_db/src/helpers/merge_imports.rs
@@ -181,7 +181,7 @@ fn recursive_merge(
181} 181}
182 182
183/// Traverses both paths until they differ, returning the common prefix of both. 183/// Traverses both paths until they differ, returning the common prefix of both.
184fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> { 184pub fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
185 let mut res = None; 185 let mut res = None;
186 let mut lhs_curr = lhs.first_qualifier_or_self(); 186 let mut lhs_curr = lhs.first_qualifier_or_self();
187 let mut rhs_curr = rhs.first_qualifier_or_self(); 187 let mut rhs_curr = rhs.first_qualifier_or_self();
@@ -289,7 +289,7 @@ fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
289 a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text)) 289 a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text))
290} 290}
291 291
292fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { 292pub fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
293 match (vis0, vis1) { 293 match (vis0, vis1) {
294 (None, None) => true, 294 (None, None) => true,
295 // FIXME: Don't use the string representation to check for equality 295 // FIXME: Don't use the string representation to check for equality
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index a34ca97ac..867d72ea4 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -33,16 +33,18 @@ use crate::{
33// be specified directly in `package.json`. 33// be specified directly in `package.json`.
34config_data! { 34config_data! {
35 struct ConfigData { 35 struct ConfigData {
36 /// The strategy to use when inserting new imports or merging imports. 36 /// How imports should be grouped into use statements.
37 assist_importGranularity | 37 assist_importGranularity |
38 assist_importMergeBehavior | 38 assist_importMergeBehavior |
39 assist_importMergeBehaviour: ImportGranularityDef = "\"guess\"", 39 assist_importMergeBehaviour: ImportGranularityDef = "\"crate\"",
40 /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.
41 assist_importEnforceGranularity: bool = "false",
40 /// The path structure for newly inserted paths to use. 42 /// The path structure for newly inserted paths to use.
41 assist_importPrefix: ImportPrefixDef = "\"plain\"", 43 assist_importPrefix: ImportPrefixDef = "\"plain\"",
42 /// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines. 44 /// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.
43 assist_importGroup: bool = "true", 45 assist_importGroup: bool = "true",
44 /// Show function name and docs in parameter hints. 46 /// Show function name and docs in parameter hints.
45 callInfo_full: bool = "true", 47 callInfo_full: bool = "true",
46 48
47 /// Automatically refresh project info via `cargo metadata` on 49 /// Automatically refresh project info via `cargo metadata` on
48 /// `Cargo.toml` changes. 50 /// `Cargo.toml` changes.
@@ -610,12 +612,12 @@ impl Config {
610 fn insert_use_config(&self) -> InsertUseConfig { 612 fn insert_use_config(&self) -> InsertUseConfig {
611 InsertUseConfig { 613 InsertUseConfig {
612 granularity: match self.data.assist_importGranularity { 614 granularity: match self.data.assist_importGranularity {
613 ImportGranularityDef::Guess => ImportGranularity::Guess,
614 ImportGranularityDef::Preserve => ImportGranularity::Preserve, 615 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
615 ImportGranularityDef::Item => ImportGranularity::Item, 616 ImportGranularityDef::Item => ImportGranularity::Item,
616 ImportGranularityDef::Crate => ImportGranularity::Crate, 617 ImportGranularityDef::Crate => ImportGranularity::Crate,
617 ImportGranularityDef::Module => ImportGranularity::Module, 618 ImportGranularityDef::Module => ImportGranularity::Module,
618 }, 619 },
620 enforce_granularity: self.data.assist_importEnforceGranularity,
619 prefix_kind: match self.data.assist_importPrefix { 621 prefix_kind: match self.data.assist_importPrefix {
620 ImportPrefixDef::Plain => PrefixKind::Plain, 622 ImportPrefixDef::Plain => PrefixKind::Plain,
621 ImportPrefixDef::ByCrate => PrefixKind::ByCrate, 623 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
@@ -721,7 +723,6 @@ enum ManifestOrProjectJson {
721#[serde(rename_all = "snake_case")] 723#[serde(rename_all = "snake_case")]
722enum ImportGranularityDef { 724enum ImportGranularityDef {
723 Preserve, 725 Preserve,
724 Guess,
725 #[serde(alias = "none")] 726 #[serde(alias = "none")]
726 Item, 727 Item,
727 #[serde(alias = "full")] 728 #[serde(alias = "full")]
@@ -891,6 +892,16 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
891 "Merge imports from the same module into a single `use` statement." 892 "Merge imports from the same module into a single `use` statement."
892 ], 893 ],
893 }, 894 },
895 "ImportGranularityDef" => set! {
896 "type": "string",
897 "enum": ["preserve", "crate", "module", "item"],
898 "enumDescriptions": [
899 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
900 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
901 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
902 "Flatten imports so that each has its own use statement."
903 ],
904 },
894 "ImportPrefixDef" => set! { 905 "ImportPrefixDef" => set! {
895 "type": "string", 906 "type": "string",
896 "enum": [ 907 "enum": [
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index cd0b8d559..781073fe5 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -138,6 +138,7 @@ fn integrated_completion_benchmark() {
138 insert_use: InsertUseConfig { 138 insert_use: InsertUseConfig {
139 granularity: ImportGranularity::Crate, 139 granularity: ImportGranularity::Crate,
140 prefix_kind: hir::PrefixKind::ByCrate, 140 prefix_kind: hir::PrefixKind::ByCrate,
141 enforce_granularity: true,
141 group: true, 142 group: true,
142 }, 143 },
143 }; 144 };
@@ -171,6 +172,7 @@ fn integrated_completion_benchmark() {
171 insert_use: InsertUseConfig { 172 insert_use: InsertUseConfig {
172 granularity: ImportGranularity::Crate, 173 granularity: ImportGranularity::Crate,
173 prefix_kind: hir::PrefixKind::ByCrate, 174 prefix_kind: hir::PrefixKind::ByCrate,
175 enforce_granularity: true,
174 group: true, 176 group: true,
175 }, 177 },
176 }; 178 };
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 64d5f9e2e..ef9e0aee9 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -1179,6 +1179,7 @@ mod tests {
1179 insert_use: InsertUseConfig { 1179 insert_use: InsertUseConfig {
1180 granularity: ImportGranularity::Item, 1180 granularity: ImportGranularity::Item,
1181 prefix_kind: PrefixKind::Plain, 1181 prefix_kind: PrefixKind::Plain,
1182 enforce_granularity: true,
1182 group: true, 1183 group: true,
1183 }, 1184 },
1184 }, 1185 },