Skip to content

feat: add syntax highlighting support #11006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
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
286 changes: 180 additions & 106 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ rkyv = { version = "=0.8.8", default-features = false, features = ["std", "

# Must be pinned with the same swc versions
pnp = { version = "0.9.0", default-features = false }
swc = { version = "=30.0.0", default-features = false }
swc = { version = "=31.0.0", default-features = false }
swc_config = { version = "=3.1.1", default-features = false }
swc_core = { version = "=31.1.0", default-features = false, features = ["parallel_rayon"] }
swc_ecma_lexer = { version = "=19.0.1", default-features = false }
swc_ecma_minifier = { version = "=25.0.0", default-features = false }
swc_error_reporters = { version = "=15.0.1", default-features = false }
swc_html = { version = "=24.0.1", default-features = false }
swc_html_minifier = { version = "=25.0.0", default-features = false }
swc_node_comments = { version = "=13.0.0", default-features = false }
swc_core = { version = "=32.0.2", default-features = false, features = ["parallel_rayon"] }
swc_ecma_lexer = { version = "=20.0.1", default-features = false }
swc_ecma_minifier = { version = "=26.0.0", default-features = false }
swc_error_reporters = { version = "=16.0.1", default-features = false }
swc_html = { version = "=25.0.0", default-features = false }
swc_html_minifier = { version = "=26.0.0", default-features = false }
swc_node_comments = { version = "=14.0.0", default-features = false }

rspack_dojang = { version = "0.1.11", default-features = false }

Expand Down
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
2 changes: 1 addition & 1 deletion crates/rspack_workspace/src/generated.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! This is a generated file. Don't modify it by hand! Run 'cargo codegen' to re-generate the file.
/// The version of the `swc_core` package used in the current workspace.
pub const fn rspack_swc_core_version() -> &'static str {
"31.1.0"
"32.0.2"
}

/// The version of the JavaScript `@rspack/core` package.
Expand Down
1 change: 1 addition & 0 deletions packages/rspack-test-tools/src/processor/diagnostic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class DiagnosticProcessor<

const statsJson = stats.toJson({
all: false,
colors: false,
errors: true,
warnings: true
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ it('should parse invalid regex syntax', () => {

console.log('regex:', a, b, c, d, f, h)
})

3 changes: 2 additions & 1 deletion xtask/benchmark/benches/groups/bundle/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{path::PathBuf, sync::Arc};
use rspack::builder::{Builder, CompilerBuilder};
use rspack_core::{
Compiler, Experiments, Mode, ModuleOptions, ModuleRule, ModuleRuleEffect, ModuleRuleUse,
ModuleRuleUseLoader, Resolve, RuleSetCondition,
ModuleRuleUseLoader, Resolve, RuleSetCondition, StatsOptions,
};
use rspack_fs::{MemoryFileSystem, NativeFileSystem};
use rspack_regex::RspackRegex;
Expand Down Expand Up @@ -59,6 +59,7 @@ pub fn basic_compiler_builder(options: BuilderOptions) -> CompilerBuilder {
extensions: Some(vec!["...".to_string(), ".jsx".to_string()]),
..Default::default()
})
.stats(StatsOptions { colors: false })
.experiments(Experiments::builder().css(true))
.input_filesystem(Arc::new(NativeFileSystem::new(false)))
.output_filesystem(Arc::new(MemoryFileSystem::default()))
Expand Down
Loading