Skip to content

Commit 99eadaf

Browse files
committed
feat: support wildcards for group_imports
1 parent 700b375 commit 99eadaf

File tree

10 files changed

+290
-3
lines changed

10 files changed

+290
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
### Added
6969

7070
- Users can now set `skip_macro_invocations` in `rustfmt.toml` [#5816](https://github.com/rust-lang/rustfmt/issues/5816)
71+
- Supported custom wildcarded groups for [group_imports](https://rust-lang.github.io/rustfmt/?version=v1.4.38&search=#group_imports).
7172

7273
### Misc
7374

Configurations.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2229,7 +2229,7 @@ Controls the strategy for how consecutive imports are grouped together.
22292229
Controls the strategy for grouping sets of consecutive imports. Imports may contain newlines between imports and still be grouped together as a single set, but other statements between imports will result in different grouping sets.
22302230

22312231
- **Default value**: `Preserve`
2232-
- **Possible values**: `Preserve`, `StdExternalCrate`, `One`
2232+
- **Possible values**: `Preserve`, `StdExternalCrate`, `One`, custom wildcard groups
22332233
- **Stable**: No (tracking issue: [#5083](https://github.com/rust-lang/rustfmt/issues/5083))
22342234

22352235
Each set of imports (one or more `use` statements, optionally separated by newlines) will be formatted independently. Other statements such as `mod ...` or `extern crate ...` will cause imports to not be grouped together.
@@ -2294,6 +2294,39 @@ use std::sync::Arc;
22942294
use uuid::Uuid;
22952295
```
22962296

2297+
#### Example for custom groups:
2298+
2299+
Discard existing import groups, and create groups as specified by the wildcarded list.
2300+
Handy aliases are supported:
2301+
2302+
- `$std` prefix is an alias for standard library (i.e `std`, `core`, `alloc`);
2303+
- `$crate` prefix is an alias for crate-local modules (i.e `self`, `crate`, `super`).
2304+
2305+
For a given config:
2306+
2307+
```toml
2308+
group_imports = [
2309+
["$std::*", "proc_macro::*"],
2310+
["my_crate::*", "crate::*::xyz"],
2311+
["$crate::*"],
2312+
]
2313+
```
2314+
2315+
The following order would be set:
2316+
2317+
```rust
2318+
use proc_macro::Span;
2319+
use std::rc::Rc;
2320+
2321+
use crate::abc::xyz;
2322+
use my_crate::a::B;
2323+
use my_crate::A;
2324+
2325+
use self::X;
2326+
use super::Y;
2327+
use crate::Z;
2328+
```
2329+
22972330
## `reorder_modules`
22982331

22992332
Reorder `mod` declarations alphabetically in group.

src/config/options.rs

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::collections::{hash_set, HashSet};
2+
use std::convert::TryFrom;
23
use std::fmt;
4+
use std::ops::Deref;
35
use std::path::{Path, PathBuf};
46
use std::str::FromStr;
57

@@ -102,7 +104,7 @@ impl Density {
102104
}
103105
}
104106

105-
#[config_type]
107+
#[config_type(skip_derive(Copy, Serialize, Deserialize))]
106108
/// Configuration for import groups, i.e. sets of imports separated by newlines.
107109
pub enum GroupImportsTactic {
108110
/// Keep groups as they are.
@@ -114,6 +116,167 @@ pub enum GroupImportsTactic {
114116
StdExternalCrate,
115117
/// Discard existing groups, and create a single group for everything
116118
One,
119+
/// Discard existing groups, and create groups as specified by the wildcarded list.
120+
/// Handy aliases are supported:
121+
///
122+
/// - `$std` prefix is an alias for standard library (i.e `std`, `core`, `alloc`);
123+
/// - `$crate` prefix is an alias for crate-local modules (i.e `self`, `crate`, `super`).
124+
Wildcards(super::WildcardGroups),
125+
}
126+
127+
impl Serialize for GroupImportsTactic {
128+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
129+
where
130+
S: Serializer,
131+
{
132+
#[derive(Serialize)]
133+
enum Simple {
134+
Preserve,
135+
StdExternalCrate,
136+
One,
137+
}
138+
139+
#[derive(Serialize)]
140+
struct WildcardHelper(Vec<Vec<String>>);
141+
142+
match self {
143+
GroupImportsTactic::Preserve => Simple::Preserve.serialize(serializer),
144+
GroupImportsTactic::StdExternalCrate => Simple::StdExternalCrate.serialize(serializer),
145+
GroupImportsTactic::One => Simple::One.serialize(serializer),
146+
GroupImportsTactic::Wildcards(w) => WildcardHelper(
147+
w.iter()
148+
.map(|w| w.0.iter().map(|l| l.as_str().to_string()).collect())
149+
.collect(),
150+
)
151+
.serialize(serializer),
152+
}
153+
}
154+
}
155+
156+
impl<'de> Deserialize<'de> for GroupImportsTactic {
157+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
158+
where
159+
D: Deserializer<'de>,
160+
{
161+
struct Vis;
162+
impl<'v> Visitor<'v> for Vis {
163+
type Value = GroupImportsTactic;
164+
165+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
166+
formatter.write_str("String or sequence")
167+
}
168+
169+
fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
170+
static ALLOWED: &[&'static str] = &["Preserve", "StdExternalCrate", "One"];
171+
match s {
172+
"Preserve" => Ok(GroupImportsTactic::Preserve),
173+
"StdExternalCrate" => Ok(GroupImportsTactic::StdExternalCrate),
174+
"One" => Ok(GroupImportsTactic::One),
175+
_ => Err(E::unknown_variant(s, ALLOWED)),
176+
}
177+
}
178+
179+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
180+
where
181+
A: SeqAccess<'v>,
182+
{
183+
let mut lines = Vec::<Vec<String>>::new();
184+
while let Some(elem) = seq.next_element()? {
185+
lines.push(elem);
186+
}
187+
188+
lines
189+
.into_iter()
190+
.map(TryFrom::try_from)
191+
.collect::<Result<Vec<_>, _>>()
192+
.map(WildcardGroups)
193+
.map(GroupImportsTactic::Wildcards)
194+
.map_err(serde::de::Error::custom)
195+
}
196+
}
197+
198+
let imports: Self = deserializer.deserialize_any(Vis)?;
199+
Ok(imports)
200+
}
201+
}
202+
203+
#[derive(Debug, Clone, Eq, PartialEq)]
204+
pub struct WildcardGroups(Vec<WildcardGroup>);
205+
206+
impl Deref for WildcardGroups {
207+
type Target = [WildcardGroup];
208+
209+
fn deref(&self) -> &Self::Target {
210+
self.0.as_slice()
211+
}
212+
}
213+
214+
impl fmt::Display for WildcardGroups {
215+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216+
f.write_str("[")?;
217+
for w in &self.0 {
218+
write!(f, "{:?}", w)?;
219+
}
220+
f.write_str("]")
221+
}
222+
}
223+
224+
#[derive(Debug, Clone)]
225+
pub struct WildcardGroup(Vec<regex::Regex>);
226+
227+
impl WildcardGroup {
228+
pub(crate) fn matches(&self, path: &str) -> bool {
229+
self.0.iter().any(|w| w.is_match(path))
230+
}
231+
}
232+
233+
impl TryFrom<Vec<String>> for WildcardGroup {
234+
type Error = regex::Error;
235+
236+
fn try_from(i: Vec<String>) -> Result<Self, Self::Error> {
237+
enum Kind {
238+
Std,
239+
Crate,
240+
}
241+
242+
i.into_iter()
243+
.map(|s| {
244+
let (s, kind) = if let Some(tail) = s.strip_prefix("$std") {
245+
(tail, Some(Kind::Std))
246+
} else if let Some(tail) = s.strip_prefix("$crate") {
247+
(tail, Some(Kind::Crate))
248+
} else {
249+
(s.as_str(), None)
250+
};
251+
252+
// poor man's wildcard
253+
let mut wildcard = regex::escape(s).replace("\\*", ".*");
254+
match kind {
255+
None => {}
256+
Some(Kind::Std) => wildcard.insert_str(0, "(::)?(std|core|alloc)"),
257+
Some(Kind::Crate) => wildcard.insert_str(0, "(::)?(self|crate|super)"),
258+
}
259+
260+
regex::Regex::new(&format!("^{wildcard}$"))
261+
})
262+
.collect::<Result<_, _>>()
263+
.map(Self)
264+
}
265+
}
266+
267+
impl Eq for WildcardGroup {}
268+
269+
impl std::cmp::PartialEq for WildcardGroup {
270+
fn eq(&self, other: &Self) -> bool {
271+
if self.0.len() != other.0.len() {
272+
return false;
273+
}
274+
275+
self.0
276+
.iter()
277+
.zip(other.0.iter())
278+
.all(|(a, b)| a.as_str() == b.as_str())
279+
}
117280
}
118281

119282
#[config_type]

src/reorder.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::cmp::{Ord, Ordering};
1111
use rustc_ast::{ast, attr};
1212
use rustc_span::{symbol::sym, Span};
1313

14-
use crate::config::{Config, GroupImportsTactic};
14+
use crate::config::{Config, GroupImportsTactic, WildcardGroups};
1515
use crate::imports::{normalize_use_trees_with_granularity, UseSegmentKind, UseTree};
1616
use crate::items::{is_mod_decl, rewrite_extern_crate, rewrite_mod};
1717
use crate::lists::{itemize_list, write_list, ListFormatting, ListItem};
@@ -117,6 +117,7 @@ fn rewrite_reorderable_or_regroupable_items(
117117
vec![normalized_items]
118118
}
119119
GroupImportsTactic::StdExternalCrate => group_imports(normalized_items),
120+
GroupImportsTactic::Wildcards(w) => group_imports_custom(w, normalized_items),
120121
};
121122

122123
if context.config.reorder_imports() {
@@ -170,6 +171,28 @@ fn contains_macro_use_attr(item: &ast::Item) -> bool {
170171
attr::contains_name(&item.attrs, sym::macro_use)
171172
}
172173

174+
fn group_imports_custom(wildcards: WildcardGroups, uts: Vec<UseTree>) -> Vec<Vec<UseTree>> {
175+
let mut groups = vec![vec![]; wildcards.len() + 1];
176+
let fallback_group = groups.len() - 1;
177+
178+
for ut in uts.into_iter() {
179+
let ut_path_str = ut.to_string();
180+
if ut.path.is_empty() {
181+
groups[fallback_group].push(ut);
182+
continue;
183+
}
184+
185+
if let Some(idx) = wildcards.iter().position(|w| w.matches(&ut_path_str)) {
186+
groups[idx].push(ut);
187+
continue;
188+
}
189+
190+
groups[fallback_group].push(ut);
191+
}
192+
193+
groups
194+
}
195+
173196
/// Divides imports into three groups, corresponding to standard, external
174197
/// and local imports. Sorts each subgroup.
175198
fn group_imports(uts: Vec<UseTree>) -> Vec<Vec<UseTree>> {

tests/config/wildcard-base.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
group_imports = [
2+
["a", "b::c"],
3+
["$crate::x::*"],
4+
["$std::sync"],
5+
["*::ipsum::*"],
6+
["xyz::abc::dez"],
7+
]

tests/config/wildcard-example.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
group_imports = [
2+
["$std::*", "proc_macro::*"],
3+
["my_crate::*", "crate::*::xyz"],
4+
["$crate::*"],
5+
]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// rustfmt-unstable: true
2+
// rustfmt-config: wildcard-example.rs
3+
use self::X;
4+
use super::Y;
5+
use crate::abc::xyz;
6+
use crate::Z;
7+
use my_crate::a::B;
8+
use my_crate::A;
9+
use proc_macro::Span;
10+
use std::rc::Rc;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// rustfmt-unstable: true
2+
// rustfmt-config: wildcard-base.toml
3+
use self::x;
4+
use crate::x::y;
5+
use crate::y;
6+
use a;
7+
use a::ipsum::b;
8+
use b::c;
9+
use b::c::d;
10+
use c::d::ipsum::b;
11+
use core::sync;
12+
use core::sync::A;
13+
use std::sync;
14+
use xyz::abc::dez;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// rustfmt-unstable: true
2+
// rustfmt-config: wildcard-example.rs
3+
use proc_macro::Span;
4+
use std::rc::Rc;
5+
6+
use crate::abc::xyz;
7+
use my_crate::a::B;
8+
use my_crate::A;
9+
10+
use self::X;
11+
use super::Y;
12+
use crate::Z;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// rustfmt-unstable: true
2+
// rustfmt-config: wildcard-base.toml
3+
use a;
4+
use b::c;
5+
6+
use crate::x::y;
7+
8+
use core::sync;
9+
use std::sync;
10+
11+
use a::ipsum::b;
12+
use c::d::ipsum::b;
13+
14+
use xyz::abc::dez;
15+
16+
use self::x;
17+
use crate::y;
18+
use b::c::d;
19+
use core::sync::A;

0 commit comments

Comments
 (0)