use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ToolCapability {
Format,
Lint,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum OverlapWarningCapability {
Format,
Lint,
}
impl OverlapWarningCapability {
#[must_use]
pub const fn matches(self, capability: ToolCapability) -> bool {
matches!(
(self, capability),
(Self::Format, ToolCapability::Format) | (Self::Lint, ToolCapability::Lint)
)
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct OverlapWarningSuppressRule {
pub capability: OverlapWarningCapability,
pub tools: Vec<String>,
#[serde(default)]
pub extensions: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ToolKind {
Cargo,
Binary,
Runner {
runner: String,
runner_args: Vec<String>,
},
}
#[derive(Debug, Clone)]
pub struct Tool {
pub name: String,
pub display_name: String,
pub binary: String,
pub kind: ToolKind,
pub capabilities: Vec<ToolCapability>,
pub check_args: Vec<String>,
pub format_args: Vec<String>,
pub detected_path: Option<PathBuf>,
}
impl Tool {
#[must_use]
pub fn new(
name: impl Into<String>,
display_name: impl Into<String>,
binary: impl Into<String>,
kind: ToolKind,
capabilities: Vec<ToolCapability>,
check_args: Vec<String>,
format_args: Vec<String>,
) -> Self {
Self {
name: name.into(),
display_name: display_name.into(),
binary: binary.into(),
kind,
capabilities,
check_args,
format_args,
detected_path: None,
}
}
#[must_use]
pub fn can_format(&self) -> bool {
self.capabilities.contains(&ToolCapability::Format)
}
#[must_use]
pub fn can_lint(&self) -> bool {
self.capabilities.contains(&ToolCapability::Lint)
}
#[must_use]
pub fn with_detected_path(mut self, path: PathBuf) -> Self {
self.detected_path = Some(path);
self
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[allow(clippy::struct_excessive_bools)]
pub struct ToolsConfig {
#[serde(default)]
pub required: Vec<String>,
#[serde(default)]
pub skip: Vec<String>,
#[serde(default)]
pub paths: std::collections::BTreeMap<String, String>,
#[serde(default = "default_true")]
pub runner_fallback: bool,
#[serde(default = "default_true")]
pub biome_use_editorconfig: bool,
#[serde(default = "default_true")]
pub biome_use_vcs_ignore: bool,
#[serde(default)]
pub overlap_warning_suppress: Vec<OverlapWarningSuppressRule>,
#[serde(default = "default_true")]
pub nix_fallback: bool,
#[serde(default)]
pub nix_packages: std::collections::BTreeMap<String, String>,
}
const fn default_true() -> bool {
true
}
impl Default for ToolsConfig {
fn default() -> Self {
Self {
required: Vec::new(),
skip: Vec::new(),
paths: std::collections::BTreeMap::new(),
runner_fallback: true,
biome_use_editorconfig: true,
biome_use_vcs_ignore: true,
overlap_warning_suppress: Vec::new(),
nix_fallback: true,
nix_packages: std::collections::BTreeMap::new(),
}
}
}
impl ToolsConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_required(mut self, tool: impl Into<String>) -> Self {
self.required.push(tool.into());
self
}
#[must_use]
pub fn with_skip(mut self, tool: impl Into<String>) -> Self {
self.skip.push(tool.into());
self
}
#[must_use]
pub fn with_path(mut self, tool: impl Into<String>, path: impl Into<String>) -> Self {
self.paths.insert(tool.into(), path.into());
self
}
#[must_use]
pub fn should_skip(&self, tool_name: &str) -> bool {
self.skip.iter().any(|s| s == tool_name)
}
#[must_use]
pub fn is_required(&self, tool_name: &str) -> bool {
self.required.iter().any(|s| s == tool_name)
}
#[must_use]
pub fn get_path(&self, tool_name: &str) -> Option<&str> {
self.paths.get(tool_name).map(String::as_str)
}
}