aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/pre
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_cli/pre')
-rw-r--r--mingling_cli/pre/lint_registry.rs208
-rw-r--r--mingling_cli/pre/mod.rs2
2 files changed, 210 insertions, 0 deletions
diff --git a/mingling_cli/pre/lint_registry.rs b/mingling_cli/pre/lint_registry.rs
new file mode 100644
index 0000000..e69592b
--- /dev/null
+++ b/mingling_cli/pre/lint_registry.rs
@@ -0,0 +1,208 @@
+use std::{fs, io::Error};
+
+use just_template::{Template, tmpl};
+
+/// Generate lint module registry file (src/lints/mod.rs)
+///
+/// Read all Rust source files in the src/lints/ directory (excluding mod.rs itself),
+/// automatically generate module declarations and pub use export statements, and write them to mod.rs.
+pub fn gen_mod_file() -> Result<(), Error> {
+ let root = std::env::current_dir()?;
+ let lints_dir = root.join("src").join("lints");
+ let tmpl_file = root.join("tmpls").join("lints.tmpl");
+ let mod_file = root.join("src").join("lints.rs");
+
+ let mut template = Template::from(fs::read_to_string(tmpl_file).unwrap());
+
+ // Collect all .rs file names (without extension), excluding mod
+ let mut entries: Vec<String> = fs::read_dir(&lints_dir)?
+ .filter_map(|e| e.ok())
+ .filter(|e| e.path().is_file())
+ .filter_map(|e| {
+ e.path()
+ .file_stem()
+ .and_then(|s| s.to_str())
+ .map(|s| s.to_string())
+ })
+ .filter(|name| name != "mod")
+ .collect();
+
+ entries.sort();
+
+ // Generate module declarations and re-export statements
+ for name in &entries {
+ tmpl!(template, impls {
+ mod_name = name
+ })
+ }
+
+ // Generate run_all_lints call arms
+ for name in &entries {
+ tmpl!(template, calls {
+ name = name
+ })
+ }
+
+ // Generate file-level lint calls (lints that have `pub fn check_file`)
+ for name in &entries {
+ let file_path = lints_dir.join(format!("{name}.rs"));
+ let has_check_file = fs::read_to_string(&file_path)
+ .map(|c| c.contains("pub fn check_file("))
+ .unwrap_or(false);
+ if has_check_file {
+ tmpl!(template, file_calls {
+ name = name
+ });
+ tmpl!(template, file_lints {
+ name = name
+ });
+ }
+ }
+
+ fs::write(&mod_file, template.to_string())?;
+
+ Ok(())
+}
+
+/// Generate lint metadata registry
+///
+/// Parses each `.rs` file in `src/lints/` (excluding mod.rs and _init.rs),
+/// extracts doc-comment metadata, and writes the result as JSON.
+pub fn gen_lint_registry() -> Result<(), Error> {
+ let root = std::env::current_dir()?;
+ let lints_dir = root.join("src").join("lints");
+
+ let mut lints: Vec<serde_json::Value> = Vec::new();
+
+ for entry in fs::read_dir(&lints_dir)? {
+ let entry = entry?;
+ let path = entry.path();
+ if !path.is_file() || path.extension().and_then(|e| e.to_str()) != Some("rs") {
+ continue;
+ }
+ let stem = match path.file_stem().and_then(|s| s.to_str()) {
+ Some(s) => s.to_string(),
+ None => continue,
+ };
+ if stem == "mod" {
+ continue;
+ }
+
+ let content = fs::read_to_string(&path)?;
+ if let Some(meta) = parse_lint_file(&content, &stem) {
+ lints.push(meta);
+ }
+ }
+
+ lints.sort_by(|a, b| {
+ a["name"]
+ .as_str()
+ .unwrap_or("")
+ .cmp(b["name"].as_str().unwrap_or(""))
+ });
+
+ let json = serde_json::json!({ "lints": lints });
+ let out_path = root.join("registry.json");
+ fs::write(&out_path, serde_json::to_string_pretty(&json).unwrap())?;
+ Ok(())
+}
+
+/// Parse a single lint `.rs` file and return its metadata as JSON.
+fn parse_lint_file(content: &str, name: &str) -> Option<serde_json::Value> {
+ // Collect leading doc comment lines (//!)
+ let mut doc_lines: Vec<String> = Vec::new();
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if let Some(rest) = trimmed.strip_prefix("//!") {
+ doc_lines.push(rest.trim().to_string());
+ } else if !doc_lines.is_empty() {
+ break;
+ }
+ }
+
+ // Title: first non-empty doc line
+ let title = doc_lines
+ .iter()
+ .find(|l| !l.is_empty())
+ .cloned()
+ .unwrap_or_default();
+
+ // Summary: between ## Summary and ## Metadata
+ let mut summary = String::new();
+ let mut in_summary = false;
+ for line in &doc_lines {
+ if line.contains("## Summary") {
+ in_summary = true;
+ continue;
+ }
+ if line.contains("## Metadata") {
+ in_summary = false;
+ }
+ if in_summary && !line.starts_with("## ") {
+ if !summary.is_empty() {
+ summary.push('\n');
+ }
+ summary.push_str(line.trim());
+ }
+ }
+
+ // Metadata section
+ let mut author = String::new();
+ let mut default_level = String::from("warn");
+ let mut active_on = String::from("File");
+ let mut in_metadata = false;
+
+ for line in &doc_lines {
+ if line.contains("## Metadata") {
+ in_metadata = true;
+ continue;
+ }
+ if in_metadata {
+ if line.starts_with("## ") {
+ break;
+ }
+ if let Some(val) = line
+ .strip_prefix("Author:")
+ .or_else(|| line.strip_prefix("Author:"))
+ {
+ author = val.trim().trim_matches('`').to_string();
+ }
+ if let Some(val) = line
+ .strip_prefix("Default:")
+ .or_else(|| line.strip_prefix("Default:"))
+ {
+ let raw = val.trim().trim_matches('`');
+ if raw == "warn" || raw == "allow" || raw == "deny" {
+ default_level = raw.to_string();
+ }
+ }
+ }
+ }
+
+ // Extract active_on from function signature (last occurrence = actual fn)
+ if let Some(pos) = content.rfind("pub fn linter(") {
+ let after = &content[pos..];
+ if let Some(colon) = after.find(':') {
+ let type_part = &after[colon + 1..].trim();
+ let raw = type_part
+ .trim_start_matches("syn::")
+ .split([' ', ')', ',', '\n'])
+ .next()
+ .unwrap_or("File");
+ // Strip "Item" prefix: ItemFn → Fn, ItemStruct → Struct, etc.
+ active_on = raw.strip_prefix("Item").unwrap_or(raw).to_string();
+ }
+ }
+
+ let mut meta = serde_json::Map::new();
+ meta.insert("author".into(), serde_json::Value::String(author));
+ meta.insert("default".into(), serde_json::Value::String(default_level));
+ meta.insert("active_on".into(), serde_json::Value::String(active_on));
+
+ Some(serde_json::json!({
+ "name": name,
+ "title": title,
+ "summary": summary.trim(),
+ "metadata": meta,
+ }))
+}
diff --git a/mingling_cli/pre/mod.rs b/mingling_cli/pre/mod.rs
new file mode 100644
index 0000000..8eacef4
--- /dev/null
+++ b/mingling_cli/pre/mod.rs
@@ -0,0 +1,2 @@
+mod lint_registry;
+pub use lint_registry::*;