1use crate::analyzer::error::Location;
6use crate::prelude::PathBuf;
7use crate::util::constants::app::{APPLICATION, ORGANIZATION};
8use crate::util::constants::vale::{DISABLED_VALE_RULES, ENABLED_VALE_PACKAGES};
9use crate::util::{Label, SemanticVersion};
10use ariadne::{Color, ReportKind};
11use bon::Builder;
12use color_eyre::owo_colors::OwoColorize;
13use derive_more::Display;
14use serde::{Deserialize, Serialize};
15use tracing::{error, trace};
16
17#[derive(Clone, Debug, Default, Display, Serialize, Deserialize)]
21#[serde(rename_all = "lowercase")]
22pub enum ValeOutputItemSeverity {
23 #[display("warning")]
27 Warning,
28 #[display("error")]
32 Error,
33 #[default]
37 #[display("suggestion")]
38 Suggestion,
39}
40#[derive(Builder, Clone, Debug, Default, Display)]
42#[display("{:?}", version)]
43#[builder(start_fn = init)]
44pub struct Vale {
45 pub version: Option<SemanticVersion>,
47 pub binary: Option<PathBuf>,
49 pub config: Option<ValeConfig>,
51}
52#[derive(Builder, Clone, Debug, Display)]
56#[display("{:?}", path)]
57#[builder(start_fn = init)]
58pub struct ValeConfig {
59 #[builder(default = PathBuf::from("./.vale/.vale.ini"))]
61 pub path: PathBuf,
62 #[builder(default = Vec::<String>::new())]
66 pub packages: Vec<String>,
67 #[builder(default = Vec::<String>::new())]
71 pub vocabularies: Vec<String>,
72 #[builder(default = Vec::<String>::new())]
76 pub disabled: Vec<String>,
77}
78#[derive(Clone, Debug, Serialize, Deserialize)]
80pub struct ValeOutput {
81 pub items: Vec<ValeOutputItem>,
83}
84#[derive(Clone, Debug, Serialize, Deserialize)]
88#[serde(rename_all = "PascalCase")]
89pub struct ValeOutputItem {
90 pub(crate) action: ValeOutputItemAction,
91 pub check: String,
95 pub(crate) description: String,
96 pub line: u32,
98 pub(crate) link: String,
99 pub message: String,
101 pub severity: ValeOutputItemSeverity,
103 pub span: Vec<u32>,
105 #[serde(rename = "Match")]
106 pub(crate) word_match: String,
107}
108#[derive(Clone, Debug, Serialize, Deserialize)]
110#[serde(rename_all = "PascalCase")]
111pub struct ValeOutputItemAction {
112 pub(crate) name: String,
114 pub(crate) params: Option<Vec<String>>,
116}
117impl ValeOutput {
118 pub fn parse(output: &str, path: PathBuf) -> Vec<ValeOutputItem> {
120 let processed = preprocess_vale_output(path, output);
121 if processed != "{}" {
122 let parsed: serde_json::Result<ValeOutput> = serde_json::from_str(&processed);
123 match parsed {
124 | Ok(ValeOutput { items }) => items,
125 | Err(why) => {
126 error!("=> {} Parse Vale output - {why}", Label::fail());
127 vec![]
128 }
129 }
130 } else {
131 vec![]
132 }
133 }
134}
135impl Default for ValeConfig {
136 fn default() -> Self {
137 fn to_string(values: Vec<&str>) -> Vec<String> {
138 values.iter().map(|s| s.to_string()).collect()
139 }
140 let config = ValeConfig::init()
141 .packages(to_string(ENABLED_VALE_PACKAGES.to_vec()))
142 .vocabularies(to_string(vec![&ORGANIZATION.to_uppercase(), APPLICATION]))
143 .disabled(to_string(DISABLED_VALE_RULES.to_vec()))
144 .build();
145 trace!("=> {} Default - {:#?}", Label::using(), config.dimmed().cyan());
146 config
147 }
148}
149impl ValeOutputItemSeverity {
150 pub fn colored(&self) -> String {
152 match self {
153 | ValeOutputItemSeverity::Warning => self.to_string().yellow().to_string(),
154 | ValeOutputItemSeverity::Error => self.to_string().red().to_string(),
155 | ValeOutputItemSeverity::Suggestion => self.to_string().blue().to_string(),
156 }
157 }
158}
159impl From<&ValeOutputItemSeverity> for Color {
160 fn from(value: &ValeOutputItemSeverity) -> Self {
161 match value {
162 | ValeOutputItemSeverity::Warning => Color::Yellow,
163 | ValeOutputItemSeverity::Error => Color::Red,
164 | ValeOutputItemSeverity::Suggestion => Color::Blue,
165 }
166 }
167}
168impl From<&ValeOutputItemSeverity> for ReportKind<'_> {
169 fn from(value: &ValeOutputItemSeverity) -> Self {
170 match value {
171 | ValeOutputItemSeverity::Warning => ReportKind::Warning,
172 | ValeOutputItemSeverity::Error => ReportKind::Error,
173 | ValeOutputItemSeverity::Suggestion => ReportKind::Custom("Suggestion", Color::Blue),
174 }
175 }
176}
177impl Location for ValeOutputItem {
178 fn locator(&self) -> String {
179 let character = self.span.first().copied().unwrap_or_default();
180 format!("Line {}, Character {}", self.line, character)
181 }
182 fn source_text(&self) -> String {
183 match self.word_match.is_empty() {
184 | true => " ".to_string(),
185 | false => self.word_match.clone(),
186 }
187 }
188}
189#[cfg(any(unix, target_os = "wasi", target_os = "redox"))]
191pub(crate) fn preprocess_vale_output(path: PathBuf, output: &str) -> String {
192 let input = path.display().to_string();
193 output.replace(&input, "items")
194}
195#[cfg(windows)]
197pub(crate) fn preprocess_vale_output(path: PathBuf, output: &str) -> String {
198 let input = path.as_path().display().to_string().replace("\\", "/");
199 let normalized = output.replace("\\\\", "/");
201 normalized.replace(&format!("//?/{}", input), "items").replace(&input, "items")
203}