aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db/src/helpers/insert_use.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_db/src/helpers/insert_use.rs')
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs85
1 files changed, 67 insertions, 18 deletions
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();