specfold_core/plugins/
rules.rs1use super::protocol::FindingsResponse;
4use super::registry::Loaded;
5use super::{payload, process};
6use crate::check::context::Ctx;
7use crate::check::finding::Finding;
8use crate::check::scope::Range;
9use serde_json::json;
10
11fn rule_id(plugin: &str, rule: &str) -> String {
12 format!("x.{plugin}.{rule}")
13}
14
15pub fn run(ctx: &Ctx, plugins: &[Loaded], out: &mut Vec<Finding>) {
16 let staged = matches!(ctx.scope.range, Range::Staged);
17 for p in plugins {
18 let d = match &p.description {
19 Ok(d) => d,
20 Err(e) => {
21 out.push(Finding::error(rule_id(&p.def.name, "describe"), "spec.toml", 1, e.to_string()));
22 continue;
23 }
24 };
25 let rules: Vec<_> = d.provides.rules.iter().filter(|r| !staged || r.on_commit).collect();
26 if rules.is_empty() {
27 continue;
28 }
29 let scope_docs: Option<Vec<usize>> =
30 (!ctx.scope.is_all()).then(|| ctx.scope.with_neighbours.iter().copied().collect());
31 let params = json!({
32 "scope": match ctx.scope.range { Range::All => "all", Range::Staged => "staged", Range::Changed(_) => "changed" },
33 "rules": rules.iter().map(|r| &r.id).collect::<Vec<_>>(),
34 "docs": payload::docs(ctx.corpus, scope_docs.as_deref()),
35 "edges": payload::edges(ctx.corpus, ctx.graph),
36 "changed_paths": ctx.scope.paths,
37 });
38 match process::call::<FindingsResponse>(&ctx.corpus.cfg, &p.def, "rule.run", params) {
39 Ok(resp) => {
40 for f in resp.findings {
41 out.push(Finding {
42 rule: rule_id(&p.def.name, &f.rule),
43 severity: f.severity,
44 path: f.path,
45 line: f.line,
46 message: f.message,
47 fix: f.fix,
48 candidates: f.candidates,
49 });
50 }
51 }
52 Err(e) => out.push(Finding::error(rule_id(&p.def.name, "run"), "spec.toml", 1, e.to_string())),
53 }
54 }
55}