Skip to content

Commit 3f4c6da

Browse files
committed
Auto merge of #16372 - davidsemakula:import-granularity-one, r=Veykril
feat: Add "One" import granularity Adds a new import granularity option "One" that merges all imports into a single use statement as long as they have the same visibility and attributes. This is similar to [rustfmt's `imports_granularity = "One"`](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=import#imports_granularity). Fixes: #11361
2 parents 48af3ef + 67c1c2b commit 3f4c6da

File tree

10 files changed

+473
-85
lines changed

10 files changed

+473
-85
lines changed

crates/ide-assists/src/handlers/auto_import.rs

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
4949
// - `item`: Don't merge imports at all, creating one import per item.
5050
// - `preserve`: Do not change the granularity of any imports. For auto-import this has the same
5151
// effect as `item`.
52+
// - `one`: Merge all imports into a single use statement as long as they have the same visibility
53+
// and attributes.
5254
//
5355
// In `VS Code` the configuration for this is `rust-analyzer.imports.granularity.group`.
5456
//

crates/ide-assists/src/handlers/merge_imports.rs

+169-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use either::Either;
2-
use ide_db::imports::merge_imports::{try_merge_imports, try_merge_trees, MergeBehavior};
2+
use ide_db::imports::{
3+
insert_use::{ImportGranularity, InsertUseConfig},
4+
merge_imports::{try_merge_imports, try_merge_trees, MergeBehavior},
5+
};
36
use syntax::{
47
algo::neighbor,
58
ast::{self, edit_in_place::Removable},
@@ -16,7 +19,7 @@ use Edit::*;
1619

1720
// Assist: merge_imports
1821
//
19-
// Merges two imports with a common prefix.
22+
// Merges neighbor imports with a common prefix.
2023
//
2124
// ```
2225
// use std::$0fmt::Formatter;
@@ -29,15 +32,23 @@ use Edit::*;
2932
pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
3033
let (target, edits) = if ctx.has_empty_selection() {
3134
// Merge a neighbor
32-
let tree: ast::UseTree = ctx.find_node_at_offset()?;
35+
let mut tree: ast::UseTree = ctx.find_node_at_offset()?;
36+
if ctx.config.insert_use.granularity == ImportGranularity::One
37+
&& tree.parent_use_tree_list().is_some()
38+
{
39+
cov_mark::hit!(resolve_top_use_tree_for_import_one);
40+
tree = tree.top_use_tree();
41+
}
3342
let target = tree.syntax().text_range();
3443

3544
let edits = if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
45+
cov_mark::hit!(merge_with_use_item_neighbors);
3646
let mut neighbor = next_prev().find_map(|dir| neighbor(&use_item, dir)).into_iter();
37-
use_item.try_merge_from(&mut neighbor)
47+
use_item.try_merge_from(&mut neighbor, &ctx.config.insert_use)
3848
} else {
49+
cov_mark::hit!(merge_with_use_tree_neighbors);
3950
let mut neighbor = next_prev().find_map(|dir| neighbor(&tree, dir)).into_iter();
40-
tree.try_merge_from(&mut neighbor)
51+
tree.clone().try_merge_from(&mut neighbor, &ctx.config.insert_use)
4152
};
4253
(target, edits?)
4354
} else {
@@ -54,10 +65,12 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
5465
let edits = match_ast! {
5566
match first_selected {
5667
ast::Use(use_item) => {
57-
use_item.try_merge_from(&mut selected_nodes.filter_map(ast::Use::cast))
68+
cov_mark::hit!(merge_with_selected_use_item_neighbors);
69+
use_item.try_merge_from(&mut selected_nodes.filter_map(ast::Use::cast), &ctx.config.insert_use)
5870
},
5971
ast::UseTree(use_tree) => {
60-
use_tree.try_merge_from(&mut selected_nodes.filter_map(ast::UseTree::cast))
72+
cov_mark::hit!(merge_with_selected_use_tree_neighbors);
73+
use_tree.try_merge_from(&mut selected_nodes.filter_map(ast::UseTree::cast), &ctx.config.insert_use)
6174
},
6275
_ => return None,
6376
}
@@ -89,11 +102,15 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
89102
}
90103

91104
trait Merge: AstNode + Clone {
92-
fn try_merge_from(self, items: &mut dyn Iterator<Item = Self>) -> Option<Vec<Edit>> {
105+
fn try_merge_from(
106+
self,
107+
items: &mut dyn Iterator<Item = Self>,
108+
cfg: &InsertUseConfig,
109+
) -> Option<Vec<Edit>> {
93110
let mut edits = Vec::new();
94111
let mut merged = self.clone();
95112
for item in items {
96-
merged = merged.try_merge(&item)?;
113+
merged = merged.try_merge(&item, cfg)?;
97114
edits.push(Edit::Remove(item.into_either()));
98115
}
99116
if !edits.is_empty() {
@@ -103,21 +120,25 @@ trait Merge: AstNode + Clone {
103120
None
104121
}
105122
}
106-
fn try_merge(&self, other: &Self) -> Option<Self>;
123+
fn try_merge(&self, other: &Self, cfg: &InsertUseConfig) -> Option<Self>;
107124
fn into_either(self) -> Either<ast::Use, ast::UseTree>;
108125
}
109126

110127
impl Merge for ast::Use {
111-
fn try_merge(&self, other: &Self) -> Option<Self> {
112-
try_merge_imports(self, other, MergeBehavior::Crate)
128+
fn try_merge(&self, other: &Self, cfg: &InsertUseConfig) -> Option<Self> {
129+
let mb = match cfg.granularity {
130+
ImportGranularity::One => MergeBehavior::One,
131+
_ => MergeBehavior::Crate,
132+
};
133+
try_merge_imports(self, other, mb)
113134
}
114135
fn into_either(self) -> Either<ast::Use, ast::UseTree> {
115136
Either::Left(self)
116137
}
117138
}
118139

119140
impl Merge for ast::UseTree {
120-
fn try_merge(&self, other: &Self) -> Option<Self> {
141+
fn try_merge(&self, other: &Self, _: &InsertUseConfig) -> Option<Self> {
121142
try_merge_trees(self, other, MergeBehavior::Crate)
122143
}
123144
fn into_either(self) -> Either<ast::Use, ast::UseTree> {
@@ -138,12 +159,41 @@ impl Edit {
138159

139160
#[cfg(test)]
140161
mod tests {
141-
use crate::tests::{check_assist, check_assist_not_applicable};
162+
use crate::tests::{
163+
check_assist, check_assist_import_one, check_assist_not_applicable,
164+
check_assist_not_applicable_for_import_one,
165+
};
142166

143167
use super::*;
144168

169+
macro_rules! check_assist_import_one_variations {
170+
($first: literal, $second: literal, $expected: literal) => {
171+
check_assist_import_one(
172+
merge_imports,
173+
concat!(concat!("use ", $first, ";"), concat!("use ", $second, ";")),
174+
$expected,
175+
);
176+
check_assist_import_one(
177+
merge_imports,
178+
concat!(concat!("use {", $first, "};"), concat!("use ", $second, ";")),
179+
$expected,
180+
);
181+
check_assist_import_one(
182+
merge_imports,
183+
concat!(concat!("use ", $first, ";"), concat!("use {", $second, "};")),
184+
$expected,
185+
);
186+
check_assist_import_one(
187+
merge_imports,
188+
concat!(concat!("use {", $first, "};"), concat!("use {", $second, "};")),
189+
$expected,
190+
);
191+
};
192+
}
193+
145194
#[test]
146195
fn test_merge_equal() {
196+
cov_mark::check!(merge_with_use_item_neighbors);
147197
check_assist(
148198
merge_imports,
149199
r"
@@ -153,7 +203,19 @@ use std::fmt::{Display, Debug};
153203
r"
154204
use std::fmt::{Display, Debug};
155205
",
156-
)
206+
);
207+
208+
// The assist macro below calls `check_assist_import_one` 4 times with different input
209+
// use item variations based on the first 2 input parameters, but only 2 calls
210+
// contain `use {std::fmt$0::{Display, Debug}};` for which the top use tree will need
211+
// to be resolved.
212+
cov_mark::check_count!(resolve_top_use_tree_for_import_one, 2);
213+
cov_mark::check_count!(merge_with_use_item_neighbors, 4);
214+
check_assist_import_one_variations!(
215+
"std::fmt$0::{Display, Debug}",
216+
"std::fmt::{Display, Debug}",
217+
"use {std::fmt::{Display, Debug}};"
218+
);
157219
}
158220

159221
#[test]
@@ -167,7 +229,12 @@ use std::fmt::Display;
167229
r"
168230
use std::fmt::{Debug, Display};
169231
",
170-
)
232+
);
233+
check_assist_import_one_variations!(
234+
"std::fmt$0::Debug",
235+
"std::fmt::Display",
236+
"use {std::fmt::{Debug, Display}};"
237+
);
171238
}
172239

173240
#[test]
@@ -182,6 +249,11 @@ use std::fmt$0::Display;
182249
use std::fmt::{Debug, Display};
183250
",
184251
);
252+
check_assist_import_one_variations!(
253+
"std::fmt::Debug",
254+
"std::fmt$0::Display",
255+
"use {std::fmt::{Debug, Display}};"
256+
);
185257
}
186258

187259
#[test]
@@ -196,6 +268,11 @@ use std::fmt::Display;
196268
use std::fmt::{self, Display};
197269
",
198270
);
271+
check_assist_import_one_variations!(
272+
"std::fmt$0",
273+
"std::fmt::Display",
274+
"use {std::fmt::{self, Display}};"
275+
);
199276
}
200277

201278
#[test]
@@ -211,6 +288,15 @@ use std::{fmt::{self, Display}};
211288
);
212289
}
213290

