Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion crates/rspack_error/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ anyhow = { workspace = true, features = ["backtrace"] }
cow-utils = { workspace = true }
derive_more = { workspace = true, features = ["debug"] }
futures = { workspace = true }
miette = { workspace = true, features = ["fancy", "derive"] }
once_cell = { workspace = true }
owo-colors = { workspace = true }
rspack_cacheable = { workspace = true }
Expand All @@ -26,3 +25,9 @@ textwrap = { workspace = true }
thiserror = { workspace = true }

unicode-width = { workspace = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
miette = { workspace = true, features = ["fancy", "derive"] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
miette = { workspace = true, features = ["fancy", "derive", "syntect-highlighter"] }
34 changes: 26 additions & 8 deletions crates/rspack_error/src/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use std::{borrow::Cow, fmt, ops::Deref, sync::Arc};
use std::{
borrow::Cow,
fmt,
ops::Deref,
sync::{Arc, LazyLock},
};

use cow_utils::CowUtils;
use miette::{GraphicalTheme, IntoDiagnostic, MietteDiagnostic};
Expand Down Expand Up @@ -165,18 +170,31 @@ impl Diagnostic {
}
}

static COLORED_GRAPHICAL_REPORT_HANDLER: LazyLock<GraphicalReportHandler> = LazyLock::new(|| {
GraphicalReportHandler::new()
.with_theme(GraphicalTheme::unicode())
.with_context_lines(2)
.with_width(usize::MAX)
});

static NO_COLOR_GRAPHICAL_REPORT_HANDLER: LazyLock<GraphicalReportHandler> = LazyLock::new(|| {
GraphicalReportHandler::new()
.with_theme(GraphicalTheme::unicode_nocolor())
.with_context_lines(2)
.with_width(usize::MAX)
.without_syntax_highlighting()
});

impl Diagnostic {
pub fn render_report(&self, colored: bool) -> crate::Result<String> {
let mut buf = String::new();
let theme = if colored {
GraphicalTheme::unicode()

let h = if colored {
&COLORED_GRAPHICAL_REPORT_HANDLER
} else {
GraphicalTheme::unicode_nocolor()
&NO_COLOR_GRAPHICAL_REPORT_HANDLER
};
let h = GraphicalReportHandler::new()
.with_theme(theme)
.with_context_lines(2)
.with_width(usize::MAX);

h.render_report(&mut buf, self.as_ref()).into_diagnostic()?;
Ok(buf)
}
Expand Down
54 changes: 46 additions & 8 deletions crates/rspack_error/src/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
use std::fmt::{self, Write};

use miette::{
Diagnostic, GraphicalTheme, LabeledSpan, MietteError, ReportHandler, Severity, SourceCode,
SourceSpan, SpanContents,
highlighters::Highlighter, Diagnostic, GraphicalTheme, LabeledSpan, MietteError, ReportHandler,
Severity, SourceCode, SourceSpan, SpanContents,
};
use owo_colors::{OwoColorize, Style};
use owo_colors::{OwoColorize, Style, StyledList};
use unicode_width::UnicodeWidthChar;

use crate::highlighters::MietteHighlighter;

/**
A [`ReportHandler`] that displays a given [`Report`](crate::Report) in a
quasi-graphical way, using terminal colors, unicode drawing characters, and
Expand All @@ -35,6 +37,7 @@ pub struct GraphicalReportHandler {
pub(crate) context_lines: usize,
pub(crate) tab_width: usize,
pub(crate) with_cause_chain: bool,
pub(crate) highlighter: MietteHighlighter,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand All @@ -56,6 +59,7 @@ impl GraphicalReportHandler {
context_lines: 1,
tab_width: 4,
with_cause_chain: true,
highlighter: MietteHighlighter::default(),
}
}

Expand All @@ -69,6 +73,7 @@ impl GraphicalReportHandler {
context_lines: 1,
tab_width: 4,
with_cause_chain: true,
highlighter: MietteHighlighter::default(),
}
}

Expand Down Expand Up @@ -138,6 +143,23 @@ impl GraphicalReportHandler {
self.context_lines = lines;
self
}

/// Enable syntax highlighting for source code snippets, using the given
/// [`Highlighter`]. See the [crate::highlighters] crate for more details.
pub fn with_syntax_highlighting(
mut self,
highlighter: impl Highlighter + Send + Sync + 'static,
) -> Self {
self.highlighter = MietteHighlighter::from(highlighter);
self
}

/// Disable syntax highlighting. This uses the
/// [`crate::highlighters::BlankHighlighter`] as a no-op highlighter.
pub fn without_syntax_highlighting(mut self) -> Self {
self.highlighter = MietteHighlighter::nocolor();
self
}
}

impl Default for GraphicalReportHandler {
Expand Down Expand Up @@ -398,6 +420,8 @@ impl GraphicalReportHandler {
.map(|(label, st)| FancySpan::new(label.label().map(String::from), *label.inner(), st))
.collect::<Vec<_>>();

let mut highlighter_state = self.highlighter.start_highlighter_state(&*contents);

// The max number of gutter-lines that will be active at any given
// point. We need this to figure out indentation, so we do one loop
// over the lines to see what the damage is gonna be.
Expand Down Expand Up @@ -461,7 +485,8 @@ impl GraphicalReportHandler {
self.render_line_gutter(f, max_gutter, line, &labels)?;

// And _now_ we can print out the line text itself!
self.render_line_text(f, &line.text)?;
let styled_text = StyledList::from(highlighter_state.highlight_line(&line.text)).to_string();
self.render_line_text(f, &styled_text)?;

// Next, we write all the highlights that apply to this particular line.
let (single_line, multi_line): (Vec<_>, Vec<_>) = labels
Expand Down Expand Up @@ -620,13 +645,26 @@ impl GraphicalReportHandler {
/// Returns an iterator over the visual width of each character in a line.
fn line_visual_char_width<'a>(&self, text: &'a str) -> impl Iterator<Item = usize> + 'a {
let mut column = 0;
let mut escaped = false;
let tab_width = self.tab_width;
text.chars().map(move |c| {
let width = if c == '\t' {
let width = match (escaped, c) {
// Round up to the next multiple of tab_width
tab_width - column % tab_width
} else {
c.width().unwrap_or(0)
(false, '\t') => tab_width - column % tab_width,
// start of ANSI escape
(false, '\x1b') => {
escaped = true;
0
}
// use Unicode width for all other characters
(false, c) => c.width().unwrap_or(0),
// end of ANSI escape
(true, 'm') => {
escaped = false;
0
}
// characters are zero width within escape sequence
(true, _) => 0,
};
column += width;
width
Expand Down
76 changes: 76 additions & 0 deletions crates/rspack_error/src/highlighters/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! This module provides a trait for creating custom syntax highlighters that
//! highlight [`Diagnostic`](crate::Diagnostic) source code with ANSI escape
//! sequences when rendering with the [`GraphicalReportHighlighter`](crate::graphical::GraphicalReportHandler).
//!
//! It also provides built-in highlighter implementations that you can use out of the box.
//! By default, there are no syntax highlighters exported by miette
//! (except for the no-op [`BlankHighlighter`]).
//! To enable support for specific highlighters, you should enable their associated feature flag.
//!
//! Currently supported syntax highlighters and their feature flags:
//! * `syntect-highlighter` - Enables [`syntect`](https://docs.rs/syntect/latest/syntect/) syntax highlighting support via the [`SyntectHighlighter`]

/// THIS FILE IS ORIGINALLY FROM THE MIETTE PROJECT:
/// https://github.com/zkat/miette/blob/907857058dc255caeae456e87146c629ce69cf5c/src/highlighters/mod.rs
use std::{ops::Deref, sync::Arc};

#[cfg(not(target_family = "wasm"))]
use miette::highlighters::SyntectHighlighter;
use miette::highlighters::{BlankHighlighter, Highlighter};

/// Arcified trait object for Highlighter. Used internally by [`crate::graphical::GraphicalReportHandler`]
///
/// Wrapping the trait object in this way allows us to implement `Debug` and `Clone`.
#[derive(Clone)]
#[repr(transparent)]
pub(crate) struct MietteHighlighter(Arc<dyn Highlighter + Send + Sync>);

impl MietteHighlighter {
pub(crate) fn nocolor() -> Self {
Self::from(BlankHighlighter)
}

#[cfg(not(target_family = "wasm"))]
pub(crate) fn syntect_truecolor() -> Self {
Self::from(SyntectHighlighter::default())
}
}

impl Default for MietteHighlighter {
#[cfg(not(target_family = "wasm"))]
fn default() -> Self {
use std::io::IsTerminal;
match std::env::var("NO_COLOR") {
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
//TODO: should use ANSI styling instead of 24-bit truecolor here
MietteHighlighter::syntect_truecolor()
}
Ok(string) if string != "0" => MietteHighlighter::nocolor(),
_ => MietteHighlighter::syntect_truecolor(),
}
}

#[cfg(target_family = "wasm")]
fn default() -> Self {
MietteHighlighter::nocolor()
}
}

impl<T: Highlighter + Send + Sync + 'static> From<T> for MietteHighlighter {
fn from(value: T) -> Self {
Self(Arc::new(value))
}
}

impl std::fmt::Debug for MietteHighlighter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MietteHighlighter(...)")
}
}

impl Deref for MietteHighlighter {
type Target = dyn Highlighter + Send + Sync;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
1 change: 1 addition & 0 deletions crates/rspack_error/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod diagnostic;
mod error;
mod ext;
pub(crate) mod graphical;
pub(crate) mod highlighters;
pub(crate) mod miette_helpers;
pub use catch_unwind::*;
pub use diagnostic::*;
Expand Down
Loading
Loading