use std::{collections::hash_set::Iter, iter::FusedIterator, ops::Deref};
use ahash::AHashSet;
use lady_deirdre::{
analysis::Revision,
arena::{Id, Identifiable},
sync::Shared,
};
use crate::{
analysis::{IssueCode, IssueSeverity, ModuleText, ScriptIssue},
format::ScriptSnippet,
runtime::ScriptOrigin,
};
pub type DiagnosticsDepth = u8;
#[derive(Clone)]
pub struct ModuleDiagnostics {
pub(super) id: Id,
pub(super) issues: Shared<AHashSet<ScriptIssue>>,
pub(super) depth: DiagnosticsDepth,
pub(super) revision: Revision,
}
impl Identifiable for ModuleDiagnostics {
#[inline(always)]
fn id(&self) -> Id {
self.id
}
}
impl<'a> IntoIterator for &'a ModuleDiagnostics {
type Item = ModuleIssue<'a>;
type IntoIter = DiagnosticsIter<'a>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl ModuleDiagnostics {
#[inline(always)]
pub fn len(&self, severity_mask: u8) -> usize {
let errors = severity_mask & (IssueSeverity::Error as u8) > 0;
let warnings = severity_mask & (IssueSeverity::Warning as u8) > 0;
match (errors, warnings) {
(false, false) => 0,
(true, false) => self
.iter()
.filter(|error| error.severity() == IssueSeverity::Error)
.count(),
(false, true) => self
.iter()
.filter(|error| error.severity() == IssueSeverity::Warning)
.count(),
(true, true) => self.issues.as_ref().len(),
}
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.issues.as_ref().is_empty()
}
#[inline(always)]
pub fn depth(&self) -> DiagnosticsDepth {
self.depth
}
#[inline(always)]
pub fn revision(&self) -> Revision {
self.revision
}
#[inline(always)]
pub fn iter(&self) -> DiagnosticsIter {
DiagnosticsIter {
id: self.id,
inner: self.issues.as_ref().iter(),
}
}
pub fn highlight<'a>(&self, text: &'a ModuleText, severity_mask: u8) -> ScriptSnippet<'a> {
let mut snippet = text.snippet();
snippet.set_caption("diagnostics");
let include_errors = severity_mask & (IssueSeverity::Error as u8) > 0;
let include_warnings = severity_mask & (IssueSeverity::Warning as u8) > 0;
let mut total_errors = 0;
let mut total_warnings = 0;
#[allow(unused)]
let mut annotations = 0;
for issue in self.iter() {
match issue.severity() {
IssueSeverity::Error => {
total_errors += 1;
if !include_errors {
continue;
}
}
IssueSeverity::Warning => {
total_warnings += 1;
if !include_warnings {
continue;
}
}
}
annotations += 1;
snippet.annotate(
issue.origin(text),
issue.severity().priority(),
issue.verbose_message(text),
);
}
let mut summary = String::with_capacity(1024);
match total_errors == 0 && total_warnings == 0 {
true => summary.push_str("No issues detected."),
false => {
summary.push_str(&format!("Errors: {}", total_errors));
if !include_errors {
summary.push_str(" (omitted).");
}
summary.push('\n');
summary.push_str(&format!("Warnings: {}", total_warnings));
if !include_warnings {
summary.push_str(" (omitted).");
}
}
};
snippet.set_summary(summary);
snippet
}
}
pub struct DiagnosticsIter<'a> {
id: Id,
inner: Iter<'a, ScriptIssue>,
}
impl<'a> Iterator for DiagnosticsIter<'a> {
type Item = ModuleIssue<'a>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
let issue = self.inner.next()?;
Some(ModuleIssue { id: self.id, issue })
}
#[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<'a> FusedIterator for DiagnosticsIter<'a> {}
impl<'a> ExactSizeIterator for DiagnosticsIter<'a> {
#[inline(always)]
fn len(&self) -> usize {
self.inner.len()
}
}
pub struct ModuleIssue<'a> {
id: Id,
issue: &'a ScriptIssue,
}
impl<'a> Identifiable for ModuleIssue<'a> {
#[inline(always)]
fn id(&self) -> Id {
self.id
}
}
impl<'a> ModuleIssue<'a> {
#[inline(always)]
pub fn code(&self) -> IssueCode {
self.issue.code()
}
#[inline(always)]
pub fn severity(&self) -> IssueSeverity {
self.code().severity()
}
#[inline(always)]
pub fn short_message(&self) -> String {
self.code().to_string()
}
#[inline(always)]
pub fn verbose_message(&self, text: &ModuleText) -> String {
self.issue.message(text.doc_read.deref()).to_string()
}
#[inline(always)]
pub fn origin(&self, text: &ModuleText) -> ScriptOrigin {
self.issue.span(text.doc_read.deref())
}
pub fn quickfix(&self) -> Option<IssueQuickfix> {
match self.issue {
ScriptIssue::UnresolvedPackage { quickfix, .. } if !quickfix.is_empty() => {
Some(IssueQuickfix {
set_text_to_origin: Some(quickfix.to_string()),
implement_use_of: None,
})
}
ScriptIssue::UnresolvedIdent {
quickfix, import, ..
} if !quickfix.is_empty() || !import.is_empty() => Some(IssueQuickfix {
set_text_to_origin: (!quickfix.is_empty()).then(|| quickfix.to_string()),
implement_use_of: (!import.is_empty()).then(|| import.to_string()),
}),
ScriptIssue::UnknownComponent { quickfix, .. } if !quickfix.is_empty() => {
Some(IssueQuickfix {
set_text_to_origin: Some(quickfix.to_string()),
implement_use_of: None,
})
}
_ => None,
}
}
#[inline(always)]
pub fn highlight<'b>(&self, text: &'b ModuleText) -> ScriptSnippet<'b> {
let mut snippet = text.snippet();
snippet.set_caption(self.severity().to_string()).annotate(
self.origin(text),
self.severity().priority(),
self.verbose_message(text),
);
snippet
}
#[inline(always)]
pub fn depth(&self) -> DiagnosticsDepth {
self.code().depth()
}
}
#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct IssueQuickfix {
pub set_text_to_origin: Option<String>,
pub implement_use_of: Option<String>,
}