291+
#[test]
292+
fn not_applicable_to_single_one_style_import() {
293+
cov_mark::check!(resolve_top_use_tree_for_import_one);
294+
check_assist_not_applicable_for_import_one(
295+
merge_imports,
296+
"use {std::{fmt, $0fmt::Display}};",
297+
);
298+
}
299+
214300
#[test]
215301
fn skip_pub1() {
216302
check_assist_not_applicable(
@@ -299,6 +385,7 @@ pub(in this::path) use std::fmt::{Debug, Display};
299385

300386
#[test]
301387
fn test_merge_nested() {
388+
cov_mark::check!(merge_with_use_tree_neighbors);
302389
check_assist(
303390
merge_imports,
304391
r"
@@ -335,6 +422,11 @@ use std::{fmt::{self, Debug}};
335422
use std::{fmt::{self, Debug, Display, Write}};
336423
",
337424
);
425+
check_assist_import_one_variations!(
426+
"std$0::{fmt::{Write, Display}}",
427+
"std::{fmt::{self, Debug}}",
428+
"use {std::{fmt::{self, Debug, Display, Write}}};"
429+
);
338430
}
339431

340432
#[test]
@@ -349,6 +441,11 @@ use std::{fmt::{Write, Display}};
349441
use std::{fmt::{self, Debug, Display, Write}};
350442
",
351443
);
444+
check_assist_import_one_variations!(
445+
"std$0::{fmt::{self, Debug}}",
446+
"std::{fmt::{Write, Display}}",
447+
"use {std::{fmt::{self, Debug, Display, Write}}};"
448+
);
352449
}
353450

