mod handlers;
mod named_source;
mod protocol;
mod service;
mod source_impls;
use std::{
borrow::Cow,
fmt::{self, Display},
ops::{Deref, DerefMut},
};
pub mod reporter;
pub use crate::service::{DiagnosticSender, DiagnosticService};
pub use crate::{
handlers::{GraphicalReportHandler, GraphicalTheme, JSONReportHandler},
named_source::NamedSource,
protocol::{Diagnostic, Severity, SourceCode},
};
pub use oxc_span::LabeledSpan;
pub type Error = Box<dyn Diagnostic + Send + Sync>;
pub type Result<T> = std::result::Result<T, OxcDiagnostic>;
fn render(diagnostic: &dyn Diagnostic) -> String {
let mut output = String::new();
let _ = GraphicalReportHandler::new_themed(GraphicalTheme::none())
.with_width(80)
.with_links(false)
.render_report(&mut output, diagnostic);
output
}
pub type Labels = smallvec::SmallVec<[LabeledSpan; 2]>;
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Diagnostics(Vec<OxcDiagnostic>);
impl Diagnostics {
pub const fn new() -> Self {
Self(Vec::new())
}
pub fn has_errors(&self) -> bool {
self.0.iter().any(|diagnostic| diagnostic.severity == Severity::Error)
}
pub fn has_warnings(&self) -> bool {
self.0.iter().any(|diagnostic| diagnostic.severity == Severity::Warning)
}
pub fn errors(&self) -> impl Iterator<Item = &OxcDiagnostic> {
self.0.iter().filter(|diagnostic| diagnostic.severity == Severity::Error)
}
pub fn warnings(&self) -> impl Iterator<Item = &OxcDiagnostic> {
self.0.iter().filter(|diagnostic| diagnostic.severity == Severity::Warning)
}
pub fn into_vec(self) -> Vec<OxcDiagnostic> {
self.0
}
pub fn push(&mut self, diagnostic: OxcDiagnostic) {
self.0.push(diagnostic);
}
pub fn extend(&mut self, diagnostics: impl IntoIterator<Item = OxcDiagnostic>) {
self.0.extend(diagnostics);
}
}
impl Deref for Diagnostics {
type Target = Vec<OxcDiagnostic>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Diagnostics {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<Vec<OxcDiagnostic>> for Diagnostics {
fn from(diagnostics: Vec<OxcDiagnostic>) -> Self {
Self(diagnostics)
}
}
impl From<OxcDiagnostic> for Diagnostics {
fn from(diagnostic: OxcDiagnostic) -> Self {
Self(vec![diagnostic])
}
}
impl From<Diagnostics> for Vec<OxcDiagnostic> {
fn from(diagnostics: Diagnostics) -> Self {
diagnostics.0
}
}
impl FromIterator<OxcDiagnostic> for Diagnostics {
fn from_iter<T: IntoIterator<Item = OxcDiagnostic>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl IntoIterator for Diagnostics {
type Item = OxcDiagnostic;
type IntoIter = std::vec::IntoIter<OxcDiagnostic>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> IntoIterator for &'a Diagnostics {
type Item = &'a OxcDiagnostic;
type IntoIter = std::slice::Iter<'a, OxcDiagnostic>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[must_use]
pub struct OxcDiagnostic {
inner: Box<OxcDiagnosticInner>,
}
impl Deref for OxcDiagnostic {
type Target = OxcDiagnosticInner;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for OxcDiagnostic {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[derive(Debug, Default, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct OxcCode {
pub scope: Option<Cow<'static, str>>,
pub number: Option<Cow<'static, str>>,
}
impl OxcCode {
#[must_use]
pub fn is_some(&self) -> bool {
self.scope.is_some() || self.number.is_some()
}
}
impl Display for OxcCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (&self.scope, &self.number) {
(Some(scope), Some(number)) => write!(f, "{scope}({number})"),
(Some(scope), None) => scope.fmt(f),
(None, Some(number)) => number.fmt(f),
(None, None) => Ok(()),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct OxcDiagnosticInner {
pub message: Cow<'static, str>,
pub labels: Labels,
pub help: Option<Cow<'static, str>>,
pub note: Option<Cow<'static, str>>,
pub severity: Severity,
pub code: OxcCode,
pub url: Option<Cow<'static, str>>,
}
impl Display for OxcDiagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.message.fmt(f)
}
}
impl std::error::Error for OxcDiagnostic {}
impl Diagnostic for OxcDiagnostic {
fn help(&self) -> Option<Cow<'_, str>> {
self.help.as_deref().map(Cow::Borrowed)
}
fn note(&self) -> Option<Cow<'_, str>> {
self.note.as_deref().map(Cow::Borrowed)
}
fn severity(&self) -> Option<Severity> {
Some(self.severity)
}
fn labels(&self) -> &[LabeledSpan] {
&self.labels
}
fn code(&self) -> Option<Cow<'_, str>> {
self.code.is_some().then(|| Cow::Owned(self.code.to_string()))
}
fn url(&self) -> Option<Cow<'_, str>> {
self.url.as_deref().map(Cow::Borrowed)
}
}
impl OxcDiagnostic {
#[cold]
#[inline(never)]
pub fn error<T: Into<Cow<'static, str>>>(message: T) -> Self {
Self::new(Severity::Error, message.into())
}
#[cold]
#[inline(never)]
pub fn warn<T: Into<Cow<'static, str>>>(message: T) -> Self {
Self::new(Severity::Warning, message.into())
}
pub fn render(&self) -> String {
render(self)
}
#[cold]
#[inline(never)]
fn new(severity: Severity, message: Cow<'static, str>) -> Self {
Self {
inner: Box::new(OxcDiagnosticInner {
message,
labels: Labels::new(),
help: None,
note: None,
severity,
code: OxcCode::default(),
url: None,
}),
}
}
#[inline]
pub fn with_error_code<T: Into<Cow<'static, str>>, U: Into<Cow<'static, str>>>(
self,
scope: T,
number: U,
) -> Self {
self.with_error_code_scope(scope).with_error_code_num(number)
}
#[inline]
pub fn with_error_code_scope<T: Into<Cow<'static, str>>>(mut self, code_scope: T) -> Self {
self.inner.code.scope.get_or_insert_with(|| code_scope.into());
debug_assert!(
self.inner.code.scope.as_ref().is_some_and(|s| !s.is_empty()),
"Error code scopes cannot be empty"
);
self
}
#[inline]
pub fn with_error_code_num<T: Into<Cow<'static, str>>>(mut self, code_num: T) -> Self {
self.inner.code.number.get_or_insert_with(|| code_num.into());
debug_assert!(
self.inner.code.number.as_ref().is_some_and(|n| !n.is_empty()),
"Error code numbers cannot be empty"
);
self
}
pub fn with_severity(mut self, severity: Severity) -> Self {
self.inner.severity = severity;
self
}
#[cold]
#[inline(never)]
pub fn with_help<T: Into<Cow<'static, str>>>(mut self, help: T) -> Self {
self.inner.help = Some(help.into());
self
}
#[cold]
#[inline(never)]
pub fn with_note<T: Into<Cow<'static, str>>>(mut self, note: T) -> Self {
self.inner.note = Some(note.into());
self
}
pub fn with_label<T: Into<LabeledSpan>>(mut self, label: T) -> Self {
self.inner.labels = std::iter::once(label.into()).collect();
self
}
pub fn with_labels<L: Into<LabeledSpan>, T: IntoIterator<Item = L>>(
mut self,
labels: T,
) -> Self {
self.inner.labels = labels.into_iter().map(Into::into).collect();
self
}
pub fn and_label<T: Into<LabeledSpan>>(mut self, label: T) -> Self {
self.inner.labels.push(label.into());
self
}
pub fn and_labels<L: Into<LabeledSpan>, T: IntoIterator<Item = L>>(
mut self,
labels: T,
) -> Self {
self.inner.labels.extend(labels.into_iter().map(Into::into));
self
}
pub fn with_url<S: Into<Cow<'static, str>>>(mut self, url: S) -> Self {
self.inner.url = Some(url.into());
self
}
pub fn with_source_code<T: SourceCode + 'static>(self, source_code: T) -> Error {
Box::new(DiagnosticWithSource { diagnostic: self, source_code })
}
pub fn render_with_source_code<T: SourceCode>(self, source_code: T) -> String {
render(&DiagnosticWithSource { diagnostic: self, source_code })
}
pub fn inner_owned(self) -> OxcDiagnosticInner {
*self.inner
}
}
impl From<OxcDiagnostic> for Error {
fn from(diagnostic: OxcDiagnostic) -> Self {
Box::new(diagnostic)
}
}
struct DiagnosticWithSource<S> {
diagnostic: OxcDiagnostic,
source_code: S,
}
impl<S> fmt::Debug for DiagnosticWithSource<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.diagnostic, f)
}
}
impl<S> Display for DiagnosticWithSource<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.diagnostic, f)
}
}
impl<S> std::error::Error for DiagnosticWithSource<S> {}
impl<S: SourceCode> Diagnostic for DiagnosticWithSource<S> {
fn code(&self) -> Option<Cow<'_, str>> {
self.diagnostic.code()
}
fn severity(&self) -> Option<Severity> {
self.diagnostic.severity()
}
fn help(&self) -> Option<Cow<'_, str>> {
self.diagnostic.help()
}
fn note(&self) -> Option<Cow<'_, str>> {
self.diagnostic.note()
}
fn url(&self) -> Option<Cow<'_, str>> {
self.diagnostic.url()
}
fn labels(&self) -> &[LabeledSpan] {
self.diagnostic.labels()
}
fn source_code(&self) -> Option<&dyn SourceCode> {
Some(&self.source_code)
}
}