Skip to main content

frs/cmds/rsl/
rules.rs

1//! Built-in rules for `frs rsl`.
2
3use std::sync::OnceLock;
4
5use crate::cmds::rsl::engine::FileContext;
6use crate::cmds::rsl::output::FormattedRuleViolation;
7use crate::cmds::rsl::output::ViolationOutputFormat;
8use crate::cmds::rsl::rules::aliased_import::AliasedImportRule;
9use crate::cmds::rsl::rules::misordered_fn::MisorderedFnRule;
10use crate::cmds::rsl::rules::misordered_item_group::MisorderedItemGroupRule;
11use crate::cmds::rsl::rules::misordered_visibility::MisorderedVisibilityRule;
12use crate::cmds::rsl::rules::nonadjacent_impl::NonadjacentImplRule;
13use crate::cmds::rsl::rules::overqualified_call::OverqualifiedCallRule;
14use crate::cmds::rsl::rules::qualified_item::QualifiedItemRule;
15use crate::cmds::rsl::rules::relative_path::RelativePathRule;
16use crate::cmds::rsl::rules::unqualified_call::UnqualifiedCallRule;
17
18pub(super) mod aliased_import;
19pub(super) mod common;
20pub(super) mod misordered_fn;
21pub(super) mod misordered_item_group;
22pub(super) mod misordered_visibility;
23pub(super) mod nonadjacent_impl;
24pub(super) mod overqualified_call;
25pub(super) mod qualified_item;
26pub(super) mod relative_path;
27pub(super) mod unqualified_call;
28
29static RULES: OnceLock<[Box<dyn Rule>; 9]> = OnceLock::new();
30
31/// Object-safe rule interface used by the dispatcher.
32///
33/// Concrete rules implement [`TypedRule`]. Its blanket implementation erases
34/// the concrete violation type only at this registry boundary.
35trait Rule: Send + Sync {
36    fn check(&self, ctx: &FileContext<'_>) -> Vec<Box<dyn RuleViolation>>;
37}
38
39impl<T> Rule for T
40where
41    T: crate::cmds::rsl::rules::TypedRule,
42{
43    fn check(&self, ctx: &FileContext<'_>) -> Vec<Box<dyn RuleViolation>> {
44        <T as crate::cmds::rsl::rules::TypedRule>::check(self, ctx)
45            .into_iter()
46            .map(|violation| Box::new(violation) as Box<dyn RuleViolation>)
47            .collect()
48    }
49}
50
51/// Object-safe violation interface used after the dispatcher erases types.
52pub(super) trait RuleViolation: Send + Sync {
53    fn render(&self, format: ViolationOutputFormat) -> String;
54}
55
56impl<T> RuleViolation for T
57where
58    T: crate::cmds::rsl::rules::TypedRuleViolation + FormattedRuleViolation,
59{
60    fn render(&self, format: ViolationOutputFormat) -> String {
61        FormattedRuleViolation::format(self, format)
62    }
63}
64
65/// Typed rule contract implemented by each concrete rule.
66///
67/// Keeping the associated violation here prevents a rule from returning the
68/// violation type owned by another rule, while still allowing `dyn Rule`.
69pub(super) trait TypedRule: Send + Sync + 'static {
70    type Violation: crate::cmds::rsl::rules::TypedRuleViolation<Rule = Self> + FormattedRuleViolation + 'static;
71
72    fn code() -> &'static str;
73
74    fn check(&self, ctx: &FileContext<'_>) -> Vec<Self::Violation>;
75}
76
77/// Typed link between a concrete violation and its owning rule.
78pub(super) trait TypedRuleViolation: Send + Sync + 'static {
79    type Rule: crate::cmds::rsl::rules::TypedRule<Violation = Self>;
80}
81
82pub(super) fn check(ctx: &FileContext<'_>) -> Vec<Box<dyn RuleViolation>> {
83    let mut violations = Vec::new();
84    for rule in self::rules() {
85        violations.extend(rule.check(ctx));
86    }
87
88    violations
89}
90
91fn rules() -> &'static [Box<dyn Rule>] {
92    RULES
93        .get_or_init(|| {
94            [
95                Box::new(MisorderedItemGroupRule::new(None)) as Box<dyn Rule>,
96                Box::new(MisorderedVisibilityRule::new(None)),
97                Box::new(NonadjacentImplRule),
98                Box::new(MisorderedFnRule),
99                Box::new(UnqualifiedCallRule),
100                Box::new(OverqualifiedCallRule),
101                Box::new(QualifiedItemRule::new(
102                    crate::cmds::rsl::rules::qualified_item::QUALIFIED_ALLOWED_PATHS,
103                )),
104                Box::new(AliasedImportRule),
105                Box::new(RelativePathRule),
106            ]
107        })
108        .as_slice()
109}
110
111#[cfg(test)]
112pub(super) fn test_ctx(file: &syn::File) -> FileContext<'_> {
113    FileContext {
114        path: std::path::Path::new("test.rs"),
115        file,
116        module_item_lists: crate::cmds::rsl::ast::module_item_lists(file),
117    }
118}