tramli_plugins/lint/
mod.rs1use tramli::{FlowDefinition, FlowState, TransitionType};
2use crate::api::PluginReport;
3
4pub type FlowPolicy<S> = Box<dyn Fn(&FlowDefinition<S>, &mut PluginReport) + Send + Sync>;
6
7pub fn default_policies<S: FlowState>() -> Vec<FlowPolicy<S>> {
9 vec![
10 Box::new(warn_terminal_with_outgoing),
11 Box::new(warn_too_many_externals),
12 Box::new(warn_dead_produced_data),
13 Box::new(warn_overwide_processors),
14 ]
15}
16
17fn warn_terminal_with_outgoing<S: FlowState>(def: &FlowDefinition<S>, report: &mut PluginReport) {
18 for state in def.terminal_states() {
19 if !def.transitions_from(*state).is_empty() {
20 report.warn("policy/terminal-outgoing", &format!("terminal state {:?} has outgoing transitions", state));
21 }
22 }
23}
24
25fn warn_too_many_externals<S: FlowState>(def: &FlowDefinition<S>, report: &mut PluginReport) {
26 for state in S::all_states() {
27 let externals: Vec<_> = def.transitions_from(*state)
28 .into_iter()
29 .filter(|t| t.transition_type == TransitionType::External)
30 .collect();
31 if externals.len() > 3 {
32 report.warn("policy/external-count", &format!("state {:?} has {} external transitions", state, externals.len()));
33 }
34 }
35}
36
37fn warn_dead_produced_data<S: FlowState>(def: &FlowDefinition<S>, report: &mut PluginReport) {
38 let dead = def.data_flow_graph().dead_data();
39 for type_id in dead {
40 let name = def.data_flow_graph().type_name(&type_id);
41 report.warn("policy/dead-data", &format!("produced but never consumed: {}", name));
42 }
43}
44
45fn warn_overwide_processors<S: FlowState>(def: &FlowDefinition<S>, report: &mut PluginReport) {
46 for t in &def.transitions {
47 if let Some(ref p) = t.processor {
48 if p.produces().len() > 3 {
49 report.warn(
50 "policy/overwide-processor",
51 &format!("{} produces {} types; consider splitting it", p.name(), p.produces().len()),
52 );
53 }
54 }
55 }
56}
57
58pub struct PolicyLintPlugin<S: FlowState> {
60 policies: Vec<FlowPolicy<S>>,
61}
62
63impl<S: FlowState> PolicyLintPlugin<S> {
64 pub fn new(policies: Vec<FlowPolicy<S>>) -> Self {
65 Self { policies }
66 }
67
68 pub fn defaults() -> Self {
69 Self::new(default_policies())
70 }
71
72 pub fn analyze(&self, definition: &FlowDefinition<S>, report: &mut PluginReport) {
73 for policy in &self.policies {
74 policy(definition, report);
75 }
76 }
77}