wowlab_tidy/languages/rust/rules/style/
mod_order.rs1use ra_ap_syntax::{
2 AstNode,
3 ast::{self, HasName},
4 syntax_editor::SyntaxEditor,
5};
6
7use super::support::item_lists;
8use crate::{AstCtx, Example, Violation};
9
10#[rustfmt::skip]
11const EXAMPLES: &[Example] = &[
12 Example {
13 label: "sorted declarations",
14 code: "mod alpha;\npub mod beta;\nmod gamma;",
15 pass: true,
16 },
17 Example {
18 label: "visibility does not affect ordering",
19 code: "pub mod zebra;\nmod alpha;",
20 pass: false,
21 },
22 Example {
23 label: "separate declaration blocks",
24 code: "mod zebra;\nfn boundary() {}\nmod alpha;",
25 pass: true,
26 },
27 Example {
28 label: "inline modules are boundaries",
29 code: "mod zebra {}\nmod alpha;",
30 pass: true,
31 },
32 Example {
33 label: "raw identifiers sort by their semantic name",
34 code: "mod spell;\nmod r#trait;\nmod weapon;",
35 pass: true,
36 },
37];
38
39crate::ast_tree_rule!(
40 mod_order,
41 "Require contiguous module-declaration blocks to be alphabetically sorted.",
42 "Stable module ordering makes module inventories predictable without grouping by visibility.",
43 Low,
44 fix_mod_order,
45);
46
47fn check_mod_order(ctx: &AstCtx<'_>) -> Vec<Violation> {
48 item_lists(ctx.root)
49 .flat_map(module_blocks)
50 .filter_map(|block| {
51 let names: Vec<String> = block.iter().filter_map(module_sort_key).collect();
52
53 (!names.is_sorted()).then(|| {
54 ctx.violation(
55 &block[0],
56 "module declarations are not alphabetically sorted",
57 )
58 })
59 })
60 .collect()
61}
62
63fn module_blocks(items: Vec<ast::Item>) -> impl Iterator<Item = Vec<ast::Module>> {
64 let mut blocks = Vec::new();
65 let mut current = Vec::new();
66
67 for item in items {
68 if let ast::Item::Module(module) = item
69 && module.item_list().is_none()
70 {
71 current.push(module);
72 } else if !current.is_empty() {
73 blocks.push(std::mem::take(&mut current));
74 }
75 }
76
77 if !current.is_empty() {
78 blocks.push(current);
79 }
80
81 blocks.into_iter().filter(|block| block.len() > 1)
82}
83
84fn fix_mod_order(ctx: &AstCtx<'_>, _violations: &[Violation]) -> Option<String> {
86 let (editor, root) = SyntaxEditor::with_ast_node(ctx.root);
87 let mut changed = false;
88
89 for block in item_lists(&root).flat_map(module_blocks) {
90 let mut target = block.clone();
91
92 target.sort_by_key(module_sort_key);
93
94 if block == target {
95 continue;
96 }
97
98 for (old, new) in block.iter().zip(&target) {
99 editor.replace(old.syntax().clone(), new.syntax().clone_subtree());
100 }
101
102 changed = true;
103 }
104
105 changed.then(|| editor.finish().new_root().to_string())
106}
107
108fn module_sort_key(module: &ast::Module) -> Option<String> {
109 module
110 .name()
111 .map(|name| name.text().trim_start_matches("r#").to_owned())
112}
113
114crate::tidy_ast_test!(check_mod_order, {
115 crate::example_tests!(EXAMPLES, check_mod_order);
116 crate::fix_tests!(ast_tree, check_mod_order, fix_mod_order);
117});