Skip to content

Commit 9f4ba3c

Browse files
committed
Make alphabetic ordering in module item groups configurable (new default: off)
From feedback to this lint after its inclusion in clippy 1.82, this has turned out to be the most requested improvement. With this improvement, it is possible to make the lint check certain top-level structural checks on modules (e.g. use statements and module inclusions at the top), but still leaving everything else up to the developer.
1 parent 8d0c0eb commit 9f4ba3c

20 files changed

+750
-113
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6308,6 +6308,7 @@ Released 2018-09-13
63086308
[`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold
63096309
[`missing-docs-in-crate-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-in-crate-items
63106310
[`module-item-order-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-item-order-groupings
6311+
[`module-items-ordered-within-groups`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-items-ordered-within-groups
63116312
[`msrv`]: https://doc.rust-lang.org/clippy/lint_configuration.html#msrv
63126313
[`pass-by-value-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pass-by-value-size-limit
63136314
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior

book/src/lint_configuration.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,21 @@ The named groupings of different source item kinds within modules.
713713
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
714714

715715

716+
## `module-items-ordered-within-groups`
717+
Whether the items within module groups should be ordered alphabetically or not.
718+
719+
This option can be configured to "all", "none", or a list of specific grouping names that should be checked
720+
(e.g. only "enums").
721+
722+
Unknown grouping names will simply be ignored.
723+
724+
**Default Value:** `"none"`
725+
726+
---
727+
**Affected lints:**
728+
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
729+
730+
716731
## `msrv`
717732
The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
718733

clippy_config/src/conf.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::types::{
33
DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering,
44
SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind,
55
SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
6+
SourceItemOrderingWithinModuleItemGroupings,
67
};
78
use clippy_utils::msrvs::Msrv;
89
use rustc_errors::Applicability;
@@ -586,6 +587,15 @@ define_Conf! {
586587
/// The named groupings of different source item kinds within modules.
587588
#[lints(arbitrary_source_item_ordering)]
588589
module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(),
590+
/// Whether the items within module groups should be ordered alphabetically or not.
591+
///
592+
/// This option can be configured to "all", "none", or a list of specific grouping names that should be checked
593+
/// (e.g. only "enums").
594+
///
595+
/// Unknown grouping names will simply be ignored.
596+
#[lints(arbitrary_source_item_ordering)]
597+
module_items_ordered_within_groups: SourceItemOrderingWithinModuleItemGroupings =
598+
SourceItemOrderingWithinModuleItemGroupings::None,
589599
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
590600
#[default_text = "current version"]
591601
#[lints(

clippy_config/src/types.rs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ impl SourceItemOrderingModuleItemKind {
241241
pub struct SourceItemOrderingModuleItemGroupings {
242242
groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)>,
243243
lut: HashMap<SourceItemOrderingModuleItemKind, usize>,
244+
back_lut: HashMap<SourceItemOrderingModuleItemKind, String>,
244245
}
245246

246247
impl SourceItemOrderingModuleItemGroupings {
@@ -256,6 +257,22 @@ impl SourceItemOrderingModuleItemGroupings {
256257
lut
257258
}
258259

260+
fn build_back_lut(
261+
groups: &[(String, Vec<SourceItemOrderingModuleItemKind>)],
262+
) -> HashMap<SourceItemOrderingModuleItemKind, String> {
263+
let mut lut = HashMap::new();
264+
for (group_name, items) in groups {
265+
for item in items {
266+
lut.insert(item.clone(), group_name.clone());
267+
}
268+
}
269+
lut
270+
}
271+
272+
pub fn grouping_name_of(&self, item: &SourceItemOrderingModuleItemKind) -> Option<String> {
273+
self.back_lut.get(item).cloned()
274+
}
275+
259276
pub fn module_level_order_of(&self, item: &SourceItemOrderingModuleItemKind) -> Option<usize> {
260277
self.lut.get(item).copied()
261278
}
@@ -266,7 +283,8 @@ impl From<&[(&str, &[SourceItemOrderingModuleItemKind])]> for SourceItemOrdering
266283
let groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)> =
267284
value.iter().map(|item| (item.0.to_string(), item.1.to_vec())).collect();
268285
let lut = Self::build_lut(&groups);
269-
Self { groups, lut }
286+
let back_lut = Self::build_back_lut(&groups);
287+
Self { groups, lut, back_lut }
270288
}
271289
}
272290

@@ -284,6 +302,7 @@ impl<'de> Deserialize<'de> for SourceItemOrderingModuleItemGroupings {
284302
let groups = Vec::<(String, Vec<SourceItemOrderingModuleItemKind>)>::deserialize(deserializer)?;
285303
let items_total: usize = groups.iter().map(|(_, v)| v.len()).sum();
286304
let lut = Self::build_lut(&groups);
305+
let back_lut = Self::build_back_lut(&groups);
287306

288307
let mut expected_items = SourceItemOrderingModuleItemKind::all_variants();
289308
for item in lut.keys() {
@@ -306,7 +325,7 @@ impl<'de> Deserialize<'de> for SourceItemOrderingModuleItemGroupings {
306325
));
307326
}
308327

309-
Ok(Self { groups, lut })
328+
Ok(Self { groups, lut, back_lut })
310329
} else if items_total != all_items.len() {
311330
Err(de::Error::custom(format!(
312331
"Some module item kinds were configured more than once, or were missing, in the source ordering configuration. \
@@ -418,6 +437,83 @@ impl Serialize for SourceItemOrderingTraitAssocItemKinds {
418437
}
419438
}
420439

440+
/// Describes which specific groupings should have their items ordered
441+
/// alphabetically.
442+
///
443+
/// This is separate from defining and enforcing groupings. For example,
444+
/// defining enums are grouped before structs still allows for an enum B to be
445+
/// placed before an enum A. Only when enforcing ordering within the grouping,
446+
/// will it be checked if A is placed before B.
447+
#[derive(Clone, Debug)]
448+
pub enum SourceItemOrderingWithinModuleItemGroupings {
449+
/// All groupings should have their items ordered.
450+
All,
451+
452+
/// None of the groupings should have their order checked.
453+
None,
454+
455+
/// Only the specified groupings should have their order checked.
456+
Custom(Vec<String>),
457+
}
458+
459+
impl SourceItemOrderingWithinModuleItemGroupings {
460+
pub fn ordered_within(&self, grouping_name: &String) -> bool {
461+
match self {
462+
SourceItemOrderingWithinModuleItemGroupings::All => true,
463+
SourceItemOrderingWithinModuleItemGroupings::None => false,
464+
SourceItemOrderingWithinModuleItemGroupings::Custom(groups) => groups.contains(grouping_name),
465+
}
466+
}
467+
}
468+
469+
/// Helper struct for deserializing the [`SourceItemOrderingWithinModuleItemGroupings`].
470+
#[derive(Deserialize)]
471+
#[serde(untagged)]
472+
enum StringOrVecOfString {
473+
String(String),
474+
Vec(Vec<String>),
475+
}
476+
477+
impl<'de> Deserialize<'de> for SourceItemOrderingWithinModuleItemGroupings {
478+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
479+
where
480+
D: Deserializer<'de>,
481+
{
482+
let description = "The available options for configuring an ordering within module item groups are: \
483+
\"all\", \"none\", or a list of module item group names \
484+
(as configured with the `module_item_order_groupings` configuration option).";
485+
486+
match StringOrVecOfString::deserialize(deserializer) {
487+
Ok(StringOrVecOfString::String(preset)) if preset == "all" => {
488+
Ok(SourceItemOrderingWithinModuleItemGroupings::All)
489+
},
490+
Ok(StringOrVecOfString::String(preset)) if preset == "none" => {
491+
Ok(SourceItemOrderingWithinModuleItemGroupings::None)
492+
},
493+
Ok(StringOrVecOfString::String(preset)) => Err(de::Error::custom(format!(
494+
"Unknown configuration option: {preset}.\n{description}"
495+
))),
496+
Ok(StringOrVecOfString::Vec(groupings)) => {
497+
Ok(SourceItemOrderingWithinModuleItemGroupings::Custom(groupings))
498+
},
499+
Err(e) => Err(de::Error::custom(format!("{e}\n{description}"))),
500+
}
501+
}
502+
}
503+
504+
impl Serialize for SourceItemOrderingWithinModuleItemGroupings {
505+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
506+
where
507+
S: ser::Serializer,
508+
{
509+
match self {
510+
SourceItemOrderingWithinModuleItemGroupings::All => serializer.serialize_str("all"),
511+
SourceItemOrderingWithinModuleItemGroupings::None => serializer.serialize_str("none"),
512+
SourceItemOrderingWithinModuleItemGroupings::Custom(vec) => vec.serialize(serializer),
513+
}
514+
}
515+
}
516+
421517
// these impls are never actually called but are used by the various config options that default to
422518
// empty lists
423519
macro_rules! unimplemented_serialize {

clippy_lints/src/arbitrary_source_item_ordering.rs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use clippy_config::Conf;
22
use clippy_config::types::{
33
SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind,
44
SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
5+
SourceItemOrderingWithinModuleItemGroupings,
56
};
67
use clippy_utils::diagnostics::span_lint_and_note;
78
use rustc_hir::{
@@ -58,8 +59,21 @@ declare_clippy_lint! {
5859
/// | `PascalCase` | "ty_alias", "opaque_ty", "enum", "struct", "union", "trait", "trait_alias", "impl" |
5960
/// | `lower_snake_case` | "fn" |
6061
///
62+
/// The groups' names are arbitrary and can be changed to suit the
63+
/// conventions that should be enforced for a specific project.
64+
///
6165
/// All item kinds must be accounted for to create an enforceable linting
62-
/// rule set.
66+
/// rule set. Following are some example configurations that may be useful.
67+
///
68+
/// Example: *module inclusions and use statements to be at the top*
69+
///
70+
/// ```toml
71+
/// module-item-order-groupings = [
72+
/// [ "modules", [ "extern_crate", "mod", "foreign_mod" ], ],
73+
/// [ "use", [ "use", ], ],
74+
/// [ "everything_else", [ "macro", "global_asm", "static", "const", "ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl", "fn", ], ],
75+
/// ]
76+
/// ```
6377
///
6478
/// ### Known Problems
6579
///
@@ -144,6 +158,7 @@ pub struct ArbitrarySourceItemOrdering {
144158
enable_ordering_for_struct: bool,
145159
enable_ordering_for_trait: bool,
146160
module_item_order_groupings: SourceItemOrderingModuleItemGroupings,
161+
module_items_ordered_within_groups: SourceItemOrderingWithinModuleItemGroupings,
147162
}
148163

149164
impl ArbitrarySourceItemOrdering {
@@ -158,6 +173,7 @@ impl ArbitrarySourceItemOrdering {
158173
enable_ordering_for_struct: conf.source_item_ordering.contains(&Struct),
159174
enable_ordering_for_trait: conf.source_item_ordering.contains(&Trait),
160175
module_item_order_groupings: conf.module_item_order_groupings.clone(),
176+
module_items_ordered_within_groups: conf.module_items_ordered_within_groups.clone(),
161177
}
162178
}
163179

@@ -192,7 +208,7 @@ impl ArbitrarySourceItemOrdering {
192208
);
193209
}
194210

195-
fn lint_member_item<T: LintContext>(cx: &T, item: &Item<'_>, before_item: &Item<'_>) {
211+
fn lint_member_item<T: LintContext>(cx: &T, item: &Item<'_>, before_item: &Item<'_>, msg: &'static str) {
196212
let span = if item.ident.as_str().is_empty() {
197213
&item.span
198214
} else {
@@ -216,14 +232,7 @@ impl ArbitrarySourceItemOrdering {
216232
return;
217233
}
218234

219-
span_lint_and_note(
220-
cx,
221-
ARBITRARY_SOURCE_ITEM_ORDERING,
222-
*span,
223-
"incorrect ordering of items (must be alphabetically ordered)",
224-
Some(*before_span),
225-
note,
226-
);
235+
span_lint_and_note(cx, ARBITRARY_SOURCE_ITEM_ORDERING, *span, msg, Some(*before_span), note);
227236
}
228237

229238
/// Produces a linting warning for incorrectly ordered trait items.
@@ -403,6 +412,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
403412
}
404413

405414
let item_kind = convert_module_item_kind(&item.kind);
415+
let grouping_name = self.module_item_order_groupings.grouping_name_of(&item_kind);
406416
let module_level_order = self
407417
.module_item_order_groupings
408418
.module_level_order_of(&item_kind)
@@ -412,13 +422,27 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
412422
use std::cmp::Ordering; // Better legibility.
413423
match module_level_order.cmp(&cur_t.order) {
414424
Ordering::Less => {
415-
Self::lint_member_item(cx, item, cur_t.item);
425+
Self::lint_member_item(
426+
cx,
427+
item,
428+
cur_t.item,
429+
"incorrect ordering of items (module item groupings specify another order)",
430+
);
416431
},
417432
Ordering::Equal if item_kind == SourceItemOrderingModuleItemKind::Use => {
418433
// Skip ordering use statements, as these should be ordered by rustfmt.
419434
},
420-
Ordering::Equal if cur_t.name > get_item_name(item) => {
421-
Self::lint_member_item(cx, item, cur_t.item);
435+
Ordering::Equal
436+
if (grouping_name.is_some_and(|grouping_name| {
437+
self.module_items_ordered_within_groups.ordered_within(&grouping_name)
438+
}) && cur_t.name > get_item_name(item)) =>
439+
{
440+
Self::lint_member_item(
441+
cx,
442+
item,
443+
cur_t.item,
444+
"incorrect ordering of items (must be alphabetically ordered)",
445+
);
422446
},
423447
Ordering::Equal | Ordering::Greater => {
424448
// Nothing to do in this case, they're already in the right order.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module-items-ordered-within-groups = true

tests/ui-toml/arbitrary_source_item_ordering/default_exp/clippy.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ module-item-order-groupings = [
99
["PascalCase", ["ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl"]],
1010
["lower_snake_case", ["fn"]]
1111
]
12-
12+
module-items-ordered-within-groups = "none"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module-items-ordered-within-groups = ["PascalCase", "ignored unknown grouping"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module-items-ordered-within-groups = "all"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: error reading Clippy's configuration file: data did not match any variant of untagged enum StringOrVecOfString The available options for configuring an ordering within module item groups are: "all", "none", or a list of module item group names (as configured with the `module_item_order_groupings` configuration option).
2+
--> $DIR/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_4/clippy.toml:1:38
3+
|
4+
LL | module-items-ordered-within-groups = true
5+
| ^^^^
6+
7+
error: aborting due to 1 previous error
8+

tests/ui-toml/arbitrary_source_item_ordering/ordering_good.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//@aux-build:../../ui/auxiliary/proc_macros.rs
2-
//@revisions: default default_exp bad_conf_1 bad_conf_2 bad_conf_3
2+
//@revisions: default default_exp bad_conf_1 bad_conf_2 bad_conf_3 bad_conf_4
33
//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/default
44
//@[default_exp] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/default_exp
55
//@[bad_conf_1] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1
66
//@[bad_conf_2] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2
77
//@[bad_conf_3] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3
8+
//@[bad_conf_4] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/bad_conf_4
89

910
#![allow(dead_code)]
1011
#![warn(clippy::arbitrary_source_item_ordering)]

0 commit comments

Comments
 (0)