//! Style and correctness rules for el-lint (beyond el-arch architectural rules). use el_parser::{Program, Stmt}; use crate::report::{LintDiagnostic, LintSeverity}; /// Run all style rules against the program and return diagnostics. pub fn check_style(program: &Program) -> Vec { let mut diags = Vec::new(); for stmt in &program.stmts { check_stmt(stmt, &mut diags); } diags } fn check_stmt(stmt: &Stmt, diags: &mut Vec) { match stmt { Stmt::FnDef { name, body, .. } => { // S001: Function body too long (>50 statements) if body.len() > 50 { diags.push(LintDiagnostic { severity: LintSeverity::Warning, code: "S001".into(), message: format!( "function `{name}` has {} statements — consider splitting", body.len() ), location: format!("function {name}"), suggestion: Some( "extract sub-functions for each logical concern".into(), ), }); } // S002: Function name not snake_case if name.chars().any(|c| c.is_uppercase()) { diags.push(LintDiagnostic { severity: LintSeverity::Warning, code: "S002".into(), message: format!("function `{name}` should be snake_case"), location: format!("function {name}"), suggestion: Some(format!("rename to `{}`", to_snake_case(name))), }); } // S003: Empty function body if body.is_empty() { diags.push(LintDiagnostic { severity: LintSeverity::Info, code: "S003".into(), message: format!("function `{name}` has an empty body"), location: format!("function {name}"), suggestion: Some("add implementation or remove if unused".into()), }); } // Recurse into nested function defs for s in body { check_stmt(s, diags); } } Stmt::TypeDef { name, .. } => { // S004: Type name not PascalCase if !is_pascal_case(name) { diags.push(LintDiagnostic { severity: LintSeverity::Warning, code: "S004".into(), message: format!("type `{name}` should be PascalCase"), location: format!("type {name}"), suggestion: None, }); } } Stmt::EnumDef { name, .. } => { // S004 also applies to enums if !is_pascal_case(name) { diags.push(LintDiagnostic { severity: LintSeverity::Warning, code: "S004".into(), message: format!("enum `{name}` should be PascalCase"), location: format!("enum {name}"), suggestion: None, }); } } Stmt::ImplDef { methods, .. } => { for m in methods { check_stmt(m, diags); } } _ => {} } } /// Convert CamelCase/mixed to snake_case. fn to_snake_case(s: &str) -> String { let mut result = String::new(); for (i, c) in s.chars().enumerate() { if c.is_uppercase() && i > 0 { result.push('_'); } result.push(c.to_lowercase().next().unwrap()); } result } /// Returns true if the first character is uppercase (PascalCase convention). fn is_pascal_case(s: &str) -> bool { s.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) }