dictator_rust/
lib.rs

1#![warn(rust_2024_compatibility, clippy::all)]
2
3//! decree.rust - Rust structural rules.
4
5mod cargo_toml;
6mod counting;
7mod structure;
8mod visibility;
9
10use dictator_decree_abi::{BoxDecree, Decree, Diagnostics};
11use dictator_supreme::SupremeConfig;
12
13pub use cargo_toml::lint_cargo_toml;
14
15/// Configuration for rust decree
16#[derive(Debug, Clone)]
17pub struct RustConfig {
18    pub max_lines: usize,
19    /// Minimum required Rust edition (e.g., "2024"). None = disabled.
20    pub min_edition: Option<String>,
21    /// Minimum required rust-version/MSRV (e.g., "1.83"). None = disabled.
22    pub min_rust_version: Option<String>,
23}
24
25impl Default for RustConfig {
26    fn default() -> Self {
27        Self {
28            max_lines: 400,
29            min_edition: None,
30            min_rust_version: None,
31        }
32    }
33}
34
35/// Lint Rust source for structural violations.
36#[must_use]
37pub fn lint_source(source: &str) -> Diagnostics {
38    lint_source_with_config(source, &RustConfig::default())
39}
40
41/// Lint with custom configuration
42#[must_use]
43pub fn lint_source_with_config(source: &str, config: &RustConfig) -> Diagnostics {
44    let mut diags = Diagnostics::new();
45
46    counting::check_file_line_count(source, config.max_lines, &mut diags);
47    visibility::check_visibility_ordering(source, &mut diags);
48
49    diags
50}
51
52#[derive(Default)]
53pub struct RustDecree {
54    config: RustConfig,
55    supreme: SupremeConfig,
56}
57
58impl RustDecree {
59    #[must_use]
60    pub const fn new(config: RustConfig, supreme: SupremeConfig) -> Self {
61        Self { config, supreme }
62    }
63}
64
65impl Decree for RustDecree {
66    fn name(&self) -> &'static str {
67        "rust"
68    }
69
70    fn lint(&self, path: &str, source: &str) -> Diagnostics {
71        let filename = std::path::Path::new(path)
72            .file_name()
73            .and_then(|f| f.to_str())
74            .unwrap_or("");
75
76        // Cargo.toml gets edition check only (no supreme formatting rules)
77        if filename == "Cargo.toml" {
78            return cargo_toml::lint_cargo_toml(source, &self.config);
79        }
80
81        // Regular Rust files get full treatment
82        let mut diags = dictator_supreme::lint_source_with_owner(source, &self.supreme, "rust");
83        diags.extend(lint_source_with_config(source, &self.config));
84
85        // Check mod.rs structure (needs filesystem access)
86        structure::check_mod_rs_structure(path, &mut diags);
87
88        diags
89    }
90
91    fn metadata(&self) -> dictator_decree_abi::DecreeMetadata {
92        dictator_decree_abi::DecreeMetadata {
93            abi_version: dictator_decree_abi::ABI_VERSION.to_string(),
94            decree_version: env!("CARGO_PKG_VERSION").to_string(),
95            description: "Rust structural rules".to_string(),
96            dectauthors: Some(env!("CARGO_PKG_AUTHORS").to_string()),
97            supported_extensions: vec!["rs".to_string()],
98            supported_filenames: vec![
99                "Cargo.toml".to_string(),
100                "build.rs".to_string(),
101                "rust-toolchain".to_string(),
102                "rust-toolchain.toml".to_string(),
103                ".rustfmt.toml".to_string(),
104                "rustfmt.toml".to_string(),
105                "clippy.toml".to_string(),
106                ".clippy.toml".to_string(),
107            ],
108            skip_filenames: vec!["Cargo.lock".to_string()],
109            capabilities: vec![dictator_decree_abi::Capability::Lint],
110        }
111    }
112}
113
114#[must_use]
115pub fn init_decree() -> BoxDecree {
116    Box::new(RustDecree::default())
117}
118
119/// Create decree with custom config
120#[must_use]
121pub fn init_decree_with_config(config: RustConfig) -> BoxDecree {
122    Box::new(RustDecree::new(config, SupremeConfig::default()))
123}
124
125/// Create decree with custom config + supreme config (merged from decree.supreme + decree.rust)
126#[must_use]
127pub fn init_decree_with_configs(config: RustConfig, supreme: SupremeConfig) -> BoxDecree {
128    Box::new(RustDecree::new(config, supreme))
129}
130
131/// Convert `DecreeSettings` to `RustConfig`
132#[must_use]
133pub fn config_from_decree_settings(settings: &dictator_core::DecreeSettings) -> RustConfig {
134    RustConfig {
135        max_lines: settings.max_lines.unwrap_or(400),
136        min_edition: settings.min_edition.clone(),
137        min_rust_version: settings.min_rust_version.clone(),
138    }
139}