aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db/src/helpers/insert_use.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-05-20 09:27:16 +0100
committerGitHub <[email protected]>2021-05-20 09:27:16 +0100
commit8bb37737c9e8b7a390ae29d5fb0daaeeb495d2b5 (patch)
tree0f8d45124abe86151e1954c1766534116e174209 /crates/ide_db/src/helpers/insert_use.rs
parent764241e38e46316b6370977e8b51e841e93e84b9 (diff)
parent2bf720900f94e36969af44ff8ac52470faf9af4b (diff)
Merge #8873
8873: Implement import-granularity guessing r=matklad a=Veykril This renames our `MergeBehavior` to `ImportGranularity` as rustfmt has it as the purpose of them are basically the same. `ImportGranularity::Preserve` currently has no specific purpose for us as we don't have an organize imports assist yet, so it currently acts the same as `ImportGranularity::Item`. We now try to guess the import style on a per file basis and fall back to the user granularity setting if the file has no specific style yet or where it is ambiguous. This can be turned off by setting `import.enforceGranularity` to `true`. Closes https://github.com/rust-analyzer/rust-analyzer/issues/8870 Co-authored-by: Lukas Tobias Wirth <[email protected]>
Diffstat (limited to 'crates/ide_db/src/helpers/insert_use.rs')
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs105
1 files changed, 101 insertions, 4 deletions
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index 55cdc4da3..aa61c5bcb 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -4,20 +4,36 @@ use std::cmp::Ordering;
4use hir::Semantics; 4use hir::Semantics;
5use syntax::{ 5use syntax::{
6 algo, 6 algo,
7 ast::{self, make, AstNode, PathSegmentKind}, 7 ast::{self, make, AstNode, AttrsOwner, 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_attrs, eq_visibility, try_merge_imports, use_tree_path_cmp, MergeBehavior,
14 },
13 RootDatabase, 15 RootDatabase,
14}; 16};
15 17
16pub use hir::PrefixKind; 18pub use hir::PrefixKind;
17 19
20/// How imports should be grouped into use statements.
21#[derive(Copy, Clone, Debug, PartialEq, Eq)]
22pub enum ImportGranularity {
23 /// Do not change the granularity of any imports and preserve the original structure written by the developer.
24 Preserve,
25 /// Merge imports from the same crate into a single use statement.
26 Crate,
27 /// Merge imports from the same module into a single use statement.
28 Module,
29 /// Flatten imports so that each has its own use statement.
30 Item,
31}
32
18#[derive(Clone, Copy, Debug, PartialEq, Eq)] 33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct InsertUseConfig { 34pub struct InsertUseConfig {
20 pub merge: Option<MergeBehavior>, 35 pub granularity: ImportGranularity,
36 pub enforce_granularity: bool,
21 pub prefix_kind: PrefixKind, 37 pub prefix_kind: PrefixKind,
22 pub group: bool, 38 pub group: bool,
23} 39}
@@ -65,15 +81,96 @@ impl ImportScope {
65 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()), 81 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
66 } 82 }
67 } 83 }
84
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.
88 let use_stmt = |item| match item {
89 ast::Item::Use(use_) => {
90 let use_tree = use_.use_tree()?;
91 Some((use_tree, use_.visibility(), use_.attrs()))
92 }
93 _ => None,
94 };
95 let mut use_stmts = match self {
96 ImportScope::File(f) => f.items(),
97 ImportScope::Module(m) => m.items(),
98 }
99 .filter_map(use_stmt);
100 let mut res = ImportGranularityGuess::Unknown;
101 let (mut prev, mut prev_vis, mut prev_attrs) = match use_stmts.next() {
102 Some(it) => it,
103 None => return res,
104 };
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;
113 }
114 }
115
116 let (curr, curr_vis, curr_attrs) = match use_stmts.next() {
117 Some(it) => it,
118 None => break res,
119 };
120 if eq_visibility(prev_vis, curr_vis.clone()) && eq_attrs(prev_attrs, curr_attrs.clone())
121 {
122 if let Some((prev_path, curr_path)) = prev.path().zip(curr.path()) {
123 if let Some(_) = common_prefix(&prev_path, &curr_path) {
124 if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
125 // Same prefix but no use tree lists so this has to be of item style.
126 break ImportGranularityGuess::Item; // this overwrites CrateOrModule, technically the file doesn't adhere to anything here.
127 } else {
128 // Same prefix with item tree lists, has to be module style as it
129 // can't be crate style since the trees wouldn't share a prefix then.
130 break ImportGranularityGuess::Module;
131 }
132 }
133 }
134 }
135 prev = curr;
136 prev_vis = curr_vis;
137 prev_attrs = curr_attrs;
138 }
139 }
140}
141
142#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
143enum ImportGranularityGuess {
144 Unknown,
145 Item,
146 Module,
147 Crate,
148 CrateOrModule,
68} 149}
69 150
70/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 151/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
71pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { 152pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) {
72 let _p = profile::span("insert_use"); 153 let _p = profile::span("insert_use");
154 let mut mb = match cfg.granularity {
155 ImportGranularity::Crate => Some(MergeBehavior::Crate),
156 ImportGranularity::Module => Some(MergeBehavior::Module),
157 ImportGranularity::Item | ImportGranularity::Preserve => None,
158 };
159 if !cfg.enforce_granularity {
160 let file_granularity = scope.guess_granularity_from_scope();
161 mb = match file_granularity {
162 ImportGranularityGuess::Unknown => mb,
163 ImportGranularityGuess::Item => None,
164 ImportGranularityGuess::Module => Some(MergeBehavior::Module),
165 ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
166 ImportGranularityGuess::CrateOrModule => mb.or(Some(MergeBehavior::Crate)),
167 };
168 }
169
73 let use_item = 170 let use_item =
74 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update(); 171 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
75 // merge into existing imports if possible 172 // merge into existing imports if possible
76 if let Some(mb) = cfg.merge { 173 if let Some(mb) = mb {
77 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 174 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
78 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { 175 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
79 ted::replace(existing_use.syntax(), merged.syntax()); 176 ted::replace(existing_use.syntax(), merged.syntax());