use crate::analyzer::error::Location;
use crate::prelude::PathBuf;
use crate::util::constants::app::{APPLICATION, ORGANIZATION};
use crate::util::constants::vale::{DISABLED_VALE_RULES, ENABLED_VALE_PACKAGES};
use crate::util::{Label, SemanticVersion};
use ariadne::{Color, ReportKind};
use bon::Builder;
use color_eyre::owo_colors::OwoColorize;
use derive_more::Display;
use serde::{Deserialize, Serialize};
use tracing::{error, trace};
#[derive(Clone, Debug, Default, Display, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ValeOutputItemSeverity {
#[display("warning")]
Warning,
#[display("error")]
Error,
#[default]
#[display("suggestion")]
Suggestion,
}
#[derive(Builder, Clone, Debug, Default, Display)]
#[display("{:?}", version)]
#[builder(start_fn = init)]
pub struct Vale {
pub version: Option<SemanticVersion>,
pub binary: Option<PathBuf>,
pub config: Option<ValeConfig>,
}
#[derive(Builder, Clone, Debug, Display)]
#[display("{:?}", path)]
#[builder(start_fn = init)]
pub struct ValeConfig {
#[builder(default = PathBuf::from("./.vale/.vale.ini"))]
pub path: PathBuf,
#[builder(default = Vec::<String>::new())]
pub packages: Vec<String>,
#[builder(default = Vec::<String>::new())]
pub vocabularies: Vec<String>,
#[builder(default = Vec::<String>::new())]
pub disabled: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ValeOutput {
pub items: Vec<ValeOutputItem>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ValeOutputItem {
pub(crate) action: ValeOutputItemAction,
pub check: String,
pub(crate) description: String,
pub line: u32,
pub(crate) link: String,
pub message: String,
pub severity: ValeOutputItemSeverity,
pub span: Vec<u32>,
#[serde(rename = "Match")]
pub(crate) word_match: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ValeOutputItemAction {
pub(crate) name: String,
pub(crate) params: Option<Vec<String>>,
}
impl ValeOutput {
pub fn parse(output: &str, path: PathBuf) -> Vec<ValeOutputItem> {
let processed = preprocess_vale_output(path, output);
if processed != "{}" {
let parsed: serde_json::Result<ValeOutput> = serde_json::from_str(&processed);
match parsed {
| Ok(ValeOutput { items }) => items,
| Err(why) => {
error!("=> {} Parse Vale output - {why}", Label::fail());
vec![]
}
}
} else {
vec![]
}
}
}
impl Default for ValeConfig {
fn default() -> Self {
fn to_string(values: Vec<&str>) -> Vec<String> {
values.iter().map(|s| s.to_string()).collect()
}
let config = ValeConfig::init()
.packages(to_string(ENABLED_VALE_PACKAGES.to_vec()))
.vocabularies(to_string(vec![&ORGANIZATION.to_uppercase(), APPLICATION]))
.disabled(to_string(DISABLED_VALE_RULES.to_vec()))
.build();
trace!("=> {} Default - {:#?}", Label::using(), config.dimmed().cyan());
config
}
}
impl ValeOutputItemSeverity {
pub fn colored(&self) -> String {
match self {
| ValeOutputItemSeverity::Warning => self.to_string().yellow().to_string(),
| ValeOutputItemSeverity::Error => self.to_string().red().to_string(),
| ValeOutputItemSeverity::Suggestion => self.to_string().blue().to_string(),
}
}
}
impl From<&ValeOutputItemSeverity> for Color {
fn from(value: &ValeOutputItemSeverity) -> Self {
match value {
| ValeOutputItemSeverity::Warning => Color::Yellow,
| ValeOutputItemSeverity::Error => Color::Red,
| ValeOutputItemSeverity::Suggestion => Color::Blue,
}
}
}
impl From<&ValeOutputItemSeverity> for ReportKind<'_> {
fn from(value: &ValeOutputItemSeverity) -> Self {
match value {
| ValeOutputItemSeverity::Warning => ReportKind::Warning,
| ValeOutputItemSeverity::Error => ReportKind::Error,
| ValeOutputItemSeverity::Suggestion => ReportKind::Custom("Suggestion", Color::Blue),
}
}
}
impl Location for ValeOutputItem {
fn locator(&self) -> String {
let character = self.span.first().copied().unwrap_or_default();
format!("Line {}, Character {}", self.line, character)
}
fn source_text(&self) -> String {
match self.word_match.is_empty() {
| true => " ".to_string(),
| false => self.word_match.clone(),
}
}
}
#[cfg(any(unix, target_os = "wasi", target_os = "redox"))]
pub(crate) fn preprocess_vale_output(path: PathBuf, output: &str) -> String {
let input = path.display().to_string();
output.replace(&input, "items")
}
#[cfg(windows)]
pub(crate) fn preprocess_vale_output(path: PathBuf, output: &str) -> String {
let input = path.as_path().display().to_string().replace("\\", "/");
let normalized = output.replace("\\\\", "/");
normalized.replace(&format!("//?/{}", input), "items").replace(&input, "items")
}