use super::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspConfig {
pub server_binary: String,
pub args: Vec<String>,
pub language_id: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum VerifierStage {
SyntaxCheck,
Build,
Test,
Lint,
Format,
}
impl VerifierStage {
pub fn policy_name(&self) -> &'static str {
match self {
VerifierStage::SyntaxCheck => "syntax",
VerifierStage::Build => "build",
VerifierStage::Test => "test",
VerifierStage::Lint => "lint",
VerifierStage::Format => "format",
}
}
}
impl std::fmt::Display for VerifierStage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
VerifierStage::SyntaxCheck => write!(f, "syntax_check"),
VerifierStage::Build => write!(f, "build"),
VerifierStage::Test => write!(f, "test"),
VerifierStage::Lint => write!(f, "lint"),
VerifierStage::Format => write!(f, "format"),
}
}
}
#[derive(Debug, Clone)]
pub struct VerifierCapability {
pub stage: VerifierStage,
pub command: Option<String>,
pub available: bool,
pub fallback_command: Option<String>,
pub fallback_available: bool,
}
impl VerifierCapability {
pub fn any_available(&self) -> bool {
self.available || self.fallback_available
}
pub fn effective_command(&self) -> Option<&str> {
if self.available {
self.command.as_deref()
} else if self.fallback_available {
self.fallback_command.as_deref()
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct LspCapability {
pub primary: LspConfig,
pub primary_available: bool,
pub fallback: Option<LspConfig>,
pub fallback_available: bool,
}
impl LspCapability {
pub fn effective_config(&self) -> Option<&LspConfig> {
if self.primary_available {
Some(&self.primary)
} else if self.fallback_available {
self.fallback.as_ref()
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct VerifierProfile {
pub plugin_name: String,
pub capabilities: Vec<VerifierCapability>,
pub lsp: LspCapability,
}
impl VerifierProfile {
pub fn get(&self, stage: VerifierStage) -> Option<&VerifierCapability> {
self.capabilities.iter().find(|c| c.stage == stage)
}
pub fn available_stages(&self) -> Vec<VerifierStage> {
self.capabilities
.iter()
.filter(|c| c.any_available())
.map(|c| c.stage)
.collect()
}
pub fn fully_degraded(&self) -> bool {
self.capabilities.iter().all(|c| !c.any_available())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DependencyAction {
Add,
Remove,
Update,
}
impl DependencyAction {
pub fn parse(value: &str) -> Option<Self> {
match value {
"add" => Some(Self::Add),
"remove" => Some(Self::Remove),
"update" => Some(Self::Update),
_ => None,
}
}
}
pub fn host_binary_available(binary: &str) -> bool {
std::process::Command::new(binary)
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
#[derive(Debug, Clone, Default)]
pub struct InitOptions {
pub name: String,
pub package_manager: Option<String>,
pub flags: Vec<String>,
pub is_empty_dir: bool,
}
#[derive(Debug, Clone)]
pub enum ProjectAction {
ExecCommand {
command: String,
description: String,
},
NoAction,
}
pub trait LanguagePlugin: Send + Sync {
fn name(&self) -> &str;
fn extensions(&self) -> &[&str];
fn key_files(&self) -> &[&str];
fn detect(&self, path: &Path) -> bool {
for key_file in self.key_files() {
if path.join(key_file).exists() {
return true;
}
}
if let Ok(entries) = std::fs::read_dir(path) {
for entry in entries.flatten() {
if let Some(ext) = entry.path().extension() {
let ext_str = ext.to_string_lossy();
if self.extensions().iter().any(|e| *e == ext_str) {
return true;
}
}
}
}
false
}
fn get_lsp_config(&self) -> LspConfig;
fn get_init_action(&self, opts: &InitOptions) -> ProjectAction;
fn check_tooling_action(&self, path: &Path) -> ProjectAction;
fn init_command(&self, opts: &InitOptions) -> String;
fn test_command(&self) -> String;
fn test_command_with_filter(&self, _filter: Option<&str>) -> String {
self.test_command()
}
fn run_command(&self) -> String;
fn run_command_for_dir(&self, _path: &Path) -> String {
self.run_command()
}
fn syntax_check_command(&self) -> Option<String> {
None
}
fn build_command(&self) -> Option<String> {
None
}
fn lint_command(&self) -> Option<String> {
None
}
fn format_command(&self) -> Option<String> {
None
}
fn format_check_command(&self) -> Option<String> {
None
}
fn file_ownership_patterns(&self) -> &[&str] {
self.extensions()
}
fn owns_file(&self, path: &str) -> bool {
let path_lower = path.to_lowercase();
self.file_ownership_patterns().iter().any(|pattern| {
let pattern = pattern.trim_start_matches('*');
path_lower.ends_with(pattern)
})
}
fn is_test_file(&self, _path: &str) -> bool {
false
}
fn host_tool_available(&self) -> bool {
true
}
fn required_binaries(&self) -> Vec<(&str, &str, &str)> {
Vec::new()
}
fn lsp_fallback(&self) -> Option<LspConfig> {
None
}
fn verifier_profile(&self) -> VerifierProfile {
let tool_available = self.host_tool_available();
let mut capabilities = Vec::new();
if let Some(cmd) = self.syntax_check_command() {
capabilities.push(VerifierCapability {
stage: VerifierStage::SyntaxCheck,
command: Some(cmd),
available: tool_available,
fallback_command: None,
fallback_available: false,
});
}
if let Some(cmd) = self.build_command() {
capabilities.push(VerifierCapability {
stage: VerifierStage::Build,
command: Some(cmd),
available: tool_available,
fallback_command: None,
fallback_available: false,
});
}
capabilities.push(VerifierCapability {
stage: VerifierStage::Test,
command: Some(self.test_command()),
available: tool_available,
fallback_command: None,
fallback_available: false,
});
if let Some(cmd) = self.lint_command() {
capabilities.push(VerifierCapability {
stage: VerifierStage::Lint,
command: Some(cmd),
available: tool_available,
fallback_command: None,
fallback_available: false,
});
}
let primary_config = self.get_lsp_config();
let primary_available = host_binary_available(&primary_config.server_binary);
let fallback = self.lsp_fallback();
let fallback_available = fallback
.as_ref()
.map(|f| host_binary_available(&f.server_binary))
.unwrap_or(false);
VerifierProfile {
plugin_name: self.name().to_string(),
capabilities,
lsp: LspCapability {
primary: primary_config,
primary_available,
fallback,
fallback_available,
},
}
}
fn legal_support_files(&self) -> &[&str] {
&[]
}
fn manifest_mutation_policy(
&self,
_manifest_path: &str,
) -> crate::types::ManifestMutationPolicy {
crate::types::ManifestMutationPolicy::Allow
}
fn dependency_commands(
&self,
_action: DependencyAction,
_packages: &[String],
_dev: bool,
) -> Vec<String> {
Vec::new()
}
fn dependency_files(&self) -> Vec<String> {
Vec::new()
}
fn dependency_command_policy(&self, _command: &str) -> crate::types::CommandPolicyDecision {
crate::types::CommandPolicyDecision::Allow
}
fn test_file_patterns(&self) -> &[&str] {
&[]
}
}