#![allow(clippy::disallowed_macros)]
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::Span;
use serde::Serialize;
use vize_carton::CompactString;
use vize_carton::String;
use vize_carton::ToCompactString;
use super::formatting::{render_help, HelpRenderTarget};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Error,
Warning,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HelpLevel {
None,
Short,
#[default]
Full,
}
impl HelpLevel {
pub fn process(&self, help: &str) -> Option<String> {
match self {
HelpLevel::None => None,
HelpLevel::Short => Some(super::formatting::strip_markdown_first_line(help)),
HelpLevel::Full => Some(help.to_compact_string()),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct TextEdit {
pub start: u32,
pub end: u32,
pub new_text: String,
}
impl TextEdit {
#[inline]
pub fn new(start: u32, end: u32, new_text: impl Into<String>) -> Self {
Self {
start,
end,
new_text: new_text.into(),
}
}
#[inline]
pub fn insert(offset: u32, text: impl Into<String>) -> Self {
Self::new(offset, offset, text)
}
#[inline]
pub fn delete(start: u32, end: u32) -> Self {
Self::new(start, end, "")
}
#[inline]
pub fn replace(start: u32, end: u32, text: impl Into<String>) -> Self {
Self::new(start, end, text)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Fix {
pub message: String,
pub edits: Vec<TextEdit>,
}
impl Fix {
#[inline]
pub fn new(message: impl Into<String>, edit: TextEdit) -> Self {
Self {
message: message.into(),
edits: vec![edit],
}
}
#[inline]
pub fn with_edits(message: impl Into<String>, edits: Vec<TextEdit>) -> Self {
Self {
message: message.into(),
edits,
}
}
#[inline]
pub fn apply(&self, source: &str) -> String {
let mut result = source.to_compact_string();
let mut edits = self.edits.clone();
edits.sort_by_key(|edit| std::cmp::Reverse(edit.start));
for edit in edits {
let start = edit.start as usize;
let end = edit.end as usize;
if start <= result.len() && end <= result.len() {
result.replace_range(start..end, &edit.new_text);
}
}
result
}
}
#[derive(Debug, Clone)]
pub struct LintDiagnostic {
pub rule_name: &'static str,
pub severity: Severity,
pub message: CompactString,
pub start: u32,
pub end: u32,
pub help: Option<CompactString>,
pub labels: Vec<Label>,
pub fix: Option<Fix>,
}
#[derive(Debug, Clone)]
pub struct Label {
pub message: CompactString,
pub start: u32,
pub end: u32,
}
impl LintDiagnostic {
#[inline]
pub fn error(
rule_name: &'static str,
message: impl Into<CompactString>,
start: u32,
end: u32,
) -> Self {
Self {
rule_name,
severity: Severity::Error,
message: message.into(),
start,
end,
help: None,
labels: Vec::new(),
fix: None,
}
}
#[inline]
pub fn warn(
rule_name: &'static str,
message: impl Into<CompactString>,
start: u32,
end: u32,
) -> Self {
Self {
rule_name,
severity: Severity::Warning,
message: message.into(),
start,
end,
help: None,
labels: Vec::new(),
fix: None,
}
}
#[inline]
pub fn with_help(mut self, help: impl Into<CompactString>) -> Self {
self.help = Some(help.into());
self
}
#[inline]
pub fn with_label(mut self, message: impl Into<CompactString>, start: u32, end: u32) -> Self {
self.labels.push(Label {
message: message.into(),
start,
end,
});
self
}
#[inline]
pub fn with_fix(mut self, fix: Fix) -> Self {
self.fix = Some(fix);
self
}
#[inline]
pub fn has_fix(&self) -> bool {
self.fix.is_some()
}
#[inline]
pub fn formatted_message(&self) -> String {
format!("[vize:{}] {}", self.rule_name, self.message).to_compact_string()
}
#[inline]
pub fn into_oxc_diagnostic(self) -> OxcDiagnostic {
let formatted_msg = format!("[vize:{}] {}", self.rule_name, self.message);
let mut diag = match self.severity {
Severity::Error => OxcDiagnostic::error(formatted_msg),
Severity::Warning => OxcDiagnostic::warn(formatted_msg),
};
diag = diag.with_label(Span::new(self.start, self.end));
if let Some(help) = self.help {
diag = diag.with_help(render_help(&help, HelpRenderTarget::PlainText));
}
for label in self.labels {
diag = diag.and_label(
Span::new(label.start, label.end).label(label.message.to_compact_string()),
);
}
diag
}
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct LintSummary {
pub error_count: usize,
pub warning_count: usize,
pub file_count: usize,
}
impl LintSummary {
#[inline]
pub fn add(&mut self, diagnostic: &LintDiagnostic) {
match diagnostic.severity {
Severity::Error => self.error_count += 1,
Severity::Warning => self.warning_count += 1,
}
}
#[inline]
pub fn has_errors(&self) -> bool {
self.error_count > 0
}
}