354451
#[test]
@@ -375,7 +472,12 @@ use foo::{bar};
375472
r"
376473
use foo::{bar::{self}};
377474
",
378-
)
475+
);
476+
check_assist_import_one_variations!(
477+
"foo::$0{bar::{self}}",
478+
"foo::{bar}",
479+
"use {foo::{bar::{self}}};"
480+
);
379481
}
380482

381483
#[test]
@@ -389,7 +491,12 @@ use foo::{bar::{self}};
389491
r"
390492
use foo::{bar::{self}};
391493
",
392-
)
494+
);
495+
check_assist_import_one_variations!(
496+
"foo::$0{bar}",
497+
"foo::{bar::{self}}",
498+
"use {foo::{bar::{self}}};"
499+
);
393500
}
394501

395502
#[test]
@@ -403,7 +510,12 @@ use std::{fmt::{self, Display}};
403510
r"
404511
use std::{fmt::{self, Display, *}};
405512
",
406-
)
513+
);
514+
check_assist_import_one_variations!(
515+
"std$0::{fmt::*}",
516+
"std::{fmt::{self, Display}}",
517+
"use {std::{fmt::{self, Display, *}}};"
518+
);
407519
}
408520

409521
#[test]
@@ -417,7 +529,12 @@ use std::str;
417529
r"
418530
use std::{cell::*, str};
419531
",
420-
)
532+
);
533+
check_assist_import_one_variations!(
534+
"std$0::cell::*",
535+
"std::str",
536+
"use {std::{cell::*, str}};"
537+
);
421538
}
422539

423540
#[test]
@@ -431,7 +548,12 @@ use std::str::*;
431548
r"
432549
use std::{cell::*, str::*};
433550
",
434-
)
551+
);
552+
check_assist_import_one_variations!(
553+
"std$0::cell::*",
554+
"std::str::*",
555+
"use {std::{cell::*, str::*}};"
556+
);
435557
}
436558

437559
#[test]
@@ -524,10 +646,16 @@ use foo::bar::Baz;
524646
use foo::{bar::Baz, *};
525647
",
526648
);
649+
check_assist_import_one_variations!(
650+
"foo::$0*",
651+
"foo::bar::Baz",
652+
"use {foo::{bar::Baz, *}};"
653+
);
527654
}
528655

529656
#[test]
530657
fn merge_selection_uses() {
658+
cov_mark::check!(merge_with_selected_use_item_neighbors);
531659
check_assist(
532660
merge_imports,
533661
r"
@@ -541,12 +669,30 @@ $0use std::fmt::Result;
541669
use std::fmt::Error;
542670
use std::fmt::{Debug, Display, Write};
543671
use std::fmt::Result;
672+
",
673+
);
674+
675+
cov_mark::check!(merge_with_selected_use_item_neighbors);
676+
check_assist_import_one(
677+
merge_imports,
678+
r"
679+
use std::fmt::Error;
680+
$0use std::fmt::Display;
681+
use std::fmt::Debug;
682+
use std::fmt::Write;
683+
$0use std::fmt::Result;
684+
",
685+
r"
686+
use std::fmt::Error;
687+
use {std::fmt::{Debug, Display, Write}};
688+
use std::fmt::Result;
544689
",
545690
);
546691
}
547692

548693
#[test]
549694
fn merge_selection_use_trees() {
695+
cov_mark::check!(merge_with_selected_use_tree_neighbors);
550696
check_assist(
551697
merge_imports,
552698
r"
@@ -564,7 +710,9 @@ use std::{
564710
fmt::Result,
565711
};",
566712
);
713+
567714
// FIXME: Remove redundant braces. See also unnecessary-braces diagnostic.
715+
cov_mark::check!(merge_with_selected_use_tree_neighbors);
568716
check_assist(
569717
merge_imports,
570718
r"use std::$0{fmt::Display, fmt::Debug}$0;",

0 commit comments

Comments
 (0)