Skip to main content

wowlab_tidy/
registry.rs

1use std::collections::BTreeMap;
2
3use crate::{AstCtx, FileCtx, Fix, TomlCtx, Violation, languages::workspace::WorkspaceCtx};
4
5/// A rule entry enriched with config data, consumed by docgen.
6#[derive(Debug, serde::Deserialize, serde::Serialize)]
7pub struct ConfigRule {
8    pub name: String,
9    pub description: String,
10    pub severity: String,
11    pub category: String,
12    pub fixable: bool,
13    pub enabled: bool,
14    #[serde(default, skip_serializing_if = "Vec::is_empty")]
15    pub ignore: Vec<String>,
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub params: Option<BTreeMap<String, ConfigValue>>,
18}
19
20/// JSON-compatible resolved configuration value.
21#[derive(Debug, serde::Deserialize, serde::Serialize)]
22#[serde(transparent)]
23pub struct ConfigValue(serde_json::Value);
24
25impl From<serde_json::Value> for ConfigValue {
26    fn from(value: serde_json::Value) -> Self {
27        Self(value)
28    }
29}
30
31impl PartialEq<i32> for ConfigValue {
32    fn eq(&self, other: &i32) -> bool {
33        self.0.as_i64() == Some(i64::from(*other))
34    }
35}
36
37/// Whether a rule operates on raw lines or parsed AST.
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39// #t(rust_non_exhaustive_on_public) internal enum, all variants matched within this crate
40pub enum RuleKind {
41    RustLine,
42    RustAst,
43    RustWorkspace,
44    Workspace,
45    Toml,
46}
47
48impl RuleKind {
49    #[must_use]
50    pub fn as_str(&self) -> &'static str {
51        match self {
52            RuleKind::RustLine => "rust-line",
53            RuleKind::RustAst => "rust-ast",
54            RuleKind::RustWorkspace => "rust-workspace",
55            RuleKind::Workspace => "workspace",
56            RuleKind::Toml => "toml",
57        }
58    }
59}
60
61impl std::fmt::Display for RuleKind {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        f.write_str(self.as_str())
64    }
65}
66
67/// Unified metadata for a registered rule (line or AST).
68#[derive(Debug)]
69// #t(rust_similar_structs) public registry metadata is distinct from the contextual LLM report projection
70pub struct RuleMeta {
71    pub name: &'static str,
72    pub description: &'static str,
73    pub justification: &'static str,
74    pub severity: Severity,
75    pub kind: RuleKind,
76    pub examples: &'static [Example],
77    pub params: &'static [RuleParam],
78    pub fixable: bool,
79}
80
81/// Collect all registered rules sorted by name.
82#[must_use]
83#[doc(alias = "lint registry", alias = "tidy rules")]
84pub fn all_rules() -> Vec<RuleMeta> {
85    let mut rules: Vec<RuleMeta> = inventory::iter::<Rule>
86        .into_iter()
87        .map(|rule| rule.info.to_meta(rule.check.kind(), rule.fix.is_some()))
88        .collect();
89
90    rules.sort_by_key(|r| r.name);
91
92    rules
93}
94
95/// A code example that demonstrates a rule — used in `--detail` output and tests.
96#[derive(Debug)]
97pub struct Example {
98    pub label: &'static str,
99    pub code: &'static str,
100    pub pass: bool,
101}
102
103#[derive(Clone, Copy, Debug, Eq, PartialEq)]
104// #t(rust_non_exhaustive_on_public) internal enum, all variants matched within this crate
105pub enum ParamType {
106    Int,
107    StringArray,
108}
109
110#[derive(Clone, Debug)]
111// #t(rust_non_exhaustive_on_public) internal enum, all variants matched within this crate
112pub enum ParamDefault {
113    Int(i64),
114    StringArray(&'static [&'static str]),
115}
116
117#[derive(Debug)]
118pub struct RuleParam {
119    pub name: &'static str,
120    pub param_type: ParamType,
121    pub default: ParamDefault,
122}
123
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125// #t(rust_non_exhaustive_on_public) severity levels are a fixed set
126pub enum Severity {
127    Low,
128    Medium,
129    High,
130}
131
132impl Severity {
133    #[must_use]
134    pub fn as_str(&self) -> &'static str {
135        match self {
136            Severity::Low => "low",
137            Severity::Medium => "medium",
138            Severity::High => "high",
139        }
140    }
141}
142
143impl std::fmt::Display for Severity {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        f.write_str(self.as_str())
146    }
147}
148
149/// Shared metadata fields common to both line and AST rules.
150#[derive(Debug)]
151// #t(rust_similar_structs) static registration fields omit the derived rule kind and fixability metadata
152pub(crate) struct RuleInfo {
153    pub name: &'static str,
154    pub description: &'static str,
155    pub justification: &'static str,
156    pub severity: Severity,
157    pub examples: &'static [Example],
158    pub params: &'static [RuleParam],
159}
160
161impl RuleInfo {
162    fn to_meta(&self, kind: RuleKind, fixable: bool) -> RuleMeta {
163        RuleMeta {
164            name: self.name,
165            description: self.description,
166            justification: self.justification,
167            severity: self.severity,
168            kind,
169            examples: self.examples,
170            params: self.params,
171            fixable,
172        }
173    }
174}
175
176/// Language-specific rule check function stored in the unified registry.
177#[derive(Clone, Copy, Debug)]
178pub(crate) enum RuleCheck {
179    RustLine(fn(&FileCtx<'_>) -> Vec<Violation>),
180    RustAst(fn(&AstCtx<'_>) -> Vec<Violation>),
181    RustWorkspace(fn(&WorkspaceCtx<'_>) -> Vec<Violation>),
182    Workspace(fn(&WorkspaceCtx<'_>) -> Vec<Violation>),
183    Toml(fn(&TomlCtx<'_>) -> Vec<Violation>),
184}
185
186impl RuleCheck {
187    pub(crate) const fn kind(self) -> RuleKind {
188        match self {
189            Self::RustLine(_) => RuleKind::RustLine,
190            Self::RustAst(_) => RuleKind::RustAst,
191            Self::RustWorkspace(_) => RuleKind::RustWorkspace,
192            Self::Workspace(_) => RuleKind::Workspace,
193            Self::Toml(_) => RuleKind::Toml,
194        }
195    }
196
197    pub(crate) const fn extensions(self) -> &'static [&'static str] {
198        match self {
199            Self::RustLine(_) | Self::RustAst(_) | Self::RustWorkspace(_) => &["rs"],
200            Self::Workspace(_) => &["rs", "toml"],
201            Self::Toml(_) => &["toml"],
202        }
203    }
204}
205
206/// Language-specific fix function corresponding to [`RuleCheck`].
207#[derive(Clone, Copy, Debug)]
208pub(crate) enum RuleFix {
209    RustLine(fn(&FileCtx<'_>, &Violation) -> Option<Fix>),
210    RustAst(fn(&AstCtx<'_>, &Violation) -> Option<Fix>),
211    RustAstTree(fn(&AstCtx<'_>, &[Violation]) -> Option<String>),
212    Toml(fn(&TomlCtx<'_>, &Violation) -> Option<Fix>),
213}
214
215/// One lint rule in the language-neutral registry.
216#[derive(Debug)]
217pub(crate) struct Rule {
218    pub info: RuleInfo,
219    pub check: RuleCheck,
220    pub fix: Option<RuleFix>,
221}
222
223inventory::collect!(Rule);