use anyhow::Error;
use lsp_types::*;
pub use lsp_types::{Position, Range};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum DiagnosticMessageSet {
Lexer,
Syntax,
Semantic,
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct DiagnosticsInfo {
diagnostics: Vec<Diagnostic>,
diagnostics_messages: HashMap<String, DiagnosticsMessage>,
source: Option<String>,
diagnostics_message_limit: Option<usize>,
lock_message_list: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct DiagnosticsMessage {
pub severity: Option<DiagnosticSeverity>,
pub message: String,
pub tags: Option<Vec<DiagnosticTag>>,
pub docs: Option<String>,
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug)]
pub struct DMExtraInfo {
pub range: Range,
pub message_template_data: HashMap<String, String>,
}
impl DMExtraInfo {
pub fn new(range: Range) -> DMExtraInfo {
DMExtraInfo {
range,
message_template_data: HashMap::new(),
}
}
pub fn range_as_string(&self) -> String {
format!(
"start: (line {}, char {}), end: (line {}, char {})",
self.range.start.line + 1, self.range.start.character,
self.range.end.line + 1, self.range.end.character
)
}
}
impl DiagnosticsInfo {
pub fn new(
diagnostics_messages: HashMap<String, DiagnosticsMessage>,
source: Option<String>,
) -> Self {
DiagnosticsInfo {
diagnostics: vec![],
diagnostics_messages,
source,
diagnostics_message_limit: Some(10000),
lock_message_list: false,
}
}
pub fn add_message(&mut self, extra_info: DMExtraInfo, code: &str) {
if self.lock_message_list {
log::debug!(
"Diagnostic message list is locked, message `{}` was not added.",
code
);
return;
}
log::error!("`{}` at: {}", code, extra_info.range_as_string());
if std::env::var("RUST_BACKTRACE").unwrap_or_default() == "1" {
let backtrace = backtrace::Backtrace::new();
log::error!("{:#?}", backtrace);
}
let range = extra_info.range;
let message = self.get_correct_message(extra_info, code);
let new_message = Diagnostic {
range,
severity: message.severity,
code: Some(NumberOrString::String(code.to_owned())),
source: self.source.clone(),
message: message.message.clone(),
tags: message.tags,
..Default::default()
};
let is_duplicate = self
.diagnostics
.iter()
.rev()
.take(10)
.any(|x| x == &new_message);
if is_duplicate {
log::warn!("Diagnostic message is duplicate");
} else {
self.diagnostics.push(new_message);
}
}
pub fn check_message_limit_reached(&self) -> bool {
if let Some(message_limit) = self.diagnostics_message_limit {
self.diagnostics.len() >= message_limit
} else {
false
}
}
pub fn lock_message_list(&mut self) {
log::debug!("Diagnostic message list locked.");
self.lock_message_list = true;
}
pub fn get_message_lock_state(&self) -> bool {
self.lock_message_list
}
pub fn get_diagnostic_list(&self) -> &[Diagnostic] {
&self.diagnostics
}
pub fn extract_diagnostic_list(self) -> Vec<Diagnostic> {
self.diagnostics
}
fn get_correct_message(&self, extra_info: DMExtraInfo, code: &str) -> DiagnosticsMessage {
let default_error = DiagnosticsMessage {
message: "Language server error: Error code not found. \
Please report this."
.to_owned(),
..Default::default()
};
let mut diagnostics_message = match self.diagnostics_messages.get(code) {
Some(message) => message.clone(),
None => {
log::error!("Could not find error code: {}", code);
return default_error;
}
};
diagnostics_message.message =
Self::replace_template(diagnostics_message.message, extra_info);
#[cfg(any(debug_assertions, test))]
if diagnostics_message.message.contains("{{") {
panic!(
"Diagnostic message template not filled in: {}",
diagnostics_message.message
);
}
diagnostics_message
}
fn replace_template(mut message: String, extra_info: DMExtraInfo) -> String {
for (key, value) in extra_info.message_template_data {
let template = format!("{{{{{}}}}}", key); message = message.replace(&template, &value);
}
message = message.replace("{{start_line}}", &extra_info.range.start.line.to_string());
message = message.replace(
"{{start_char}}",
&extra_info.range.start.character.to_string(),
);
message = message.replace("{{end_line}}", &extra_info.range.end.line.to_string());
message = message.replace("{{end_char}}", &extra_info.range.end.character.to_string());
message
}
pub fn load_from_file(set: DiagnosticMessageSet, source: Option<String>) -> DiagnosticsInfo {
let file_bytes = match set {
DiagnosticMessageSet::Lexer => crate::DiagnosticsMessages::get("lexer_dm.json"),
DiagnosticMessageSet::Syntax => crate::DiagnosticsMessages::get("syntax_dm.json"),
DiagnosticMessageSet::Semantic => crate::DiagnosticsMessages::get("semantic_dm.json"),
}
.expect("Error loading DiagnosticsInfo.");
let diagnostics_messages = std::str::from_utf8(file_bytes.data.as_ref())
.expect("Error loading DiagnosticsInfo, Non UTF-8 Found.");
DiagnosticsInfo::new(parse_json_file(diagnostics_messages).unwrap(), source)
}
}
fn parse_json_file<C: DeserializeOwned>(file: &str) -> Result<C, Error> {
let parsed_result = &mut serde_json::de::Deserializer::from_str(file);
let result: Result<C, _> = serde_path_to_error::deserialize(parsed_result);
let parsed_object: C = match result {
Ok(data) => data,
Err(err) => {
let path = err.path().to_string();
log::error!("Error: {} \nIn: {}", err, path);
return Err(Error::from(err));
}
};
Ok(parsed_object)
}
#[macro_export]
macro_rules! hash_map {
($($key:expr => $val:expr),* $(,)*) => ({
#[allow(unused_mut)]
let mut map = ::std::collections::HashMap::new();
$( map.insert($key.to_owned(), $val); )*
map
});
}