a42429012e
- crates/ → engrams/ (Rust engrams live here)
- el-compiler/ added: el self-hosting compiler as an el package
- src/{compiler,lexer,parser,codegen}.el
- bootstrap/el-compiler.elc (114KB, Rust-compiled seed)
- el.toml Cargo.toml workspace paths updated
- neuron-rs cross-repo path deps fixed (were pointing to products/ instead of foundation/)
117 lines
3.8 KiB
Rust
117 lines
3.8 KiB
Rust
//! 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<LintDiagnostic> {
|
|
let mut diags = Vec::new();
|
|
|
|
for stmt in &program.stmts {
|
|
check_stmt(stmt, &mut diags);
|
|
}
|
|
|
|
diags
|
|
}
|
|
|
|
fn check_stmt(stmt: &Stmt, diags: &mut Vec<LintDiagnostic>) {
|
|
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)
|
|
}
|