use crate::graph::Graph;
use crate::level::{AttributeSpec, Level};
use crate::metrics::MetricInputs;
use crate::node::Node;
use crate::preset::Preset;
use crate::report::ReportOverride;
use anyhow::Result;
use std::collections::BTreeMap;
use std::path::Path;
#[derive(Debug, Clone, Default)]
pub struct PluginInput {
pub ignore: Vec<String>,
pub ignore_tests: bool,
pub gitignore: bool,
pub ignore_files: bool,
pub hidden: bool,
}
pub trait LanguagePlugin: Sync {
fn name(&self) -> &str;
fn config(&self) -> toml::Table {
toml::Table::new()
}
fn detect(&self, workspace: &Path, input: &PluginInput) -> bool;
fn levels(&self) -> Vec<Level>;
fn analyze(&self, workspace: &Path, input: &PluginInput) -> Result<Graph>;
fn metrics(&self, _graph: &Graph) -> Vec<(String, MetricInputs)> {
Vec::new()
}
fn function_units(&self, _graph: &Graph) -> Vec<(Node, MetricInputs)> {
Vec::new()
}
fn versions(&self, _workspace: &Path, _input: &PluginInput) -> Vec<(String, String)> {
Vec::new()
}
fn roots(&self, _workspace: &Path) -> Vec<(String, String)> {
Vec::new()
}
fn presets(&self, _input: &PluginInput) -> Vec<Preset> {
Vec::new()
}
fn metric_specs(
&self,
defaults: BTreeMap<String, AttributeSpec>,
) -> BTreeMap<String, AttributeSpec> {
defaults
}
fn report_overrides(&self) -> ReportOverride {
ReportOverride::default()
}
}
pub struct PluginRegistration(pub &'static dyn LanguagePlugin);
inventory::collect!(PluginRegistration);
pub fn registry() -> Vec<&'static dyn LanguagePlugin> {
inventory::iter::<PluginRegistration>()
.map(|entry| entry.0)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::Graph;
struct Dummy;
impl LanguagePlugin for Dummy {
fn name(&self) -> &str {
"dummy"
}
fn detect(&self, _w: &Path, _i: &PluginInput) -> bool {
false
}
fn levels(&self) -> Vec<crate::level::Level> {
Vec::new()
}
fn analyze(&self, _w: &Path, _i: &PluginInput) -> Result<Graph> {
Ok(Graph {
nodes: Vec::new(),
edges: Vec::new(),
})
}
}
#[test]
fn trait_default_hooks_are_noops() {
let p = Dummy;
let ws = Path::new("/tmp");
let input = PluginInput::default();
assert_eq!(p.name(), "dummy");
assert!(!p.detect(ws, &input));
assert!(p.levels().is_empty());
let g = p.analyze(ws, &input).expect("dummy analyze ok");
assert!(g.nodes.is_empty() && g.edges.is_empty());
let empty_graph = Graph {
nodes: Vec::new(),
edges: Vec::new(),
};
assert!(
p.function_units(&empty_graph).is_empty(),
"default: no function units"
);
assert!(p.metrics(&empty_graph).is_empty(), "default: no metrics");
assert!(p.versions(ws, &input).is_empty(), "default: no versions");
assert!(p.roots(ws).is_empty(), "default: no roots");
assert!(p.config().is_empty(), "default: empty config table");
assert!(p.presets(&input).is_empty());
let specs: BTreeMap<String, AttributeSpec> = BTreeMap::new();
assert!(p.metric_specs(specs).is_empty());
let ro = p.report_overrides();
assert!(ro.columns.is_noop() && ro.card.is_noop() && ro.stats.is_noop());
}
}