Skip to content

Commit ca9b050

Browse files
magurotunacalebcartwright
authored andcommitted
Implement One option for imports_granularity (#4669)
This option merges all imports into a single `use` statement as long as they have the same visibility.
1 parent e81c393 commit ca9b050

File tree

6 files changed

+288
-19
lines changed

6 files changed

+288
-19
lines changed

Configurations.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -1679,7 +1679,7 @@ pub enum Foo {}
16791679
How imports should be grouped into `use` statements. Imports will be merged or split to the configured level of granularity.
16801680

16811681
- **Default value**: `Preserve`
1682-
- **Possible values**: `Preserve`, `Crate`, `Module`, `Item`
1682+
- **Possible values**: `Preserve`, `Crate`, `Module`, `Item`, `One`
16831683
- **Stable**: No
16841684

16851685
#### `Preserve` (default):
@@ -1733,6 +1733,23 @@ use qux::h;
17331733
use qux::i;
17341734
```
17351735

1736+
#### `One`:
1737+
1738+
Merge all imports into a single `use` statement as long as they have the same visibility.
1739+
1740+
```rust
1741+
pub use foo::{x, y};
1742+
use {
1743+
bar::{
1744+
a,
1745+
b::{self, f, g},
1746+
c,
1747+
d::e,
1748+
},
1749+
qux::{h, i},
1750+
};
1751+
```
1752+
17361753
## `merge_imports`
17371754

17381755
This option is deprecated. Use `imports_granularity = "Crate"` instead.

src/config/options.rs

+2
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ pub enum ImportGranularity {
125125
Module,
126126
/// Use one `use` statement per imported item.
127127
Item,
128+
/// Use one `use` statement including all items.
129+
One,
128130
}
129131

130132
#[config_type]

src/imports.rs

+128-18
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,29 @@ impl UseSegment {
138138
}
139139
}
140140

141+
// Check if self == other with their aliases removed.
142+
fn equal_except_alias(&self, other: &Self) -> bool {
143+
match (self, other) {
144+
(UseSegment::Ident(ref s1, _), UseSegment::Ident(ref s2, _)) => s1 == s2,
145+
(UseSegment::Slf(_), UseSegment::Slf(_))
146+
| (UseSegment::Super(_), UseSegment::Super(_))
147+
| (UseSegment::Crate(_), UseSegment::Crate(_))
148+
| (UseSegment::Glob, UseSegment::Glob) => true,
149+
(UseSegment::List(ref list1), UseSegment::List(ref list2)) => list1 == list2,
150+
_ => false,
151+
}
152+
}
153+
154+
fn get_alias(&self) -> Option<&str> {
155+
match self {
156+
UseSegment::Ident(_, a)
157+
| UseSegment::Slf(a)
158+
| UseSegment::Super(a)
159+
| UseSegment::Crate(a) => a.as_deref(),
160+
_ => None,
161+
}
162+
}
163+
141164
fn from_path_segment(
142165
context: &RewriteContext<'_>,
143166
path_seg: &ast::PathSegment,
@@ -558,6 +581,7 @@ impl UseTree {
558581
SharedPrefix::Module => {
559582
self.path[..self.path.len() - 1] == other.path[..other.path.len() - 1]
560583
}
584+
SharedPrefix::One => true,
561585
}
562586
}
563587
}
@@ -598,7 +622,7 @@ impl UseTree {
598622
fn merge(&mut self, other: &UseTree, merge_by: SharedPrefix) {
599623
let mut prefix = 0;
600624
for (a, b) in self.path.iter().zip(other.path.iter()) {
601-
if *a == *b {
625+
if a.equal_except_alias(b) {
602626
prefix += 1;
603627
} else {
604628
break;
@@ -633,14 +657,20 @@ fn merge_rest(
633657
return Some(new_path);
634658
}
635659
} else if len == 1 {
636-
let rest = if a.len() == len { &b[1..] } else { &a[1..] };
637-
return Some(vec![
638-
b[0].clone(),
639-
UseSegment::List(vec![
640-
UseTree::from_path(vec![UseSegment::Slf(None)], DUMMY_SP),
641-
UseTree::from_path(rest.to_vec(), DUMMY_SP),
642-
]),
643-
]);
660+
let (common, rest) = if a.len() == len {
661+
(&a[0], &b[1..])
662+
} else {
663+
(&b[0], &a[1..])
664+
};
665+
let mut list = vec![UseTree::from_path(
666+
vec![UseSegment::Slf(common.get_alias().map(ToString::to_string))],
667+
DUMMY_SP,
668+
)];
669+
match rest {
670+
[UseSegment::List(rest_list)] => list.extend(rest_list.clone()),
671+
_ => list.push(UseTree::from_path(rest.to_vec(), DUMMY_SP)),
672+
}
673+
return Some(vec![b[0].clone(), UseSegment::List(list)]);
644674
} else {
645675
len -= 1;
646676
}
@@ -655,18 +685,54 @@ fn merge_rest(
655685
}
656686

657687
fn merge_use_trees_inner(trees: &mut Vec<UseTree>, use_tree: UseTree, merge_by: SharedPrefix) {
658-
let similar_trees = trees
659-
.iter_mut()
660-
.filter(|tree| tree.share_prefix(&use_tree, merge_by));
688+
struct SimilarTree<'a> {
689+
similarity: usize,
690+
path_len: usize,
691+
tree: &'a mut UseTree,
692+
}
693+
694+
let similar_trees = trees.iter_mut().filter_map(|tree| {
695+
if tree.share_prefix(&use_tree, merge_by) {
696+
// In the case of `SharedPrefix::One`, `similarity` is used for deciding with which
697+
// tree `use_tree` should be merge.
698+
// In other cases `similarity` won't be used, so set it to `0` as a dummy value.
699+
let similarity = if merge_by == SharedPrefix::One {
700+
tree.path
701+
.iter()
702+
.zip(&use_tree.path)
703+
.take_while(|(a, b)| a.equal_except_alias(b))
704+
.count()
705+
} else {
706+
0
707+
};
708+
709+
let path_len = tree.path.len();
710+
Some(SimilarTree {
711+
similarity,
712+
tree,
713+
path_len,
714+
})
715+
} else {
716+
None
717+
}
718+
});
719+
661720
if use_tree.path.len() == 1 && merge_by == SharedPrefix::Crate {
662-
if let Some(tree) = similar_trees.min_by_key(|tree| tree.path.len()) {
663-
if tree.path.len() == 1 {
721+
if let Some(tree) = similar_trees.min_by_key(|tree| tree.path_len) {
722+
if tree.path_len == 1 {
723+
return;
724+
}
725+
}
726+
} else if merge_by == SharedPrefix::One {
727+
if let Some(sim_tree) = similar_trees.max_by_key(|tree| tree.similarity) {
728+
if sim_tree.similarity > 0 {
729+
sim_tree.tree.merge(&use_tree, merge_by);
664730
return;
665731
}
666732
}
667-
} else if let Some(tree) = similar_trees.max_by_key(|tree| tree.path.len()) {
668-
if tree.path.len() > 1 {
669-
tree.merge(&use_tree, merge_by);
733+
} else if let Some(sim_tree) = similar_trees.max_by_key(|tree| tree.path_len) {
734+
if sim_tree.path_len > 1 {
735+
sim_tree.tree.merge(&use_tree, merge_by);
670736
return;
671737
}
672738
}
@@ -880,6 +946,7 @@ impl Rewrite for UseTree {
880946
pub(crate) enum SharedPrefix {
881947
Crate,
882948
Module,
949+
One,
883950
}
884951

885952
#[cfg(test)]
@@ -904,7 +971,7 @@ mod test {
904971
}
905972

906973
fn eat(&mut self, c: char) {
907-
assert!(self.input.next().unwrap() == c);
974+
assert_eq!(self.input.next().unwrap(), c);
908975
}
909976

910977
fn push_segment(
@@ -1094,6 +1161,49 @@ mod test {
10941161
);
10951162
}
10961163

1164+
#[test]
1165+
fn test_use_tree_merge_one() {
1166+
test_merge!(One, ["a", "b"], ["{a, b}"]);
1167+
1168+
test_merge!(One, ["a::{aa, ab}", "b", "a"], ["{a::{self, aa, ab}, b}"]);
1169+
1170+
test_merge!(One, ["a as x", "b as y"], ["{a as x, b as y}"]);
1171+
1172+
test_merge!(
1173+
One,
1174+
["a::{aa as xa, ab}", "b", "a"],
1175+
["{a::{self, aa as xa, ab}, b}"]
1176+
);
1177+
1178+
test_merge!(
1179+
One,
1180+
["a", "a::{aa, ab::{aba, abb}}"],
1181+
["a::{self, aa, ab::{aba, abb}}"]
1182+
);
1183+
1184+
test_merge!(One, ["a", "b::{ba, *}"], ["{a, b::{ba, *}}"]);
1185+
1186+
test_merge!(One, ["a", "b", "a::aa"], ["{a::{self, aa}, b}"]);
1187+
1188+
test_merge!(
1189+
One,
1190+
["a::aa::aaa", "a::ac::aca", "a::aa::*"],
1191+
["a::{aa::{aaa, *}, ac::aca}"]
1192+
);
1193+
1194+
test_merge!(
1195+
One,
1196+
["a", "b::{ba, bb}", "a::{aa::*, ab::aba}"],
1197+
["{a::{self, aa::*, ab::aba}, b::{ba, bb}}"]
1198+
);
1199+
1200+
test_merge!(
1201+
One,
1202+
["b", "a::ac::{aca, acb}", "a::{aa::*, ab}"],
1203+
["{a::{aa::*, ab, ac::{aca, acb}}, b}"]
1204+
);
1205+
}
1206+
10971207
#[test]
10981208
fn test_flatten_use_trees() {
10991209
assert_eq!(

src/reorder.rs

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ fn rewrite_reorderable_or_regroupable_items(
113113
merge_use_trees(normalized_items, SharedPrefix::Module)
114114
}
115115
ImportGranularity::Item => flatten_use_trees(normalized_items),
116+
ImportGranularity::One => merge_use_trees(normalized_items, SharedPrefix::One),
116117
ImportGranularity::Preserve => normalized_items,
117118
};
118119

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// rustfmt-imports_granularity: One
2+
3+
use b;
4+
use a::ac::{aca, acb};
5+
use a::{aa::*, ab};
6+
7+
use a as x;
8+
use b::ba;
9+
use a::{aa, ab};
10+
11+
use a::aa::aaa;
12+
use a::ab::aba as x;
13+
use a::aa::*;
14+
15+
use a::aa;
16+
use a::ad::ada;
17+
#[cfg(test)]
18+
use a::{ab, ac::aca};
19+
use b;
20+
#[cfg(test)]
21+
use b::{
22+
ba, bb,
23+
bc::bca::{bcaa, bcab},
24+
};
25+
26+
pub use a::aa;
27+
pub use a::ae;
28+
use a::{ab, ac, ad};
29+
use b::ba;
30+
pub use b::{bb, bc::bca};
31+
32+
use a::aa::aaa;
33+
use a::ac::{aca, acb};
34+
use a::{aa::*, ab};
35+
use b::{
36+
ba,
37+
bb::{self, bba},
38+
};
39+
40+
use crate::a;
41+
use crate::b::ba;
42+
use c::ca;
43+
44+
use super::a;
45+
use c::ca;
46+
use super::b::ba;
47+
48+
use crate::a;
49+
use super::b;
50+
use c::{self, ca};
51+
52+
use a::{
53+
// some comment
54+
aa::{aaa, aab},
55+
ab,
56+
// another comment
57+
ac::aca,
58+
};
59+
use b as x;
60+
use a::ad::ada;
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// rustfmt-imports_granularity: One
2+
3+
use {
4+
a::{
5+
aa::*,
6+
ab,
7+
ac::{aca, acb},
8+
},
9+
b,
10+
};
11+
12+
use {
13+
a::{self as x, aa, ab},
14+
b::ba,
15+
};
16+
17+
use a::{
18+
aa::{aaa, *},
19+
ab::aba as x,
20+
};
21+
22+
#[cfg(test)]
23+
use a::{ab, ac::aca};
24+
#[cfg(test)]
25+
use b::{
26+
ba, bb,
27+
bc::bca::{bcaa, bcab},
28+
};
29+
use {
30+
a::{aa, ad::ada},
31+
b,
32+
};
33+
34+
pub use {
35+
a::{aa, ae},
36+
b::{bb, bc::bca},
37+
};
38+
use {
39+
a::{ab, ac, ad},
40+
b::ba,
41+
};
42+
43+
use {
44+
a::{
45+
aa::{aaa, *},
46+
ab,
47+
ac::{aca, acb},
48+
},
49+
b::{
50+
ba,
51+
bb::{self, bba},
52+
},
53+
};
54+
55+
use {
56+
crate::{a, b::ba},
57+
c::ca,
58+
};
59+
60+
use {
61+
super::{a, b::ba},
62+
c::ca,
63+
};
64+
65+
use {
66+
super::b,
67+
crate::a,
68+
c::{self, ca},
69+
};
70+
71+
use {
72+
a::{
73+
aa::{aaa, aab},
74+
ab,
75+
ac::aca,
76+
ad::ada,
77+
},
78+
b as x,
79+
};

0 commit comments

Comments
 (0)