use super::{Error, Lint, Note};
use crate::ast::Ast;
use crate::grammar::{attributes, Attributable, Entity};
use crate::slice_file::{SliceFile, Span};
use crate::slice_options::SliceOptions;
#[derive(Debug)]
pub struct Diagnostic {
kind: DiagnosticKind,
level: DiagnosticLevel,
span: Option<Span>,
scope: Option<String>,
notes: Vec<Note>,
}
impl Diagnostic {
pub fn new(kind: impl Into<DiagnosticKind>) -> Self {
let kind = kind.into();
let level = match &kind {
DiagnosticKind::Error(_) => DiagnosticLevel::Error,
DiagnosticKind::Lint(lint) => lint.get_default_level(),
};
Diagnostic {
kind,
level,
span: None,
scope: None,
notes: Vec::new(),
}
}
pub fn message(&self) -> String {
match &self.kind {
DiagnosticKind::Error(error) => error.message(),
DiagnosticKind::Lint(lint) => lint.message(),
}
}
pub fn code(&self) -> &str {
match &self.kind {
DiagnosticKind::Error(error) => error.code(),
DiagnosticKind::Lint(lint) => lint.code(),
}
}
pub fn level(&self) -> DiagnosticLevel {
self.level
}
pub fn span(&self) -> Option<&Span> {
self.span.as_ref()
}
pub fn scope(&self) -> Option<&String> {
self.scope.as_ref()
}
pub fn notes(&self) -> &[Note] {
&self.notes
}
pub fn set_span(mut self, span: &Span) -> Self {
self.span = Some(span.to_owned());
self
}
pub fn set_scope(mut self, scope: impl Into<String>) -> Self {
self.scope = Some(scope.into());
self
}
pub fn add_note(mut self, message: impl Into<String>, span: Option<&Span>) -> Self {
self.notes.push(Note {
message: message.into(),
span: span.cloned(),
});
self
}
pub fn extend_notes<I: IntoIterator<Item = Note>>(mut self, iter: I) -> Self {
self.notes.extend(iter);
self
}
pub fn push_into(self, diagnostics: &mut Diagnostics) {
diagnostics.0.push(self);
}
}
#[derive(Debug)]
pub enum DiagnosticKind {
Error(Error),
Lint(Lint),
}
impl From<Error> for DiagnosticKind {
fn from(error: Error) -> Self {
DiagnosticKind::Error(error)
}
}
impl From<Lint> for DiagnosticKind {
fn from(lint: Lint) -> Self {
DiagnosticKind::Lint(lint)
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum DiagnosticLevel {
#[rustfmt::skip] Error,
Warning,
Allowed,
}
#[derive(Debug, Default)]
pub struct Diagnostics(Vec<Diagnostic>);
impl Diagnostics {
pub fn new() -> Self {
Self::default()
}
pub fn extend(&mut self, other: Diagnostics) {
self.0.extend(other.0);
}
pub fn has_errors(&self) -> bool {
let mut diagnostics = self.0.iter();
diagnostics.any(|diagnostic| matches!(diagnostic.kind, DiagnosticKind::Error(_)))
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn into_updated(mut self, ast: &Ast, files: &[SliceFile], options: &SliceOptions) -> Vec<Diagnostic> {
fn is_lint_allowed_by<'b>(mut identifiers: impl Iterator<Item = &'b String>, lint: &Lint) -> bool {
identifiers.any(|identifier| identifier == "All" || identifier == lint.code())
}
fn is_lint_allowed_by_attributes(attributable: &(impl Attributable + ?Sized), lint: &Lint) -> bool {
let attributes = attributable.all_attributes().concat().into_iter();
let mut allowed = attributes.filter_map(|a| a.downcast::<attributes::Allow>());
allowed.any(|allow| is_lint_allowed_by(allow.allowed_lints.iter(), lint))
}
for diagnostic in &mut self.0 {
if let DiagnosticKind::Lint(lint) = &diagnostic.kind {
if is_lint_allowed_by(options.allowed_lints.iter(), lint) {
diagnostic.level = DiagnosticLevel::Allowed;
}
if let Some(span) = diagnostic.span() {
let file = files.iter().find(|f| f.relative_path == span.file).expect("no file");
if is_lint_allowed_by_attributes(file, lint) {
diagnostic.level = DiagnosticLevel::Allowed;
}
}
if let Some(scope) = diagnostic.scope() {
if let Ok(entity) = ast.find_element::<dyn Entity>(scope) {
if is_lint_allowed_by_attributes(entity, lint) {
diagnostic.level = DiagnosticLevel::Allowed;
}
}
}
}
}
self.0
}
pub fn into_inner(self) -> Vec<Diagnostic> {
self.0
}
}
pub fn get_totals(diagnostics: &[Diagnostic]) -> (usize, usize) {
let (mut total_warnings, mut total_errors) = (0, 0);
for diagnostic in diagnostics {
match diagnostic.level() {
DiagnosticLevel::Error => total_errors += 1,
DiagnosticLevel::Warning => total_warnings += 1,
DiagnosticLevel::Allowed => {}
}
}
(total_warnings, total_errors)
}