use crate::report::{Fix, Severity, Violation};
pub fn lsp_severity(sev: Severity) -> u8 {
match sev {
Severity::Error => 1,
Severity::Warning => 2,
Severity::Info => 3,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Position {
pub line: u32,
pub character: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Range {
pub start: Position,
pub end: Position,
}
#[derive(Debug, Clone)]
pub struct Diagnostic {
pub range: Range,
pub severity: u8,
pub code: String,
pub source: String,
pub message: String,
pub code_description_href: Option<String>,
}
#[derive(Debug, Clone)]
pub struct TextEdit {
pub range: Range,
pub new_text: String,
}
#[derive(Debug, Clone)]
pub struct CodeAction {
pub title: String,
pub kind: String,
pub uri: String,
pub edit: TextEdit,
}
fn token_end(chars: &[char], col: usize) -> usize {
if col >= chars.len() || chars[col].is_whitespace() {
return chars.len();
}
let mut i = col;
while i < chars.len() && !chars[i].is_whitespace() {
i += 1;
}
i
}
pub fn violation_to_diagnostic(
v: &Violation,
guide_url: Option<&str>,
source: Option<&str>,
) -> Diagnostic {
let line = v.line.saturating_sub(1);
let mut col = v.column.unwrap_or(1).saturating_sub(1) as usize;
let mut end_col = col;
if let Some(source) = source {
let lines: Vec<&str> = source.lines().collect();
if (line as usize) < lines.len() {
let text: Vec<char> = lines[line as usize].trim_end().chars().collect();
if v.column.is_none() {
col = 0;
end_col = text.len();
} else {
col = col.min(text.len());
end_col = token_end(&text, col);
}
}
}
Diagnostic {
range: Range {
start: Position {
line,
character: col as u32,
},
end: Position {
line,
character: end_col as u32,
},
},
severity: lsp_severity(v.severity),
code: v.rule_id.clone(),
source: "jss-lint".to_string(),
message: v.message.clone(),
code_description_href: guide_url.map(|s| s.to_string()),
}
}
fn offset_to_lsp_position(text: &str, offset: usize) -> Position {
let chunk: Vec<char> = text.chars().take(offset).collect();
let line = chunk.iter().filter(|&&c| c == '\n').count() as u32;
let character = match chunk.iter().rposition(|&c| c == '\n') {
Some(idx) => (chunk.len() - idx - 1) as u32,
None => chunk.len() as u32,
};
Position { line, character }
}
pub fn fix_to_text_edit(fix: &Fix, source: &str) -> TextEdit {
TextEdit {
range: Range {
start: offset_to_lsp_position(source, fix.start),
end: offset_to_lsp_position(source, fix.end),
},
new_text: fix.replacement.clone(),
}
}
pub fn violation_to_code_action(v: &Violation, source: &str, uri: &str) -> Option<CodeAction> {
let fix = v.fix.as_ref()?;
Some(CodeAction {
title: fix.description.clone(),
kind: "quickfix".to_string(),
uri: uri.to_string(),
edit: fix_to_text_edit(fix, source),
})
}