use foxguard::rules::{Rule, RuleRegistry};
use foxguard::{Language, Severity};
const LANGUAGE_ORDER: &[Language] = &[
Language::JavaScript,
Language::Python,
Language::Go,
Language::Ruby,
Language::Java,
Language::Php,
Language::Rust,
Language::CSharp,
Language::Swift,
Language::Kotlin,
Language::Solidity,
Language::Haskell,
Language::NginxConf,
Language::ApacheConf,
Language::HAProxyConf,
Language::Dockerfile,
Language::Manifest,
];
fn language_slug(language: Language) -> &'static str {
match language {
Language::JavaScript => "js",
Language::Python => "py",
Language::Go => "go",
Language::Ruby => "rb",
Language::Java => "java",
Language::Php => "php",
Language::Rust => "rs",
Language::CSharp => "cs",
Language::Swift => "swift",
Language::Kotlin => "kt",
Language::C => "c",
Language::Hcl => "hcl",
Language::Solidity => "sol",
Language::Yaml => "yaml",
Language::NginxConf => "nginxconf",
Language::ApacheConf => "apacheconf",
Language::HAProxyConf => "haproxyconf",
Language::Dockerfile => "dockerfile",
Language::Manifest => "manifest",
Language::Bash => "bash",
Language::Ocaml => "ocaml",
Language::Scala => "scala",
Language::Elixir => "elixir",
Language::Json => "json",
Language::Apex => "apex",
Language::Clojure => "clojure",
Language::Html => "html",
Language::Xml => "xml",
Language::Dart => "dart",
Language::Haskell => "hs",
Language::Regex => "regex",
}
}
fn language_display_name(language: Language) -> &'static str {
match language {
Language::JavaScript => "JavaScript / TypeScript",
Language::Python => "Python",
Language::Go => "Go",
Language::Ruby => "Ruby",
Language::Java => "Java",
Language::Php => "PHP",
Language::Rust => "Rust",
Language::CSharp => "C#",
Language::Swift => "Swift",
Language::Kotlin => "Kotlin",
Language::C => "C",
Language::Hcl => "HCL / Terraform",
Language::Solidity => "Solidity",
Language::Yaml => "YAML",
Language::NginxConf => "Nginx",
Language::ApacheConf => "Apache",
Language::HAProxyConf => "HAProxy",
Language::Dockerfile => "Dockerfile",
Language::Manifest => "Manifest",
Language::Bash => "Bash",
Language::Ocaml => "OCaml",
Language::Scala => "Scala",
Language::Elixir => "Elixir",
Language::Json => "JSON",
Language::Apex => "Apex",
Language::Clojure => "Clojure",
Language::Html => "HTML",
Language::Xml => "XML",
Language::Dart => "Dart",
Language::Haskell => "Haskell",
Language::Regex => "Regex",
}
}
fn language_array_name(language: Language) -> &'static str {
match language {
Language::JavaScript => "jsRules",
Language::Python => "pyRules",
Language::Go => "goRules",
Language::Ruby => "rubyRules",
Language::Java => "javaRules",
Language::Php => "phpRules",
Language::Rust => "rustRules",
Language::CSharp => "csharpRules",
Language::Swift => "swiftRules",
Language::Kotlin => "kotlinRules",
Language::C => "cRules",
Language::Hcl => "hclRules",
Language::Solidity => "solidityRules",
Language::Yaml => "yamlRules",
Language::NginxConf => "nginxconfRules",
Language::ApacheConf => "apacheconfRules",
Language::HAProxyConf => "haproxyconfRules",
Language::Dockerfile => "dockerfileRules",
Language::Manifest => "manifestRules",
Language::Bash => "bashRules",
Language::Ocaml => "ocamlRules",
Language::Scala => "scalaRules",
Language::Elixir => "elixirRules",
Language::Json => "jsonRules",
Language::Apex => "apexRules",
Language::Clojure => "clojureRules",
Language::Html => "htmlRules",
Language::Xml => "xmlRules",
Language::Dart => "dartRules",
Language::Haskell => "haskellRules",
Language::Regex => "regexRules",
}
}
fn severity_str(severity: Severity) -> &'static str {
match severity {
Severity::Low => "low",
Severity::Medium => "medium",
Severity::High => "high",
Severity::Critical => "critical",
}
}
fn escape_single_quoted(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\'' => out.push_str("\\'"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
_ => out.push(c),
}
}
out
}
fn emit_rule_group(language: Language, rules: &[&dyn Rule]) {
println!("const {}: Rule[] = [", language_array_name(language));
for rule in rules {
let id = escape_single_quoted(rule.id());
let cwe = escape_single_quoted(rule.cwe().unwrap_or(""));
let desc = escape_single_quoted(rule.description());
let severity = severity_str(rule.severity());
println!(
" {{ id: '{}', cwe: '{}', desc: '{}', severity: '{}' }},",
id, cwe, desc, severity
);
}
println!("];");
}
fn main() {
let registry = RuleRegistry::new();
let all = registry.all_rules();
let grouped: Vec<(Language, Vec<&dyn Rule>)> = LANGUAGE_ORDER
.iter()
.copied()
.map(|lang| {
let mut rules: Vec<&dyn Rule> = all
.iter()
.map(|r| r.as_ref())
.filter(|r| r.language() == lang)
.collect();
rules.sort_by(|a, b| a.id().cmp(b.id()));
(lang, rules)
})
.collect();
println!(
"// DO NOT EDIT — generated by `cargo run --bin gen_rules_ts > www/src/data/rules.ts`."
);
println!("// Source of truth: the Rust rule registry in `src/rules/mod.rs`.");
println!("// To add or update rules, edit the Rust source and regenerate this file.");
println!();
println!("export interface Rule {{");
println!(" id: string;");
println!(" cwe: string;");
println!(" desc: string;");
println!(" severity: 'critical' | 'high' | 'medium' | 'low';");
println!("}}");
println!();
println!("export interface RuleGroup {{");
println!(" name: string;");
println!(" slug: string;");
println!(" rules: Rule[];");
println!("}}");
println!();
for (language, rules) in &grouped {
emit_rule_group(*language, rules);
println!();
}
println!("export const ruleGroups: RuleGroup[] = [");
for (language, _) in &grouped {
println!(
" {{ name: '{}', slug: '{}', rules: {} }},",
escape_single_quoted(language_display_name(*language)),
language_slug(*language),
language_array_name(*language),
);
}
println!("];");
println!();
println!("export const totalRules = ruleGroups.reduce((sum, g) => sum + g.rules.length, 0);");
println!(
"export const productLanguageCount = {};",
LANGUAGE_ORDER.len()
);
}