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