Skip to content

Commit

Permalink
chore(remap): add new remap-diagnostic crate
Browse files Browse the repository at this point in the history
Signed-off-by: Jean Mertz <git@jeanmertz.com>
  • Loading branch information
JeanMertz committed Feb 4, 2021
1 parent 9bb1509 commit 86daabf
Show file tree
Hide file tree
Showing 10 changed files with 878 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ members = [
"lib/prometheus-parser",
"lib/remap-cli",
"lib/remap-compiler",
"lib/remap-diagnostic",
"lib/remap-functions",
"lib/remap-lang",
"lib/remap-parser",
Expand Down
10 changes: 10 additions & 0 deletions lib/remap-diagnostic/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "remap-diagnostic"
version = "0.1.0"
authors = ["Vector Contributors <vector@timber.io>"]
edition = "2018"
publish = false

[dependencies]
codespan-reporting = "0.11"
termcolor = "1"
363 changes: 363 additions & 0 deletions lib/remap-diagnostic/LICENSE

Large diffs are not rendered by default.

225 changes: 225 additions & 0 deletions lib/remap-diagnostic/src/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
use crate::{DiagnosticError, Label, Note, Severity, Span};
use codespan_reporting::diagnostic;
use std::ops::{Deref, DerefMut};

#[derive(Debug, Clone, PartialEq)]
pub struct Diagnostic {
severity: Severity,
message: String,
labels: Vec<Label>,
notes: Vec<Note>,
}

impl Diagnostic {
pub fn error(message: impl ToString) -> Self {
Self::new(Severity::Error, message, vec![], vec![])
}

pub fn bug(message: impl ToString) -> Self {
Self::new(Severity::Bug, message, vec![], vec![])
}

pub fn new(
severity: Severity,
message: impl ToString,
labels: Vec<Label>,
notes: Vec<Note>,
) -> Self {
Self {
severity,
message: message.to_string(),
labels,
notes,
}
}

pub fn with_primary(self, message: impl ToString, span: impl Into<Span>) -> Self {
self.with_label(Label::primary(message, span.into()))
}

pub fn with_context(self, message: impl ToString, span: impl Into<Span>) -> Self {
self.with_label(Label::context(message, span.into()))
}

pub fn with_label(mut self, label: Label) -> Self {
self.labels.push(label);
self
}

pub fn with_note(mut self, note: Note) -> Self {
self.notes.push(note);
self
}

pub fn severity(&self) -> Severity {
self.severity
}

pub fn message(&self) -> &str {
&self.message
}

pub fn notes(&self) -> &[Note] {
&self.notes
}

pub fn labels(&self) -> &[Label] {
&self.labels
}

/// Returns `true` if the diagnostic represents either an
/// [error](Variant::Error) or [bug](Variant::Bug).
#[inline]
pub fn is_problem(&self) -> bool {
self.severity.is_error() || self.severity.is_bug()
}

/// Returns `true` if the diagnostic represents a [bug](Variant::Bug).
#[inline]
pub fn is_bug(&self) -> bool {
self.severity.is_bug()
}

/// Returns `true` if the diagnostic represents an [error](Variant::Error).
#[inline]
pub fn is_error(&self) -> bool {
self.severity.is_error()
}

/// Returns `true` if the diagnostic represents a
/// [warning](Variant::Warning).
#[inline]
pub fn is_warning(&self) -> bool {
self.severity.is_warning()
}

/// Returns `true` if the diagnostic represents a [note](Variant::Note).
#[inline]
pub fn is_note(&self) -> bool {
self.severity.is_note()
}
}

impl From<Box<dyn DiagnosticError>> for Diagnostic {
fn from(error: Box<dyn DiagnosticError>) -> Self {
Self {
severity: Severity::Error,
message: error.message(),
labels: error.labels(),
notes: error.notes(),
}
}
}

impl Into<diagnostic::Diagnostic<()>> for Diagnostic {
fn into(self) -> diagnostic::Diagnostic<()> {
let mut notes = self.notes.to_vec();
notes.push(Note::SeeLangDocs);

diagnostic::Diagnostic {
severity: self.severity.into(),
code: None,
message: self.message.to_string(),
labels: self.labels.to_vec().into_iter().map(Into::into).collect(),
notes: notes.iter().map(ToString::to_string).collect(),
}
}
}

// -----------------------------------------------------------------------------

#[derive(Debug, Clone, Default, PartialEq)]
pub struct DiagnosticList(Vec<Diagnostic>);

impl DiagnosticList {
/// Turns the diagnostic list into a result type, the `Ok` variant is
/// returned if none of the diagnostics are errors or bugs. Otherwise the
/// `Err` variant is returned.
pub fn into_result(self) -> std::result::Result<DiagnosticList, DiagnosticList> {
if self.is_err() {
return Err(self);
}

Ok(self)
}

/// Returns `true` if there are any errors or bugs in the parsed source.
pub fn is_err(&self) -> bool {
self.0.iter().any(|d| d.is_problem())
}

/// Returns the list of bug-level diagnostics.
pub fn bugs(&self) -> Vec<&Diagnostic> {
self.0.iter().filter(|d| d.is_bug()).collect()
}

/// Returns the list of error-level diagnostics.
pub fn errors(&self) -> Vec<&Diagnostic> {
self.0.iter().filter(|d| d.is_error()).collect()
}

/// Returns the list of warning-level diagnostics.
pub fn warnings(&self) -> Vec<&Diagnostic> {
self.0.iter().filter(|d| d.is_warning()).collect()
}

/// Returns the list of note-level diagnostics.
pub fn notes(&self) -> Vec<&Diagnostic> {
self.0.iter().filter(|d| d.is_note()).collect()
}

/// Returns `true` if there are any bug diagnostics.
pub fn has_bugs(&self) -> bool {
self.0.iter().any(|d| d.is_bug())
}

/// Returns `true` if there are any error diagnostics.
pub fn has_errors(&self) -> bool {
self.0.iter().any(|d| d.is_error())
}

/// Returns `true` if there are any warning diagnostics.
pub fn has_warnings(&self) -> bool {
self.0.iter().any(|d| d.is_warning())
}

/// Returns `true` if there are any note diagnostics.
pub fn has_notes(&self) -> bool {
self.0.iter().any(|d| d.is_note())
}
}

impl Deref for DiagnosticList {
type Target = Vec<Diagnostic>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for DiagnosticList {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl IntoIterator for DiagnosticList {
type Item = Diagnostic;
type IntoIter = std::vec::IntoIter<Diagnostic>;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

impl<T: Into<Diagnostic>> From<Vec<T>> for DiagnosticList {
fn from(diagnostics: Vec<T>) -> Self {
Self(diagnostics.into_iter().map(Into::into).collect())
}
}

impl<T: Into<Diagnostic>> From<T> for DiagnosticList {
fn from(diagnostic: T) -> Self {
Self(vec![diagnostic.into()])
}
}
65 changes: 65 additions & 0 deletions lib/remap-diagnostic/src/formatter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::DiagnosticList;
use std::fmt;

/// A formatter to display diagnostics tied to a given source.
pub struct Formatter<'a> {
source: &'a str,
diagnostics: DiagnosticList,
color: bool,
}

impl<'a> Formatter<'a> {
pub fn new(source: &'a str, diagnostics: impl Into<DiagnosticList>) -> Self {
Self {
source,
diagnostics: diagnostics.into(),
color: false,
}
}

pub fn colored(mut self) -> Self {
self.color = true;
self
}

pub fn enable_colors(&mut self, color: bool) {
self.color = color
}
}

impl<'a> fmt::Display for Formatter<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use codespan_reporting::files::SimpleFile;
use codespan_reporting::term;
use std::str::from_utf8;
use termcolor::Buffer;

let file = SimpleFile::new("", self.source);
let config = term::Config::default();
let mut buffer = if self.color {
Buffer::ansi()
} else {
Buffer::no_color()
};

f.write_str("\n")?;

for diagnostic in self.diagnostics.iter() {
term::emit(&mut buffer, &config, &file, &diagnostic.to_owned().into())
.map_err(|_| fmt::Error)?;
}

// Diagnostic messages can contain whitespace at the end of some lines.
// This causes problems when used in our UI testing, as editors often
// strip end-of-line whitespace. Removing this has no actual visual
// impact.
let string = from_utf8(buffer.as_slice())
.map_err(|_| fmt::Error)?
.lines()
.map(|line| line.trim_end())
.collect::<Vec<_>>()
.join("\n");

f.write_str(&string)
}
}
43 changes: 43 additions & 0 deletions lib/remap-diagnostic/src/label.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::Span;
use codespan_reporting::diagnostic;

#[derive(Debug, PartialEq, Clone)]
pub struct Label {
pub message: String,
pub primary: bool,
pub span: Span,
}

impl Label {
pub fn primary(message: impl ToString, span: impl Into<Span>) -> Self {
Self {
message: message.to_string(),
primary: true,
span: span.into(),
}
}

pub fn context(message: impl ToString, span: impl Into<Span>) -> Self {
Self {
message: message.to_string(),
primary: false,
span: span.into(),
}
}
}

impl Into<diagnostic::Label<()>> for Label {
fn into(self) -> diagnostic::Label<()> {
let style = match self.primary {
true => diagnostic::LabelStyle::Primary,
false => diagnostic::LabelStyle::Secondary,
};

diagnostic::Label {
style,
file_id: (),
range: self.span.start()..self.span.end(),
message: self.message,
}
}
}
38 changes: 38 additions & 0 deletions lib/remap-diagnostic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
mod diagnostic;
mod formatter;
mod label;
mod note;
mod severity;
mod span;

pub use diagnostic::{Diagnostic, DiagnosticList};
pub use formatter::Formatter;
pub use label::Label;
pub use note::Note;
pub use severity::Severity;
pub use span::{span, Span};

/// A trait that can be implemented by error types to provide diagnostic
/// information about the given error.
pub trait DiagnosticError: std::error::Error {
/// The subject message of the error.
///
/// Defaults to the error message itself.
fn message(&self) -> String {
self.to_string()
}

/// One or more labels to provide more context for a given error.
///
/// Defaults to no labels.
fn labels(&self) -> Vec<Label> {
vec![]
}

/// One or more notes shown at the bottom of the diagnostic message.
///
/// Defaults to no notes.
fn notes(&self) -> Vec<Note> {
vec![]
}
}
Loading

0 comments on commit 86daabf

Please sign in to comment.