diff --git a/Cargo.lock b/Cargo.lock index 279351836d5..6c21f261337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3329,6 +3329,7 @@ dependencies = [ "napi-derive", "napi-h", "once_cell", + "ropey", "rspack_allocator", "rspack_binding_options", "rspack_binding_values", @@ -3343,6 +3344,7 @@ dependencies = [ "rspack_plugin_html", "rspack_plugin_javascript", "rspack_tracing", + "rspack_util", "tokio", "tracing", ] @@ -3993,6 +3995,7 @@ dependencies = [ "indexmap 2.2.6", "itoa", "regex", + "ropey", "rspack_regex", "rustc-hash 1.1.0", "serde", diff --git a/Cargo.toml b/Cargo.toml index 43ffb7f1fc9..6242733b54a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ proc-macro2 = { version = "1.0.79" } quote = { version = "1.0.35" } rayon = { version = "1.10.0" } regex = { version = "1.10.4" } +ropey = "1.6.1" rspack_sources = { version = "=0.3.2" } rustc-hash = { version = "1.1.0" } serde = { version = "1.0.197" } diff --git a/crates/node_binding/Cargo.toml b/crates/node_binding/Cargo.toml index 074104cf18e..e342a78439c 100644 --- a/crates/node_binding/Cargo.toml +++ b/crates/node_binding/Cargo.toml @@ -14,6 +14,7 @@ default = [] plugin = ["rspack_binding_options/plugin"] [dependencies] +ropey = { workspace = true } rspack_allocator = { version = "0.1.0", path = "../rspack_allocator" } rspack_binding_options = { version = "0.1.0", path = "../rspack_binding_options" } rspack_binding_values = { version = "0.1.0", path = "../rspack_binding_values" } @@ -27,6 +28,7 @@ rspack_napi = { version = "0.1.0", path = "../rspack_napi" } rspack_paths = { version = "0.1.0", path = "../rspack_paths" } rspack_plugin_html = { version = "0.1.0", path = "../rspack_plugin_html" } rspack_plugin_javascript = { version = "0.1.0", path = "../rspack_plugin_javascript" } +rspack_util = { version = "0.1.0", path = "../rspack_util" } rspack_tracing = { version = "0.1.0", path = "../rspack_tracing" } tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 2f440ddad38..0ccb2f4fb35 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -99,8 +99,9 @@ export class JsCompilation { get chunkGroups(): Array get hash(): string | null dependencies(): DependenciesDto - pushDiagnostic(diagnostic: JsDiagnostic): void - spliceDiagnostic(start: number, end: number, replaceWith: Array): void + pushDiagnostic(diagnostic: JsRspackDiagnostic): void + spliceDiagnostic(start: number, end: number, replaceWith: Array): void + pushNativeDiagnostic(diagnostic: ExternalObject<'Diagnostic'>): void pushNativeDiagnostics(diagnostics: ExternalObject<'Diagnostic[]'>): void getErrors(): Array getWarnings(): Array @@ -281,6 +282,8 @@ export interface ContextInfo { issuer: string } +export function formatDiagnostic(diagnostic: JsDiagnostic): ExternalObject<'Diagnostic'> + export interface JsAdditionalTreeRuntimeRequirementsArg { chunk: JsChunk runtimeRequirements: JsRuntimeGlobals @@ -484,8 +487,23 @@ export interface JsCreateData { } export interface JsDiagnostic { - severity: JsRspackSeverity - error: JsRspackError + message: string + help?: string + sourceCode?: string + location?: JsDiagnosticLocation + file?: string + severity: "error" | "warning" + moduleIdentifier?: string +} + +export interface JsDiagnosticLocation { + text?: string + /** 1-based */ + line: number + /** 0-based in bytes */ + column: number + /** Length in bytes */ + length: number } export interface JsEntryData { @@ -689,6 +707,11 @@ export interface JsResourceData { fragment?: string } +export interface JsRspackDiagnostic { + severity: JsRspackSeverity + error: JsRspackError +} + export interface JsRspackError { name: string message: string diff --git a/crates/node_binding/src/diagnostic.rs b/crates/node_binding/src/diagnostic.rs new file mode 100644 index 00000000000..3112d77550b --- /dev/null +++ b/crates/node_binding/src/diagnostic.rs @@ -0,0 +1,83 @@ +use napi::bindgen_prelude::External; +use rspack_error::{ + miette::{self, LabeledSpan, MietteDiagnostic, Severity}, + Diagnostic, +}; +use rspack_util::location::{ + try_line_column_length_to_location, try_line_column_length_to_offset_length, +}; + +#[napi(object)] +pub struct JsDiagnosticLocation { + pub text: Option, + /// 1-based + pub line: u32, + /// 0-based in bytes + pub column: u32, + /// Length in bytes + pub length: u32, +} + +#[napi(object)] +pub struct JsDiagnostic { + pub message: String, + pub help: Option, + pub source_code: Option, + pub location: Option, + pub file: Option, + + #[napi(ts_type = "\"error\" | \"warning\"")] + pub severity: String, + pub module_identifier: Option, +} + +#[napi(ts_return_type = "ExternalObject<'Diagnostic'>")] +pub fn format_diagnostic(diagnostic: JsDiagnostic) -> External { + let JsDiagnostic { + message, + help, + source_code, + location, + severity, + module_identifier, + file, + } = diagnostic; + let mut d = MietteDiagnostic::new(message).with_severity(match severity.as_str() { + "warning" => Severity::Warning, + _ => Severity::Error, + }); + if let Some(help) = help { + d = d.with_help(help); + } + let mut loc = None; + if let Some(ref source_code) = source_code { + let rope = ropey::Rope::from_str(source_code); + if let Some(location) = location { + loc = try_line_column_length_to_location( + &rope, + location.line as usize, + location.column as usize, + location.length as usize, + ); + if let Some((offset, length)) = try_line_column_length_to_offset_length( + &rope, + location.line as usize, + location.column as usize, + location.length as usize, + ) { + d = d.with_label(LabeledSpan::new(location.text, offset, length)); + } + } + } + + let mut error = miette::Error::new(d); + if let Some(source_code) = source_code { + error = error.with_source_code(source_code); + } + External::new( + Diagnostic::from(error) + .with_file(file.map(Into::into)) + .with_loc(loc.map(|l| l.to_string())) + .with_module_identifier(module_identifier.map(Into::into)), + ) +} diff --git a/crates/node_binding/src/lib.rs b/crates/node_binding/src/lib.rs index 4afbc5021a0..63bd061b3ce 100644 --- a/crates/node_binding/src/lib.rs +++ b/crates/node_binding/src/lib.rs @@ -16,10 +16,12 @@ use rspack_error::Diagnostic; use rspack_fs_node::{AsyncNodeWritableFileSystem, ThreadsafeNodeFS}; mod compiler; +mod diagnostic; mod panic; mod plugins; mod resolver_factory; +pub use diagnostic::*; use plugins::*; use resolver_factory::*; use rspack_binding_options::*; diff --git a/crates/node_binding/test.js b/crates/node_binding/test.js new file mode 100644 index 00000000000..271ea980a32 --- /dev/null +++ b/crates/node_binding/test.js @@ -0,0 +1,21 @@ +const { formatDiagnostic } = require("./binding") + +const diagnostic = { + name: "ModuleError", + message: "failed to link", + severity: "error", + sourceCode: `abc; +def; +ghi;`, +help: "Try to fix it", + location: { + text: "abc", + line: 1, + column: 1, + length: 1, + }, + module_identifier: "test", + file: "test", +} + +formatDiagnostic(diagnostic) diff --git a/crates/rspack_binding_values/src/compilation/mod.rs b/crates/rspack_binding_values/src/compilation/mod.rs index ff77909593a..382079ba348 100644 --- a/crates/rspack_binding_values/src/compilation/mod.rs +++ b/crates/rspack_binding_values/src/compilation/mod.rs @@ -33,7 +33,7 @@ use crate::{ chunk::JsChunk, CompatSource, JsAsset, JsAssetInfo, JsChunkGroup, JsCompatSource, JsPathData, JsStats, ToJsCompatSource, }; -use crate::{JsDiagnostic, JsRspackError}; +use crate::{JsRspackDiagnostic, JsRspackError}; #[napi] pub struct JsCompilation(pub(crate) &'static mut rspack_core::Compilation); @@ -309,7 +309,7 @@ impl JsCompilation { } #[napi] - pub fn push_diagnostic(&mut self, diagnostic: JsDiagnostic) { + pub fn push_diagnostic(&mut self, diagnostic: JsRspackDiagnostic) { self.0.push_diagnostic(diagnostic.into()); } @@ -318,7 +318,7 @@ impl JsCompilation { &mut self, start: u32, end: u32, - replace_with: Vec, + replace_with: Vec, ) { let diagnostics = replace_with.into_iter().map(Into::into).collect(); self @@ -326,6 +326,11 @@ impl JsCompilation { .splice_diagnostic(start as usize, end as usize, diagnostics); } + #[napi(ts_args_type = r#"diagnostic: ExternalObject<'Diagnostic'>"#)] + pub fn push_native_diagnostic(&mut self, diagnostic: External) { + self.0.push_diagnostic(diagnostic.clone()); + } + #[napi(ts_args_type = r#"diagnostics: ExternalObject<'Diagnostic[]'>"#)] pub fn push_native_diagnostics(&mut self, mut diagnostics: External>) { while let Some(diagnostic) = diagnostics.pop() { diff --git a/crates/rspack_binding_values/src/rspack_error.rs b/crates/rspack_binding_values/src/rspack_error.rs index 679fb41a1a7..5e2b4f761c5 100644 --- a/crates/rspack_binding_values/src/rspack_error.rs +++ b/crates/rspack_binding_values/src/rspack_error.rs @@ -1,14 +1,14 @@ use napi_derive::napi; -use rspack_error::{Diagnostic, Result, RspackSeverity}; +use rspack_error::{miette, Diagnostic, Result, RspackSeverity}; #[napi(object)] -pub struct JsDiagnostic { +pub struct JsRspackDiagnostic { pub severity: JsRspackSeverity, pub error: JsRspackError, } -impl From for Diagnostic { - fn from(value: JsDiagnostic) -> Self { +impl From for Diagnostic { + fn from(value: JsRspackDiagnostic) -> Self { value.error.into_diagnostic(value.severity.into()) } } @@ -28,6 +28,15 @@ impl From for RspackSeverity { } } +impl From for miette::Severity { + fn from(value: JsRspackSeverity) -> Self { + match value { + JsRspackSeverity::Error => miette::Severity::Error, + JsRspackSeverity::Warn => miette::Severity::Warning, + } + } +} + #[napi(object)] #[derive(Debug)] pub struct JsRspackError { diff --git a/crates/rspack_plugin_javascript/Cargo.toml b/crates/rspack_plugin_javascript/Cargo.toml index e1f7743fcde..2e75014565d 100644 --- a/crates/rspack_plugin_javascript/Cargo.toml +++ b/crates/rspack_plugin_javascript/Cargo.toml @@ -19,7 +19,7 @@ num-bigint = { version = "0.4.4" } once_cell = { workspace = true } rayon = { workspace = true } regex = { workspace = true } -ropey = "1.6.1" +ropey = { workspace = true } rspack_ast = { version = "0.1.0", path = "../rspack_ast" } rspack_collections = { version = "0.1.0", path = "../rspack_collections" } rspack_core = { version = "0.1.0", path = "../rspack_core" } diff --git a/crates/rspack_plugin_javascript/src/webpack_comment.rs b/crates/rspack_plugin_javascript/src/webpack_comment.rs index e9453457510..30e9ff390c5 100644 --- a/crates/rspack_plugin_javascript/src/webpack_comment.rs +++ b/crates/rspack_plugin_javascript/src/webpack_comment.rs @@ -240,7 +240,7 @@ fn byte_offset_to_location(rope: &ropey::Rope, start: usize, end: usize) -> Loca /// Convert match item to error span within the source /// -/// # Panic +/// # Panics /// /// Panics if `comment_span` is out-of-bound of `source`. /// Panics if either `match_start` or `match_end` is out-of-bound of `comment_text`. diff --git a/crates/rspack_util/Cargo.toml b/crates/rspack_util/Cargo.toml index 59d1040fbfc..3f5d92c36f0 100644 --- a/crates/rspack_util/Cargo.toml +++ b/crates/rspack_util/Cargo.toml @@ -15,6 +15,7 @@ dashmap = { workspace = true } indexmap = { workspace = true } itoa = { version = "1.0.11" } regex = { workspace = true } +ropey = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/rspack_util/src/lib.rs b/crates/rspack_util/src/lib.rs index 007527f340f..59bc3b968f4 100644 --- a/crates/rspack_util/src/lib.rs +++ b/crates/rspack_util/src/lib.rs @@ -11,6 +11,7 @@ pub mod fx_hash; pub mod identifier; pub mod infallible; pub mod itoa; +pub mod location; pub mod number_hash; pub mod path; pub mod queue; diff --git a/crates/rspack_util/src/location.rs b/crates/rspack_util/src/location.rs new file mode 100644 index 00000000000..a6c57ecfc9f --- /dev/null +++ b/crates/rspack_util/src/location.rs @@ -0,0 +1,72 @@ +use std::fmt::Display; + +#[derive(Debug)] +pub struct Location { + /// Start line, 0-based + pub sl: u32, + /// Start column, 0-based + pub sc: u32, + /// End line, 0-based + pub el: u32, + /// End column, 0-based + pub ec: u32, +} + +impl Display for Location { + /// Print location in human readable format + /// + /// Lines are 1-based, columns are 0-based + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.sl == self.el { + if self.sc == self.ec { + return write!(f, "{}:{}", self.sl + 1, self.sc); + } + return write!(f, "{}:{}-{}", self.sl + 1, self.sc, self.ec); + } + write!(f, "{}:{}-{}:{}", self.sl + 1, self.sc, self.el + 1, self.ec) + } +} + +/// Convert line, column, length to a [`Location`] +/// +/// Line are 1-based, column are 0-based in bytes +/// +/// Return `None` if any value is out of bounds +pub fn try_line_column_length_to_location( + rope: &ropey::Rope, + line: usize, + column: usize, + length: usize, +) -> Option { + let sl = line.saturating_sub(1); + let sc = column; + + let sb = rope.try_line_to_byte(sl).ok()?; + let char_index = rope.try_byte_to_char(sb + sc + length).ok()?; + let el = rope.try_char_to_line(char_index).ok()?; + let ec = char_index - rope.try_line_to_char(el).ok()?; + + Some(Location { + sl: sl as u32, + sc: sc as u32, + el: el as u32, + ec: ec as u32, + }) +} + +/// Convert line, column, length to a (offset, length) +/// +/// Offset is 0-based in bytes +/// +/// Return `None` if any value is out of bounds +pub fn try_line_column_length_to_offset_length( + rope: &ropey::Rope, + line: usize, + column: usize, + length: usize, +) -> Option<(usize, usize)> { + let line = line.saturating_sub(1); + let sb = rope.try_line_to_byte(line).ok()?; + let offset = sb + column; + Some((offset, length)) +} diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/basic.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/basic.js new file mode 100644 index 00000000000..922837269d6 --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/basic.js @@ -0,0 +1,12 @@ +/** @type {import("@rspack/core").LoaderDefinition} */ +module.exports = function() { + this.experiments.emitDiagnostic({ + message: "`React` is not defined", + severity: "error", + }); + this.experiments.emitDiagnostic({ + message: "`React` is not defined", + severity: "warning", + }); + return "" +} diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/index.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/index.js new file mode 100644 index 00000000000..c99e08468f8 --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/index.js @@ -0,0 +1,12 @@ +require("!./basic.js!") +require("!./basic.js!./lib.js") + +require("!./with-file.js!") +require("!./with-file.js!./lib.js") + +require("!./with-help.js!") + +require("!./with-location.js!") + +require("!./with-multiple-line.js!") + diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/lib.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/lib.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/stats.err b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/stats.err new file mode 100644 index 00000000000..550f3e46e05 --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/stats.err @@ -0,0 +1,69 @@ +WARNING in (./basic.js!) + ⚠ ModuleWarning: `React` is not defined + +WARNING in ./lib.js (./basic.js!./lib.js) + ⚠ ModuleWarning: `React` is not defined + +ERROR in (./basic.js!) + × ModuleError: `React` is not defined + +ERROR in ./lib.js (./basic.js!./lib.js) + × ModuleError: `React` is not defined + +ERROR in ./some-file.js + (./with-file.js!) 1:1-4 + × ModuleError: `React` is not defined + ╭──── + 1 │
+ · ─── + ╰──── + +ERROR in ./some-file.js +./lib.js (./with-file.js!./lib.js) 1:1-4 + × ModuleError: `React` is not defined + ╭──── + 1 │
+ · ─── + ╰──── + +ERROR in (./with-help.js!) 1:1-4 + × ModuleError: `React` is not defined + ╭──── + 1 │
+ · ─── + ╰──── + help: try to import `React` + +ERROR in (./with-location.js!) 1:1-4 + × ModuleError: `React` is not defined + ╭──── + 1 │
+ · ─── + ╰──── + +ERROR in (./with-multiple-line.js!) 1:0-2:4 + × ModuleError: Multiple line error + ╭─[1:0] + 1 │ ╭─▶ ~~~~~ + 2 │ ╰─▶ ~~~~~ + ╰──── + +ERROR in (./with-multiple-line.js!) 1:0-2:4 + × ModuleError: Multiple line error + ╭─[1:0] + 1 │ ╭─▶ ~~~~~ + 2 │ ├─▶ ~~~~~ + · ╰──── unexpected '~' + ╰──── + +ERROR in (./with-multiple-line.js!) 3:4 + × ModuleError: Multiple line snippet + ╭─[3:4] + 1 │ ~~~~~ + 2 │ ~~~~~ + 3 │ ~~~~~ + · ▲ + · ╰── unexpected '~' + 4 │ ~~~~~ + 5 │ ~~~~~ + ╰──── \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-file.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-file.js new file mode 100644 index 00000000000..30ad8424475 --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-file.js @@ -0,0 +1,17 @@ +/** @type {import("@rspack/core").LoaderDefinition} */ +module.exports = function() { + this.experiments.emitDiagnostic({ + message: "`React` is not defined", + severity: "error", + sourceCode: `
`, + location: { + line: 1, + column: 1, + length: 3, + }, + file: "./some-file.js" + }); + return "" +} + + diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-help.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-help.js new file mode 100644 index 00000000000..e3bd1d0805e --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-help.js @@ -0,0 +1,16 @@ +/** @type {import("@rspack/core").LoaderDefinition} */ +module.exports = function() { + this.experiments.emitDiagnostic({ + message: "`React` is not defined", + severity: "error", + sourceCode: `
`, + help: "try to import `React`", + location: { + line: 1, + column: 1, + length: 3, + } + }); + return "" +} + diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-location.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-location.js new file mode 100644 index 00000000000..058bc9f051d --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-location.js @@ -0,0 +1,15 @@ +/** @type {import("@rspack/core").LoaderDefinition} */ +module.exports = function() { + this.experiments.emitDiagnostic({ + message: "`React` is not defined", + severity: "error", + sourceCode: `
`, + location: { + line: 1, + column: 1, + length: 3, + } + }); + return "" +} + diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-multiple-line.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-multiple-line.js new file mode 100644 index 00000000000..97044707e7f --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-diagnostic/with-multiple-line.js @@ -0,0 +1,42 @@ +/** @type {import("@rspack/core").LoaderDefinition} */ +module.exports = function() { + this.experiments.emitDiagnostic({ + message: "Multiple line error", + severity: "error", + sourceCode: `~~~~~ +~~~~~`, + location: { + line: 1, + column: 0, + length: 10, + }, + }); + this.experiments.emitDiagnostic({ + message: "Multiple line error", + severity: "error", + sourceCode: `~~~~~ +~~~~~`, + location: { + text: "unexpected '~'", + line: 1, + column: 0, + length: 10, + }, + }); + this.experiments.emitDiagnostic({ + message: "Multiple line snippet", + severity: "error", + sourceCode: `~~~~~ +~~~~~ +~~~~~ +~~~~~ +~~~~~`, + location: { + text: "unexpected '~'", + line: 3, + column: 4, + length: 0, + }, + }); + return "" +} diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index 1de9e310a5d..ea7dba00bf1 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -1033,9 +1033,11 @@ export class Compilation { // @internal __internal__hasAsset(name: string): boolean; // @internal - __internal__pushDiagnostic(diagnostic: binding.JsDiagnostic): void; + __internal__pushDiagnostic(diagnostic: ExternalObject<"Diagnostic">): void; // @internal - __internal__pushNativeDiagnostics(diagnostics: ExternalObject<"Diagnostic[]">): void; + __internal__pushDiagnostics(diagnostics: ExternalObject<"Diagnostic[]">): void; + // @internal + __internal__pushRspackDiagnostic(diagnostic: binding.JsRspackDiagnostic): void; // @internal __internal__setAssetSource(filename: string, source: Source): void; // @internal @@ -1859,6 +1861,31 @@ export type DevtoolNamespace = z.infer; // @public (undocumented) const devtoolNamespace: z.ZodString; +// @public (undocumented) +interface Diagnostic { + // (undocumented) + file?: string; + // (undocumented) + help?: string; + // (undocumented) + location?: DiagnosticLocation; + // (undocumented) + message: string; + // (undocumented) + severity: "error" | "warning"; + // (undocumented) + sourceCode?: string; +} + +// @public (undocumented) +interface DiagnosticLocation { + column: number; + length: number; + line: number; + // (undocumented) + text?: string; +} + // @public (undocumented) class DirectoryWatcher extends EventEmitter { constructor(directoryPath: string, options: Watchpack.WatcherOptions); @@ -5915,6 +5942,7 @@ export interface LoaderContext { emitFile(name: string, content: string | Buffer, sourceMap?: string, assetInfo?: JsAssetInfo): void; // (undocumented) emitWarning(warning: Error): void; + experiments: LoaderExperiments; // (undocumented) fs: any; // (undocumented) @@ -5987,6 +6015,12 @@ export type LoaderDefinition = LoaderDe // @public (undocumented) export type LoaderDefinitionFunction = (this: LoaderContext & ContextAdditions, content: string, sourceMap?: string | SourceMap, additionalData?: AdditionalData) => string | void | Buffer | Promise; +// @public (undocumented) +interface LoaderExperiments { + // (undocumented) + emitDiagnostic(diagnostic: Diagnostic): void; +} + // @public (undocumented) class LoaderObject { constructor(loaderItem: JsLoaderItem, compiler: Compiler); diff --git a/packages/rspack/src/Compilation.ts b/packages/rspack/src/Compilation.ts index 08a27291ad1..5dfea48cf23 100644 --- a/packages/rspack/src/Compilation.ts +++ b/packages/rspack/src/Compilation.ts @@ -29,7 +29,7 @@ import { cutOffLoaderExecution } from "./ErrorHelpers"; import { type CodeGenerationResult, Module } from "./Module"; import type { NormalModuleFactory } from "./NormalModuleFactory"; import type { ResolverFactory } from "./ResolverFactory"; -import { JsDiagnostic, type RspackError } from "./RspackError"; +import { JsRspackDiagnostic, type RspackError } from "./RspackError"; import { Stats, type StatsAsset, @@ -679,7 +679,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si * * @internal */ - __internal__pushDiagnostic(diagnostic: binding.JsDiagnostic) { + __internal__pushRspackDiagnostic(diagnostic: binding.JsRspackDiagnostic) { this.#inner.pushDiagnostic(diagnostic); } @@ -688,9 +688,16 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si * * @internal */ - __internal__pushNativeDiagnostics( - diagnostics: ExternalObject<"Diagnostic[]"> - ) { + __internal__pushDiagnostic(diagnostic: ExternalObject<"Diagnostic">) { + this.#inner.pushNativeDiagnostic(diagnostic); + } + + /** + * Note: This is not a webpack public API, maybe removed in future. + * + * @internal + */ + __internal__pushDiagnostics(diagnostics: ExternalObject<"Diagnostic[]">) { this.#inner.pushNativeDiagnostics(diagnostics); } @@ -709,7 +716,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si for (let i = 0; i < errs.length; i++) { const error = errs[i]; inner.pushDiagnostic( - JsDiagnostic.__to_binding(error, JsRspackSeverity.Error) + JsRspackDiagnostic.__to_binding(error, JsRspackSeverity.Error) ); } return Reflect.apply(target, thisArg, errs); @@ -740,7 +747,10 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si errs: ErrorType[] ) { const errList = errs.map(error => { - return JsDiagnostic.__to_binding(error, JsRspackSeverity.Error); + return JsRspackDiagnostic.__to_binding( + error, + JsRspackSeverity.Error + ); }); inner.spliceDiagnostic(0, 0, errList); return Reflect.apply(target, thisArg, errs); @@ -754,7 +764,10 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si [startIdx, delCount, ...errors]: [number, number, ...ErrorType[]] ) { const errList = errors.map(error => { - return JsDiagnostic.__to_binding(error, JsRspackSeverity.Error); + return JsRspackDiagnostic.__to_binding( + error, + JsRspackSeverity.Error + ); }); inner.spliceDiagnostic(startIdx, startIdx + delCount, errList); return Reflect.apply(target, thisArg, [ @@ -793,7 +806,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si thisArg, processWarningsHook.call(warns as any).map(warn => { inner.pushDiagnostic( - JsDiagnostic.__to_binding(warn, JsRspackSeverity.Warn) + JsRspackDiagnostic.__to_binding(warn, JsRspackSeverity.Warn) ); return warn; }) @@ -829,7 +842,10 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si 0, 0, warnings.map(warn => { - return JsDiagnostic.__to_binding(warn, JsRspackSeverity.Warn); + return JsRspackDiagnostic.__to_binding( + warn, + JsRspackSeverity.Warn + ); }) ); return Reflect.apply(target, thisArg, warnings); @@ -844,7 +860,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si ) { warns = processWarningsHook.call(warns as any); const warnList = warns.map(warn => { - return JsDiagnostic.__to_binding(warn, JsRspackSeverity.Warn); + return JsRspackDiagnostic.__to_binding(warn, JsRspackSeverity.Warn); }); inner.spliceDiagnostic(startIdx, startIdx + delCount, warnList); return Reflect.apply(target, thisArg, [ diff --git a/packages/rspack/src/RspackError.ts b/packages/rspack/src/RspackError.ts index 44caf207458..6fb5a96dfa7 100644 --- a/packages/rspack/src/RspackError.ts +++ b/packages/rspack/src/RspackError.ts @@ -3,11 +3,11 @@ import { concatErrorMsgAndStack } from "./util"; export type RspackError = binding.JsRspackError; -export class JsDiagnostic { +export class JsRspackDiagnostic { static __to_binding( error: Error | RspackError, severity: binding.JsRspackSeverity - ): binding.JsDiagnostic { + ): binding.JsRspackDiagnostic { return { error: concatErrorMsgAndStack(error), severity diff --git a/packages/rspack/src/config/adapterRuleUse.ts b/packages/rspack/src/config/adapterRuleUse.ts index dc03f3c9eb8..e8f5791f4ca 100644 --- a/packages/rspack/src/config/adapterRuleUse.ts +++ b/packages/rspack/src/config/adapterRuleUse.ts @@ -50,6 +50,35 @@ export interface AdditionalData { [index: string]: any; } +export interface DiagnosticLocation { + /** Text for highlighting the location */ + text?: string; + /** 1-based line */ + line: number; + /** 0-based column in bytes */ + column: number; + /** Length in bytes */ + length: number; +} + +export interface Diagnostic { + message: string; + help?: string; + sourceCode?: string; + /** + * Location to the source code. + * + * If `sourceCode` is not provided, location will be omitted. + */ + location?: DiagnosticLocation; + file?: string; + severity: "error" | "warning"; +} + +interface LoaderExperiments { + emitDiagnostic(diagnostic: Diagnostic): void; +} + export interface LoaderContext { version: 2; resource: string; @@ -139,6 +168,11 @@ export interface LoaderContext { callback: (err?: Error, res?: any) => void ): void; fs: any; + /** + * This is an experimental API and maybe subject to change. + * @experimental + */ + experiments: LoaderExperiments; utils: { absolutify: (context: string, request: string) => string; contextify: (context: string, request: string) => string; diff --git a/packages/rspack/src/loader-runner/index.ts b/packages/rspack/src/loader-runner/index.ts index ba1d5b019de..4eb6161a3d9 100644 --- a/packages/rspack/src/loader-runner/index.ts +++ b/packages/rspack/src/loader-runner/index.ts @@ -16,7 +16,8 @@ import { type JsLoaderContext, type JsLoaderItem, JsLoaderState, - JsRspackSeverity + JsRspackSeverity, + formatDiagnostic } from "@rspack/binding"; import { OriginalSource, @@ -32,6 +33,7 @@ import { NormalModule } from "../NormalModule"; import { NonErrorEmittedError, type RspackError } from "../RspackError"; import { BUILTIN_LOADER_PREFIX, + type Diagnostic, type LoaderContext, isUseSimpleSourceMap, isUseSourceMap @@ -624,7 +626,7 @@ export async function runLoaders( )})`; error = concatErrorMsgAndStack(error); (error as RspackError).moduleIdentifier = this._module.identifier(); - compiler._lastCompilation!.__internal__pushDiagnostic({ + compiler._lastCompilation!.__internal__pushRspackDiagnostic({ error, severity: JsRspackSeverity.Error }); @@ -640,7 +642,7 @@ export async function runLoaders( )})`; warning = concatErrorMsgAndStack(warning); (warning as RspackError).moduleIdentifier = this._module.identifier(); - compiler._lastCompilation!.__internal__pushDiagnostic({ + compiler._lastCompilation!.__internal__pushRspackDiagnostic({ error: warning, severity: JsRspackSeverity.Warn }); @@ -690,6 +692,20 @@ export async function runLoaders( ); }; loaderContext.fs = compiler.inputFileSystem; + loaderContext.experiments = { + emitDiagnostic: (diagnostic: Diagnostic) => { + const d = Object.assign({}, diagnostic, { + message: + diagnostic.severity === "warning" + ? `ModuleWarning: ${diagnostic.message}` + : `ModuleError: ${diagnostic.message}`, + moduleIdentifier: context._module.moduleIdentifier + }); + compiler._lastCompilation!.__internal__pushDiagnostic( + formatDiagnostic(d) + ); + } + }; const getAbsolutify = memoize(() => absolutify.bindCache(compiler.root)); const getAbsolutifyInContext = memoize(() => diff --git a/x.mjs b/x.mjs index 3a5b0a460aa..46859efb69d 100755 --- a/x.mjs +++ b/x.mjs @@ -203,11 +203,15 @@ const rspackCommand = program.command("rspack").alias("rs").description(` rspackCommand .option("-d, --debug", "Launch debugger in VSCode") .action(async ({ debug }) => { - if (!debug) { - await $`npx rspack ${getVariadicArgs()}`; - return; + try { + if (!debug) { + await $`npx rspack ${getVariadicArgs()}`; + return; + } + await launchRspackCli(getVariadicArgs()); + } catch (e) { + process.exit(e.exitCode); } - await launchRspackCli(getVariadicArgs()); }); // x rsd