use ::line_index::LineIndex;
use lsp_types::{CodeDescription, Diagnostic, DiagnosticSeverity, Position, Range, TextEdit, Url};
use salsa::Database as Db;
use squawk_ide::db::{File, line_index as file_line_index, parse};
use squawk_linter::{Edit, Linter};
use crate::{
diagnostic::{AssociatedDiagnosticData, DIAGNOSTIC_NAME},
ignore::{ignore_file_edit, ignore_line_edit},
};
fn to_text_edit(edit: Edit, line_index: &LineIndex) -> Option<TextEdit> {
let start_line = line_index.try_line_col(edit.text_range.start())?;
let end_line = line_index.try_line_col(edit.text_range.end())?;
let range = Range::new(
Position::new(start_line.line, start_line.col),
Position::new(end_line.line, end_line.col),
);
Some(TextEdit::new(range, edit.text.unwrap_or_default()))
}
#[salsa::tracked]
pub(crate) fn lint(db: &dyn Db, file: File) -> Vec<Diagnostic> {
let parse = parse(db, file);
let content = file.content(db);
let parse_errors = parse.errors();
let mut linter = Linter::with_default_rules();
let violations = linter.lint(&parse, content);
let line_index = file_line_index(db, file);
let mut diagnostics = Vec::with_capacity(violations.len() + parse_errors.len());
for error in parse_errors {
let range_start = error.range().start();
let range_end = error.range().end();
let start_line_col = line_index.line_col(range_start);
let mut end_line_col = line_index.line_col(range_end);
if start_line_col.line == end_line_col.line && start_line_col.col == end_line_col.col {
end_line_col.col += 1;
}
let diagnostic = Diagnostic {
range: Range::new(
Position::new(start_line_col.line, start_line_col.col),
Position::new(end_line_col.line, end_line_col.col),
),
severity: Some(DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String(
"syntax-error".to_string(),
)),
code_description: Some(CodeDescription {
href: Url::parse("https://squawkhq.com/docs/syntax-error").unwrap(),
}),
source: Some(DIAGNOSTIC_NAME.to_string()),
message: error.message().to_string(),
..Default::default()
};
diagnostics.push(diagnostic);
}
for violation in violations {
let range_start = violation.text_range.start();
let range_end = violation.text_range.end();
let start_line_col = line_index.line_col(range_start);
let mut end_line_col = line_index.line_col(range_end);
if start_line_col.line == end_line_col.line && start_line_col.col == end_line_col.col {
end_line_col.col += 1;
}
let ignore_line_edit = ignore_line_edit(&violation, &line_index, &parse)
.and_then(|e| to_text_edit(e, &line_index));
let ignore_file_edit = ignore_file_edit(&violation, &line_index, &parse)
.and_then(|e| to_text_edit(e, &line_index));
let (title, fix_edits) = if let Some(fix) = violation.fix {
(fix.title, fix.edits)
} else {
("".to_string(), vec![])
};
let edits = fix_edits
.into_iter()
.filter_map(|x| to_text_edit(x, &line_index))
.collect();
let data = AssociatedDiagnosticData {
title,
edits,
ignore_line_edit,
ignore_file_edit,
};
let diagnostic = Diagnostic {
range: Range::new(
Position::new(start_line_col.line, start_line_col.col),
Position::new(end_line_col.line, end_line_col.col),
),
severity: Some(DiagnosticSeverity::WARNING),
code: Some(lsp_types::NumberOrString::String(
violation.code.to_string(),
)),
code_description: Some(CodeDescription {
href: Url::parse(&format!("https://squawkhq.com/docs/{}", violation.code)).unwrap(),
}),
source: Some(DIAGNOSTIC_NAME.to_string()),
message: violation.message,
data: Some(serde_json::to_value(data).unwrap()),
..Default::default()
};
diagnostics.push(diagnostic);
}
diagnostics
}