1#[cfg(test)]
3mod tests;
4
5use std::cmp::Ordering;
6
7use hir::Semantics;
8use syntax::{
9 Direction, NodeOrToken, SyntaxKind, SyntaxNode, algo,
10 ast::{
11 self, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind,
12 edit_in_place::Removable, make,
13 },
14 ted,
15};
16
17use crate::{
18 RootDatabase,
19 imports::merge_imports::{
20 MergeBehavior, NormalizationStyle, common_prefix, eq_attrs, eq_visibility,
21 try_merge_imports, use_tree_cmp,
22 },
23};
24
25pub use hir::PrefixKind;
26
27#[derive(Copy, Clone, Debug, PartialEq, Eq)]
29pub enum ImportGranularity {
30 Preserve,
33 Crate,
35 Module,
37 Item,
39 One,
42}
43
44impl From<ImportGranularity> for NormalizationStyle {
45 fn from(granularity: ImportGranularity) -> Self {
46 match granularity {
47 ImportGranularity::One => NormalizationStyle::One,
48 _ => NormalizationStyle::Default,
49 }
50 }
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub struct InsertUseConfig {
55 pub granularity: ImportGranularity,
56 pub enforce_granularity: bool,
57 pub prefix_kind: PrefixKind,
58 pub group: bool,
59 pub skip_glob_imports: bool,
60}
61
62#[derive(Debug, Clone)]
63pub struct ImportScope {
64 pub kind: ImportScopeKind,
65 pub required_cfgs: Vec<ast::Attr>,
66}
67
68#[derive(Debug, Clone)]
69pub enum ImportScopeKind {
70 File(ast::SourceFile),
71 Module(ast::ItemList),
72 Block(ast::StmtList),
73}
74
75impl ImportScope {
76 pub fn find_insert_use_container(
79 position: &SyntaxNode,
80 sema: &Semantics<'_, RootDatabase>,
81 ) -> Option<Self> {
82 let mut block = None;
84 let mut required_cfgs = Vec::new();
85 for syntax in sema.ancestors_with_macros(position.clone()) {
89 if let Some(file) = ast::SourceFile::cast(syntax.clone()) {
90 return Some(ImportScope { kind: ImportScopeKind::File(file), required_cfgs });
91 } else if let Some(module) = ast::Module::cast(syntax.clone()) {
92 return sema
95 .original_ast_node(module)?
96 .item_list()
97 .map(ImportScopeKind::Module)
98 .map(|kind| ImportScope { kind, required_cfgs });
99 } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax) {
100 if block.is_none()
101 && let Some(b) = ast::BlockExpr::cast(has_attrs.syntax().clone())
102 && let Some(b) = sema.original_ast_node(b)
103 {
104 block = b.stmt_list();
105 }
106 if has_attrs
107 .attrs()
108 .any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
109 {
110 if let Some(b) = block {
111 return Some(ImportScope {
112 kind: ImportScopeKind::Block(b),
113 required_cfgs,
114 });
115 }
116 required_cfgs.extend(has_attrs.attrs().filter(|attr| {
117 attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
118 }));
119 }
120 }
121 }
122 None
123 }
124
125 pub fn as_syntax_node(&self) -> &SyntaxNode {
126 match &self.kind {
127 ImportScopeKind::File(file) => file.syntax(),
128 ImportScopeKind::Module(item_list) => item_list.syntax(),
129 ImportScopeKind::Block(block) => block.syntax(),
130 }
131 }
132
133 pub fn clone_for_update(&self) -> Self {
134 Self {
135 kind: match &self.kind {
136 ImportScopeKind::File(file) => ImportScopeKind::File(file.clone_for_update()),
137 ImportScopeKind::Module(item_list) => {
138 ImportScopeKind::Module(item_list.clone_for_update())
139 }
140 ImportScopeKind::Block(block) => ImportScopeKind::Block(block.clone_for_update()),
141 },
142 required_cfgs: self.required_cfgs.iter().map(|attr| attr.clone_for_update()).collect(),
143 }
144 }
145}
146
147pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
149 insert_use_with_alias_option(scope, path, cfg, None);
150}
151
152pub fn insert_use_as_alias(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
153 let text: &str = "use foo as _";
154 let parse = syntax::SourceFile::parse(text, span::Edition::CURRENT_FIXME);
155 let node = parse
156 .tree()
157 .syntax()
158 .descendants()
159 .find_map(ast::UseTree::cast)
160 .expect("Failed to make ast node `Rename`");
161 let alias = node.rename();
162
163 insert_use_with_alias_option(scope, path, cfg, alias);
164}
165
166fn insert_use_with_alias_option(
167 scope: &ImportScope,
168 path: ast::Path,
169 cfg: &InsertUseConfig,
170 alias: Option<ast::Rename>,
171) {
172 let _p = tracing::info_span!("insert_use_with_alias_option").entered();
173 let mut mb = match cfg.granularity {
174 ImportGranularity::Crate => Some(MergeBehavior::Crate),
175 ImportGranularity::Module => Some(MergeBehavior::Module),
176 ImportGranularity::One => Some(MergeBehavior::One),
177 ImportGranularity::Item | ImportGranularity::Preserve => None,
178 };
179 if !cfg.enforce_granularity {
180 let file_granularity = guess_granularity_from_scope(scope);
181 mb = match file_granularity {
182 ImportGranularityGuess::Unknown => mb,
183 ImportGranularityGuess::Item => None,
184 ImportGranularityGuess::Module => Some(MergeBehavior::Module),
185 ImportGranularityGuess::ModuleOrItem => mb.and(Some(MergeBehavior::Module)),
186 ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
187 ImportGranularityGuess::CrateOrModule => mb.or(Some(MergeBehavior::Crate)),
188 ImportGranularityGuess::One => Some(MergeBehavior::One),
189 };
190 }
191
192 let mut use_tree = make::use_tree(path, None, alias, false);
193 if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
194 use_tree = use_tree.clone_for_update();
195 use_tree.wrap_in_tree_list();
196 }
197 let use_item = make::use_(None, None, use_tree).clone_for_update();
198 for attr in
199 scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
200 {
201 ted::insert(ted::Position::first_child_of(use_item.syntax()), attr);
202 }
203
204 if let Some(mb) = mb {
206 let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
207 for existing_use in
208 scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
209 {
210 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
211 ted::replace(existing_use.syntax(), merged.syntax());
212 return;
213 }
214 }
215 }
216 insert_use_(scope, use_item, cfg.group);
219}
220
221pub fn ast_to_remove_for_path_in_use_stmt(path: &ast::Path) -> Option<Box<dyn Removable>> {
222 if path.parent_path().is_some() {
224 return None;
225 }
226 let use_tree = path.syntax().parent().and_then(ast::UseTree::cast)?;
227 if use_tree.use_tree_list().is_some() || use_tree.star_token().is_some() {
228 return None;
229 }
230 if let Some(use_) = use_tree.syntax().parent().and_then(ast::Use::cast) {
231 return Some(Box::new(use_));
232 }
233 Some(Box::new(use_tree))
234}
235
236pub fn remove_path_if_in_use_stmt(path: &ast::Path) {
237 if let Some(node) = ast_to_remove_for_path_in_use_stmt(path) {
238 node.remove();
239 }
240}
241
242#[derive(Eq, PartialEq, PartialOrd, Ord)]
243enum ImportGroup {
244 Std,
246 ExternCrate,
247 ThisCrate,
248 ThisModule,
249 SuperModule,
250 One,
251}
252
253impl ImportGroup {
254 fn new(use_tree: &ast::UseTree) -> ImportGroup {
255 if use_tree.path().is_none() && use_tree.use_tree_list().is_some() {
256 return ImportGroup::One;
257 }
258
259 let Some(first_segment) = use_tree.path().as_ref().and_then(ast::Path::first_segment)
260 else {
261 return ImportGroup::ExternCrate;
262 };
263
264 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
265 match kind {
266 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
267 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
268 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
269 PathSegmentKind::Name(name) => match name.text().as_str() {
270 "std" => ImportGroup::Std,
271 "core" => ImportGroup::Std,
272 _ => ImportGroup::ExternCrate,
273 },
274 PathSegmentKind::SelfTypeKw => ImportGroup::ExternCrate,
276 PathSegmentKind::Type { .. } => ImportGroup::ExternCrate,
277 }
278 }
279}
280
281#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
282enum ImportGranularityGuess {
283 Unknown,
284 Item,
285 Module,
286 ModuleOrItem,
287 Crate,
288 CrateOrModule,
289 One,
290}
291
292fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
293 let use_stmt = |item| match item {
296 ast::Item::Use(use_) => {
297 let use_tree = use_.use_tree()?;
298 Some((use_tree, use_.visibility(), use_.attrs()))
299 }
300 _ => None,
301 };
302 let mut use_stmts = match &scope.kind {
303 ImportScopeKind::File(f) => f.items(),
304 ImportScopeKind::Module(m) => m.items(),
305 ImportScopeKind::Block(b) => b.items(),
306 }
307 .filter_map(use_stmt);
308 let mut res = ImportGranularityGuess::Unknown;
309 let Some((mut prev, mut prev_vis, mut prev_attrs)) = use_stmts.next() else { return res };
310
311 let is_tree_one_style =
312 |use_tree: &ast::UseTree| use_tree.path().is_none() && use_tree.use_tree_list().is_some();
313 let mut seen_one_style_groups = Vec::new();
314
315 loop {
316 if is_tree_one_style(&prev) {
317 if res != ImportGranularityGuess::One {
318 if res != ImportGranularityGuess::Unknown {
319 break ImportGranularityGuess::Unknown;
321 }
322
323 res = ImportGranularityGuess::One;
324 seen_one_style_groups.push((prev_vis.clone(), prev_attrs.clone()));
325 }
326 } else if let Some(use_tree_list) = prev.use_tree_list() {
327 if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
328 break ImportGranularityGuess::Crate;
330 } else {
331 res = ImportGranularityGuess::CrateOrModule;
333 }
334 }
335
336 let Some((curr, curr_vis, curr_attrs)) = use_stmts.next() else { break res };
337 if is_tree_one_style(&curr) {
338 if res != ImportGranularityGuess::One
339 || seen_one_style_groups.iter().any(|(prev_vis, prev_attrs)| {
340 eq_visibility(prev_vis.clone(), curr_vis.clone())
341 && eq_attrs(prev_attrs.clone(), curr_attrs.clone())
342 })
343 {
344 break ImportGranularityGuess::Unknown;
347 }
348 seen_one_style_groups.push((curr_vis.clone(), curr_attrs.clone()));
349 } else if eq_visibility(prev_vis, curr_vis.clone())
350 && eq_attrs(prev_attrs, curr_attrs.clone())
351 && let Some((prev_path, curr_path)) = prev.path().zip(curr.path())
352 && let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path)
353 {
354 if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
355 let prefix_c = prev_prefix.qualifiers().count();
356 let curr_c = curr_path.qualifiers().count() - prefix_c;
357 let prev_c = prev_path.qualifiers().count() - prefix_c;
358 if curr_c == 1 && prev_c == 1 {
359 break ImportGranularityGuess::Item;
361 } else {
362 res = ImportGranularityGuess::ModuleOrItem;
364 }
365 } else {
366 break ImportGranularityGuess::Module;
369 }
370 }
371 prev = curr;
372 prev_vis = curr_vis;
373 prev_attrs = curr_attrs;
374 }
375}
376
377fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
378 let scope_syntax = scope.as_syntax_node();
379 let insert_use_tree =
380 use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
381 let group = ImportGroup::new(&insert_use_tree);
382 let path_node_iter = scope_syntax
383 .children()
384 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
385 .flat_map(|(use_, node)| {
386 let tree = use_.use_tree()?;
387 Some((tree, node))
388 });
389
390 if group_imports {
391 let group_iter = path_node_iter
394 .clone()
395 .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
396 .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
397
398 let mut last = None;
400 let post_insert: Option<(_, SyntaxNode)> = group_iter
402 .inspect(|(.., node)| last = Some(node.clone()))
403 .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
404
405 if let Some((.., node)) = post_insert {
406 cov_mark::hit!(insert_group);
407 return ted::insert(ted::Position::before(node), use_item.syntax());
409 }
410 if let Some(node) = last {
411 cov_mark::hit!(insert_group_last);
412 return ted::insert(ted::Position::after(node), use_item.syntax());
414 }
415
416 let mut last = None;
419 let post_group = path_node_iter
421 .inspect(|(.., node)| last = Some(node.clone()))
422 .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
423 if let Some((.., node)) = post_group {
424 cov_mark::hit!(insert_group_new_group);
425 ted::insert(ted::Position::before(&node), use_item.syntax());
426 if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
427 ted::insert(ted::Position::after(node), make::tokens::single_newline());
428 }
429 return;
430 }
431 if let Some(node) = last {
433 cov_mark::hit!(insert_group_no_group);
434 ted::insert(ted::Position::after(&node), use_item.syntax());
435 ted::insert(ted::Position::after(node), make::tokens::single_newline());
436 return;
437 }
438 } else {
439 if let Some((_, node)) = path_node_iter.last() {
441 cov_mark::hit!(insert_no_grouping_last);
442 ted::insert(ted::Position::after(node), use_item.syntax());
443 return;
444 }
445 }
446
447 let l_curly = match &scope.kind {
448 ImportScopeKind::File(_) => None,
449 ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
451 ImportScopeKind::Block(block) => block.l_curly_token(),
453 };
454 if let Some(last_inner_element) = scope_syntax
457 .children_with_tokens()
458 .skip(l_curly.is_some() as usize)
460 .take_while(|child| match child {
461 NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
462 NodeOrToken::Token(token) => {
463 [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
464 .contains(&token.kind())
465 }
466 })
467 .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
468 .last()
469 {
470 cov_mark::hit!(insert_empty_inner_attr);
471 ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
472 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
473 } else {
474 match l_curly {
475 Some(b) => {
476 cov_mark::hit!(insert_empty_module);
477 ted::insert(ted::Position::after(&b), make::tokens::single_newline());
478 ted::insert(ted::Position::after(&b), use_item.syntax());
479 }
480 None => {
481 cov_mark::hit!(insert_empty_file);
482 ted::insert(
483 ted::Position::first_child_of(scope_syntax),
484 make::tokens::blank_line(),
485 );
486 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
487 }
488 }
489 }
490}
491
492fn is_inner_attribute(node: SyntaxNode) -> bool {
493 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
494}