Skip to content

Commit 17d90ca

Browse files
authored
Option to create groups for std, external crates, and other imports (#4445)
* Add config option. It might actually be better to have a three-way option. * Add test case for opinionated reordering * Opinionated reordering * Update Documentation.md examples * Rename tests * Removed temp test * Rename reorder_imports_opinionated -> group_imports * Add extra tests Tests for interaction with `no_reorder` and `merge_imports`. * Decouple reordering and grouping * Change None -> Preserve for group_imports config Also reword configuration to be less specific. * Move test files; change description wording. * Handle indented import groups
1 parent 8d68652 commit 17d90ca

12 files changed

+254
-21
lines changed

Configurations.md

+50
Original file line numberDiff line numberDiff line change
@@ -2006,6 +2006,56 @@ use dolor;
20062006
use sit;
20072007
```
20082008

2009+
## `group_imports`
2010+
2011+
Controls the strategy for how imports are grouped together.
2012+
2013+
- **Default value**: `Preserve`
2014+
- **Possible values**: `Preserve`, `StdExternalCrate`
2015+
- **Stable**: No
2016+
2017+
#### `Preserve` (default):
2018+
2019+
Preserve the source file's import groups.
2020+
2021+
```rust
2022+
use super::update::convert_publish_payload;
2023+
use chrono::Utc;
2024+
2025+
use alloc::alloc::Layout;
2026+
use juniper::{FieldError, FieldResult};
2027+
use uuid::Uuid;
2028+
2029+
use std::sync::Arc;
2030+
2031+
use broker::database::PooledConnection;
2032+
2033+
use super::schema::{Context, Payload};
2034+
use crate::models::Event;
2035+
use core::f32;
2036+
```
2037+
2038+
#### `StdExternalCrate`:
2039+
2040+
Discard existing import groups, and create three groups for:
2041+
1. `std`, `core` and `alloc`,
2042+
2. external crates,
2043+
3. `self`, `super` and `crate` imports.
2044+
2045+
```rust
2046+
use alloc::alloc::Layout;
2047+
use core::f32;
2048+
use std::sync::Arc;
2049+
2050+
use broker::database::PooledConnection;
2051+
use chrono::Utc;
2052+
use juniper::{FieldError, FieldResult};
2053+
use uuid::Uuid;
2054+
2055+
use super::schema::{Context, Payload};
2056+
use super::update::convert_publish_payload;
2057+
use crate::models::Event;
2058+
```
20092059

20102060
## `reorder_modules`
20112061

src/config.rs

+3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ create_config! {
7878
imports_indent: IndentStyle, IndentStyle::Block, false, "Indent of imports";
7979
imports_layout: ListTactic, ListTactic::Mixed, false, "Item layout inside a import block";
8080
merge_imports: bool, false, false, "Merge imports";
81+
group_imports: GroupImportsTactic, GroupImportsTactic::Preserve, false,
82+
"Controls the strategy for how imports are grouped together";
8183

8284
// Ordering
8385
reorder_imports: bool, true, true, "Reorder import and extern crate statements alphabetically";
@@ -593,6 +595,7 @@ where_single_line = false
593595
imports_indent = "Block"
594596
imports_layout = "Mixed"
595597
merge_imports = false
598+
group_imports = "Preserve"
596599
reorder_imports = true
597600
reorder_modules = true
598601
reorder_impl_items = false

src/config/options.rs

+12
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ impl Density {
106106
}
107107
}
108108

109+
#[config_type]
110+
/// Configuration for import groups, i.e. sets of imports separated by newlines.
111+
pub enum GroupImportsTactic {
112+
/// Keep groups as they are.
113+
Preserve,
114+
/// Discard existing groups, and create new groups for
115+
/// 1. `std` / `core` / `alloc` imports
116+
/// 2. other imports
117+
/// 3. `self` / `crate` / `super` imports
118+
StdExternalCrate,
119+
}
120+
109121
#[config_type]
110122
pub enum ReportTactic {
111123
Always,

src/formatting/reorder.rs

+83-21
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ use std::cmp::{Ord, Ordering};
1111
use rustc_ast::ast;
1212
use rustc_span::{symbol::sym, Span};
1313

14-
use crate::config::Config;
14+
use crate::config::{Config, GroupImportsTactic};
15+
use crate::formatting::imports::UseSegment;
1516
use crate::formatting::modules::{get_mod_inner_attrs, FileModMap};
1617
use crate::formatting::{
1718
imports::{merge_use_trees, UseTree},
@@ -192,9 +193,10 @@ fn rewrite_reorderable_item(
192193
}
193194
}
194195

195-
/// Rewrite a list of items with reordering. Every item in `items` must have
196-
/// the same `ast::ItemKind`.
197-
fn rewrite_reorderable_items(
196+
/// Rewrite a list of items with reordering and/or regrouping. Every item
197+
/// in `items` must have the same `ast::ItemKind`. Whether reordering, regrouping,
198+
/// or both are done is determined from the `context`.
199+
fn rewrite_reorderable_or_regroupable_items(
198200
context: &RewriteContext<'_>,
199201
reorderable_items: &[&ast::Item],
200202
shape: Shape,
@@ -227,19 +229,35 @@ fn rewrite_reorderable_items(
227229
if context.config.merge_imports() {
228230
normalized_items = merge_use_trees(normalized_items);
229231
}
230-
normalized_items.sort();
232+
233+
let mut regrouped_items = match context.config.group_imports() {
234+
GroupImportsTactic::Preserve => vec![normalized_items],
235+
GroupImportsTactic::StdExternalCrate => group_imports(normalized_items),
236+
};
237+
238+
if context.config.reorder_imports() {
239+
regrouped_items.iter_mut().for_each(|items| items.sort())
240+
}
231241

232242
// 4 = "use ", 1 = ";"
233243
let nested_shape = shape.offset_left(4)?.sub_width(1)?;
234-
let item_vec: Vec<_> = normalized_items
244+
let item_vec: Vec<_> = regrouped_items
235245
.into_iter()
236-
.map(|use_tree| ListItem {
237-
item: use_tree.rewrite_top_level(context, nested_shape),
238-
..use_tree.list_item.unwrap_or_else(ListItem::empty)
246+
.filter(|use_group| !use_group.is_empty())
247+
.map(|use_group| {
248+
let item_vec: Vec<_> = use_group
249+
.into_iter()
250+
.map(|use_tree| ListItem {
251+
item: use_tree.rewrite_top_level(context, nested_shape),
252+
..use_tree.list_item.unwrap_or_else(ListItem::empty)
253+
})
254+
.collect();
255+
wrap_reorderable_items(context, &item_vec, nested_shape)
239256
})
240-
.collect();
257+
.collect::<Option<Vec<_>>>()?;
241258

242-
wrap_reorderable_items(context, &item_vec, nested_shape)
259+
let join_string = format!("\n\n{}", shape.indent.to_string(context.config));
260+
Some(item_vec.join(&join_string))
243261
}
244262
_ => {
245263
let list_items = itemize_list(
@@ -268,6 +286,34 @@ fn contains_macro_use_attr(attrs: &[ast::Attribute]) -> bool {
268286
crate::formatting::attr::contains_name(attrs, sym::macro_use)
269287
}
270288

289+
/// Divides imports into three groups, corresponding to standard, external
290+
/// and local imports. Sorts each subgroup.
291+
fn group_imports(uts: Vec<UseTree>) -> Vec<Vec<UseTree>> {
292+
let mut std_imports = Vec::new();
293+
let mut external_imports = Vec::new();
294+
let mut local_imports = Vec::new();
295+
296+
for ut in uts.into_iter() {
297+
if ut.path.is_empty() {
298+
external_imports.push(ut);
299+
continue;
300+
}
301+
match &ut.path[0] {
302+
UseSegment::Ident(id, _) => match id.as_ref() {
303+
"std" | "alloc" | "core" => std_imports.push(ut),
304+
_ => external_imports.push(ut),
305+
},
306+
UseSegment::Slf(_) | UseSegment::Super(_) | UseSegment::Crate(_) => {
307+
local_imports.push(ut)
308+
}
309+
// These are probably illegal here
310+
UseSegment::Glob | UseSegment::List(_) => external_imports.push(ut),
311+
}
312+
}
313+
314+
vec![std_imports, external_imports, local_imports]
315+
}
316+
271317
/// A simplified version of `ast::ItemKind`.
272318
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
273319
enum ReorderableItemKind {
@@ -311,21 +357,29 @@ impl ReorderableItemKind {
311357
}
312358
}
313359

314-
fn in_group(self) -> bool {
360+
fn is_regroupable(self, config: &Config) -> bool {
315361
match self {
316362
ReorderableItemKind::ExternCrate
317363
| ReorderableItemKind::Mod
318-
| ReorderableItemKind::Use => true,
364+
| ReorderableItemKind::Other => false,
365+
ReorderableItemKind::Use => config.group_imports() != GroupImportsTactic::Preserve,
366+
}
367+
}
368+
369+
fn in_group(self, config: &Config) -> bool {
370+
match self {
371+
ReorderableItemKind::ExternCrate | ReorderableItemKind::Mod => true,
372+
ReorderableItemKind::Use => config.group_imports() == GroupImportsTactic::Preserve,
319373
ReorderableItemKind::Other => false,
320374
}
321375
}
322376
}
323377

324378
impl<'b, 'a: 'b> FmtVisitor<'a> {
325-
/// Format items with the same item kind and reorder them. If `in_group` is
326-
/// `true`, then the items separated by an empty line will not be reordered
327-
/// together.
328-
fn walk_reorderable_items(
379+
/// Format items with the same item kind and reorder them, regroup them, or
380+
/// both. If `in_group` is `true`, then the items separated by an empty line
381+
/// will not be reordered together.
382+
fn walk_reorderable_or_regroupable_items(
329383
&mut self,
330384
items: &[&ast::Item],
331385
item_kind: ReorderableItemKind,
@@ -355,7 +409,12 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
355409
let lo = items.first().unwrap().span().lo();
356410
let hi = items.last().unwrap().span().hi();
357411
let span = mk_sp(lo, hi);
358-
let rw = rewrite_reorderable_items(&self.get_context(), items, self.shape(), span);
412+
let rw = rewrite_reorderable_or_regroupable_items(
413+
&self.get_context(),
414+
items,
415+
self.shape(),
416+
span,
417+
);
359418
self.push_rewrite(span, rw);
360419
} else {
361420
for item in items {
@@ -374,9 +433,12 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
374433
// subsequent items that have the same item kind to be reordered within
375434
// `walk_reorderable_items`. Otherwise, just format the next item for output.
376435
let item_kind = ReorderableItemKind::from(items[0], self.file_mod_map);
377-
if item_kind.is_reorderable(self.config) {
378-
let visited_items_num =
379-
self.walk_reorderable_items(items, item_kind, item_kind.in_group());
436+
if item_kind.is_reorderable(self.config) || item_kind.is_regroupable(self.config) {
437+
let visited_items_num = self.walk_reorderable_or_regroupable_items(
438+
items,
439+
item_kind,
440+
item_kind.in_group(self.config),
441+
);
380442
let (_, rest) = items.split_at(visited_items_num);
381443
items = rest;
382444
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// rustfmt-group_imports: StdExternalCrate
2+
// rustfmt-merge_imports: true
3+
use chrono::Utc;
4+
use super::update::convert_publish_payload;
5+
6+
use juniper::{FieldError, FieldResult};
7+
use uuid::Uuid;
8+
use alloc::alloc::Layout;
9+
10+
use std::sync::Arc;
11+
use alloc::vec::Vec;
12+
13+
use broker::database::PooledConnection;
14+
15+
use super::schema::{Context, Payload};
16+
use core::f32;
17+
use crate::models::Event;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// rustfmt-group_imports: StdExternalCrate
2+
mod test {
3+
use crate::foo::bar;
4+
use std::path;
5+
use crate::foo::bar2;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// rustfmt-group_imports: StdExternalCrate
2+
// rustfmt-reorder_imports: false
3+
4+
use chrono::Utc;
5+
use super::update::convert_publish_payload;
6+
7+
use juniper::{FieldError, FieldResult};
8+
use uuid::Uuid;
9+
use alloc::alloc::Layout;
10+
11+
use std::sync::Arc;
12+
13+
use broker::database::PooledConnection;
14+
15+
use super::schema::{Context, Payload};
16+
use core::f32;
17+
use crate::models::Event;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// rustfmt-group_imports: StdExternalCrate
2+
use chrono::Utc;
3+
use super::update::convert_publish_payload;
4+
5+
use juniper::{FieldError, FieldResult};
6+
use uuid::Uuid;
7+
use alloc::alloc::Layout;
8+
9+
use std::sync::Arc;
10+
11+
use broker::database::PooledConnection;
12+
13+
use super::schema::{Context, Payload};
14+
use core::f32;
15+
use crate::models::Event;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// rustfmt-group_imports: StdExternalCrate
2+
// rustfmt-merge_imports: true
3+
use alloc::{alloc::Layout, vec::Vec};
4+
use core::f32;
5+
use std::sync::Arc;
6+
7+
use broker::database::PooledConnection;
8+
use chrono::Utc;
9+
use juniper::{FieldError, FieldResult};
10+
use uuid::Uuid;
11+
12+
use super::{
13+
schema::{Context, Payload},
14+
update::convert_publish_payload,
15+
};
16+
use crate::models::Event;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// rustfmt-group_imports: StdExternalCrate
2+
mod test {
3+
use std::path;
4+
5+
use crate::foo::bar;
6+
use crate::foo::bar2;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// rustfmt-group_imports: StdExternalCrate
2+
// rustfmt-reorder_imports: false
3+
4+
use alloc::alloc::Layout;
5+
use std::sync::Arc;
6+
use core::f32;
7+
8+
use chrono::Utc;
9+
use juniper::{FieldError, FieldResult};
10+
use uuid::Uuid;
11+
use broker::database::PooledConnection;
12+
13+
use super::update::convert_publish_payload;
14+
use super::schema::{Context, Payload};
15+
use crate::models::Event;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// rustfmt-group_imports: StdExternalCrate
2+
use alloc::alloc::Layout;
3+
use core::f32;
4+
use std::sync::Arc;
5+
6+
use broker::database::PooledConnection;
7+
use chrono::Utc;
8+
use juniper::{FieldError, FieldResult};
9+
use uuid::Uuid;
10+
11+
use super::schema::{Context, Payload};
12+
use super::update::convert_publish_payload;
13+
use crate::models::Event;

0 commit comments

Comments
 (0)