pub mod gosec;
pub mod osv;
pub mod osv_db;
#[cfg(feature = "oxc")]
pub mod oxc_native;
pub mod oxlint;
pub mod pmd;
pub mod registry;
pub mod rustsec;
pub use gosec::GosecProvider;
pub use osv::OsvProvider;
pub use osv_db::{OsvDatabase, OsvVulnerability, VulnMatch};
#[cfg(feature = "oxc")]
pub use oxc_native::OxcNativeProvider;
pub use oxlint::OxlintProvider;
pub use pmd::PmdProvider;
pub use rustsec::RustSecProvider;
use anyhow::Result;
use rma_common::{Finding, Language};
use std::path::Path;
pub trait AnalysisProvider: Send + Sync {
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn supports_language(&self, lang: Language) -> bool;
fn is_available(&self) -> bool;
fn version(&self) -> Option<String>;
fn analyze_file(&self, path: &Path) -> Result<Vec<Finding>>;
fn analyze_directory(&self, path: &Path) -> Result<Vec<Finding>> {
self.analyze_file(path)
}
fn analyze_files(&self, files: &[&Path]) -> Result<Vec<Finding>> {
let mut all_findings = Vec::new();
for file in files {
let findings = self.analyze_file(file)?;
all_findings.extend(findings);
}
Ok(all_findings)
}
}
pub struct ProviderRegistry {
providers: Vec<Box<dyn AnalysisProvider>>,
}
impl Default for ProviderRegistry {
fn default() -> Self {
Self::new()
}
}
impl ProviderRegistry {
pub fn new() -> Self {
Self {
providers: Vec::new(),
}
}
pub fn register(&mut self, provider: Box<dyn AnalysisProvider>) {
self.providers.push(provider);
}
pub fn providers(&self) -> &[Box<dyn AnalysisProvider>] {
&self.providers
}
pub fn providers_for_language(&self, lang: Language) -> Vec<&dyn AnalysisProvider> {
self.providers
.iter()
.filter(|p| p.is_available() && p.supports_language(lang))
.map(|p| p.as_ref())
.collect()
}
pub fn get(&self, name: &str) -> Option<&dyn AnalysisProvider> {
self.providers
.iter()
.find(|p| p.name() == name)
.map(|p| p.as_ref())
}
pub fn has_provider_for(&self, lang: Language) -> bool {
self.providers
.iter()
.any(|p| p.is_available() && p.supports_language(lang))
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockProvider {
name: &'static str,
available: bool,
languages: Vec<Language>,
}
impl AnalysisProvider for MockProvider {
fn name(&self) -> &'static str {
self.name
}
fn description(&self) -> &'static str {
"Mock provider for testing"
}
fn supports_language(&self, lang: Language) -> bool {
self.languages.contains(&lang)
}
fn is_available(&self) -> bool {
self.available
}
fn version(&self) -> Option<String> {
Some("1.0.0".to_string())
}
fn analyze_file(&self, _path: &Path) -> Result<Vec<Finding>> {
Ok(Vec::new())
}
}
#[test]
fn test_registry_providers_for_language() {
let mut registry = ProviderRegistry::new();
registry.register(Box::new(MockProvider {
name: "java-linter",
available: true,
languages: vec![Language::Java],
}));
registry.register(Box::new(MockProvider {
name: "js-linter",
available: true,
languages: vec![Language::JavaScript, Language::TypeScript],
}));
let java_providers = registry.providers_for_language(Language::Java);
assert_eq!(java_providers.len(), 1);
assert_eq!(java_providers[0].name(), "java-linter");
let js_providers = registry.providers_for_language(Language::JavaScript);
assert_eq!(js_providers.len(), 1);
assert_eq!(js_providers[0].name(), "js-linter");
}
#[test]
fn test_registry_unavailable_provider() {
let mut registry = ProviderRegistry::new();
registry.register(Box::new(MockProvider {
name: "unavailable",
available: false,
languages: vec![Language::Java],
}));
let providers = registry.providers_for_language(Language::Java);
assert!(providers.is_empty());
}
}