Skip to main content

specfold_core/plugins/
rules.rs

1//! Plugin rules: run each plugin that provides rules and turn its answer into findings.
2
3use 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}