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
25 changes: 3 additions & 22 deletions Cargo.lock

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

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ rayon = { version = "1.11.0", default-features = false }
regex = { version = "1.12.2", default-features = false }
regex-syntax = { version = "0.8.5", default-features = false, features = ["std"] }
regress = { version = "0.10.4", default-features = false, features = ["pattern"] }
ropey = { version = "1.6.1", default-features = false }
rspack_resolver = { features = ["package_json_raw_json_api", "yarn_pnp"], version = "0.6.6", default-features = false }
rspack_sources = { version = "=0.4.17", default-features = false }
rustc-hash = { version = "2.1.0", default-features = false }
Expand Down
1 change: 0 additions & 1 deletion crates/rspack_binding_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ tracy-client = ["dep:tracy-client", "rspack_allocator/tracy-client"]

[dependencies]
anyhow = { workspace = true }
ropey = { workspace = true }
rspack_allocator = { workspace = true }
rspack_browserslist = { workspace = true }
rspack_collections = { workspace = true }
Expand Down
18 changes: 9 additions & 9 deletions crates/rspack_binding_api/src/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use napi::bindgen_prelude::*;
use rspack_error::{Diagnostic, Error as RspackError, Label, Severity};
use rspack_util::location::{
try_line_column_length_to_location, try_line_column_length_to_offset_length,
v8_line_column_length_to_offset_length, v8_line_column_to_byte_location,
};

#[napi(object)]
Expand Down Expand Up @@ -49,16 +49,16 @@ pub fn format_diagnostic(diagnostic: JsDiagnostic) -> Result<External<Diagnostic
}
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,
loc = v8_line_column_to_byte_location(
&source_code,
location.line as usize,
location.column as usize,
location.length as usize,
);
let (offset, length) = try_line_column_length_to_offset_length(
&rope,
// TODO: Avoid redundant computation: location and (offset, length) are both derived from the same inputs.
let (offset, byte_length) = v8_line_column_length_to_offset_length(
&source_code,
location.line as usize,
location.column as usize,
location.length as usize,
Expand All @@ -69,8 +69,8 @@ pub fn format_diagnostic(diagnostic: JsDiagnostic) -> Result<External<Diagnostic
"Format diagnostic failed: Invalid location. Did you pass the correct line, column and length?",
)
})?;
let end_byte = offset.saturating_add(length);
if end_byte > rope.len_bytes() {
let end_byte = offset.saturating_add(byte_length);
if end_byte > source_code.len() {
return Err(Error::new(
Status::Unknown,
"Format diagnostic failed: Invalid `length` in location.",
Expand All @@ -85,7 +85,7 @@ pub fn format_diagnostic(diagnostic: JsDiagnostic) -> Result<External<Diagnostic
error.labels = Some(vec![Label {
name: location.text,
offset,
len: length,
len: byte_length,
}]);
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/rspack_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ indexmap = { workspace = true, features = ["rayon"] }
indoc = { workspace = true }
itertools = { workspace = true }
json = { workspace = true }
memchr = { workspace = true }
mime_guess = { workspace = true }
napi = { workspace = true, optional = true }
num-bigint = { workspace = true }
Expand All @@ -32,7 +33,6 @@ paste = { workspace = true }
rayon = { workspace = true }
regex = { workspace = true }
rkyv = { workspace = true, optional = true }
ropey = { workspace = true }
rspack_cacheable = { workspace = true }
rspack_collections = { workspace = true }
rspack_dojang = { workspace = true }
Expand Down
124 changes: 82 additions & 42 deletions crates/rspack_core/src/dependency/dependency_location.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::fmt::{self, Debug};
use std::{
fmt::{self, Debug},
sync::Arc,
};

use ropey::Rope;
use rspack_cacheable::cacheable;
use rspack_location::{DependencyLocation, RealDependencyLocation, SourcePosition};
use rspack_util::SpanExt;
Expand Down Expand Up @@ -63,57 +65,95 @@
}
}

impl SourceLocation for ropey::Rope {
fn look_up_range_pos(&self, start: u32, end: u32) -> Option<(SourcePosition, SourcePosition)> {
let start_char_offset = self.try_byte_to_char(start as usize).ok()?;
let end_char_offset = self.try_byte_to_char(end as usize).ok()?;

let start_line = self.char_to_line(start_char_offset);
let start_column = start_char_offset - self.line_to_char(start_line);
let end_line = self.char_to_line(end_char_offset);
let end_column = end_char_offset - self.line_to_char(end_line);

Some((
SourcePosition {
line: start_line + 1,
column: start_column + 1,
},
SourcePosition {
line: end_line + 1,
column: end_column,
},
))
}
}

impl SourceLocation for &str {
fn look_up_range_pos(&self, start: u32, end: u32) -> Option<(SourcePosition, SourcePosition)> {
let r = ropey::Rope::from_str(self);
r.look_up_range_pos(start, end)
let s = *self;
let len = s.len();
let start_idx = start as usize;
let end_idx = end as usize;

if start_idx > len || end_idx > len {
return None;
}

// Fast paths
if end_idx == 0 {
let p = SourcePosition { line: 1, column: 1 };
return Some((p, p));
}
if start_idx == end_idx {
// We still need line start to compute UTF-16 column. Fall through to single pass.
}

let bytes = s.as_bytes();

// Single pass over [..end_idx], tracking:
// - total lines up to end
// - last newline before start
// - last newline before end
let mut line_end = 1usize;
let mut last_nl_before_start: Option<usize> = None;
let mut last_nl_before_end: Option<usize> = None;

for idx in memchr::memchr_iter(b'\n', &bytes[..end_idx]) {
line_end += 1;
// update last newline before end
last_nl_before_end = Some(idx);
// update last newline before start only if newline is strictly before start_idx
if idx < start_idx {
last_nl_before_start = Some(idx);
}
}

// Derive line for start by counting newlines before start:
// It's line_end minus the number of newlines after start within [..end_idx]
// Simpler: count lines up to start in the same loop using last_nl_before_start.
let line_start = if start_idx == 0 {
1
} else {
// Count lines up to start: number of '\n' before start + 1
let mut count_lines_before_start = 1usize;
// If we need exact count, we can recompute cheaply with memchr on [..start_idx].
// But to ensure single pass, use the information we tracked:
// We don't have the count, so do a memchr over the shorter slice only when start < end.
// This is still <= one full scan of the string in worst case.
if start_idx > 0 {
count_lines_before_start = 1 + memchr::memchr_iter(b'\n', &bytes[..start_idx]).count();
}
count_lines_before_start
};

// Compute line start byte offsets
let start_line_start_byte = last_nl_before_start.map(|p| p + 1).unwrap_or(0);
let end_line_start_byte = last_nl_before_end.map(|p| p + 1).unwrap_or(0);

Check failure on line 128 in crates/rspack_core/src/dependency/dependency_location.rs

View workflow job for this annotation

GitHub Actions / Run Rust Tests / Rust check

called `map(<f>).unwrap_or(<a>)` on an `Option` value

// UTF-16 columns are 1-based
let start_seg = &s[start_line_start_byte..start_idx];
let end_seg = &s[end_line_start_byte..end_idx];

let start_col_utf16 = start_seg.encode_utf16().count() + 1;
let end_col_utf16 = end_seg.encode_utf16().count() + 1;

let start_pos = SourcePosition {
line: line_start,
column: start_col_utf16,
};
let end_pos = SourcePosition {
line: line_end,
column: end_col_utf16,
};

Some((start_pos, end_pos))
}
}

/// Type alias for a shared reference to a `SourceLocation` trait object, typically used for source maps.
pub type SharedSourceMap = Rope;
pub type SharedSourceMap = Arc<str>;

pub trait AsLoc {
fn as_loc(&self) -> &dyn SourceLocation;
}

impl AsLoc for Rope {
#[inline]
fn as_loc(&self) -> &dyn SourceLocation {
self
}
}

impl AsLoc for &Rope {
#[inline]
fn as_loc(&self) -> &dyn SourceLocation {
*self
}
}

impl AsLoc for &str {
#[inline]
fn as_loc(&self) -> &dyn SourceLocation {
Expand Down
8 changes: 2 additions & 6 deletions crates/rspack_core/src/resolver/resolver_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rspack_error::{Error, Severity, cyan, yellow};
use rspack_fs::ReadableFileSystem;
use rspack_loader_runner::DescriptionData;
use rspack_paths::AssertUtf8;
use rspack_util::location::try_line_column_length_to_offset_length;
use rspack_util::location::utf8_line_column_to_offset;
use rustc_hash::FxHashSet as HashSet;

use super::{ResolveResult, Resource, boxfs::BoxFS};
Expand Down Expand Up @@ -337,17 +337,13 @@ fn map_rspack_resolver_error(
rspack_resolver::ResolveError::NotFound(_) => map_resolver_error(false, args),
rspack_resolver::ResolveError::JSON(error) => {
if let Some(content) = &error.content {
let rope = ropey::Rope::from(&**content);
let Some((offset, _)) =
try_line_column_length_to_offset_length(&rope, error.line, error.column, 0)
else {
let Some(offset) = utf8_line_column_to_offset(content, error.line, error.column) else {
return rspack_error::error!(format!(
"JSON parse error: {:?} in '{}'",
error,
error.path.display()
));
};
drop(rope);

fn ceil_char_boundary(content: &str, mut index: usize) -> usize {
if index > content.len() {
Expand Down
5 changes: 4 additions & 1 deletion crates/rspack_location/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use std::fmt::{self, Debug};
pub use itoa::Buffer;
use rspack_cacheable::cacheable;

/// Represents a position in the source file, including the line number and column number.
/// Represents a position within a source file (line and column).
/// Semantics match V8 Error stack positions:
/// - Both line and column are 1-based.
/// - Column counts UTF-16 code units (not Unicode scalar values or UTF-8 bytes).
#[cacheable]
#[derive(Debug, Clone, Copy)]
pub struct SourcePosition {
Expand Down
2 changes: 1 addition & 1 deletion crates/rspack_plugin_javascript/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ fast-glob = { workspace = true }
indexmap = { workspace = true }
itertools = { workspace = true }
linked_hash_set = { workspace = true }
memchr = { workspace = true }
num-bigint = { workspace = true }
once_cell = { workspace = true }
rayon = { workspace = true }
regex = { workspace = true }
regress = { workspace = true, features = ["pattern"] }
ropey = { workspace = true }
rspack_cacheable = { workspace = true }
rspack_collections = { workspace = true }
rspack_core = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl CommonJsFullRequireDependency {
asi_safe: bool,
source_map: Option<SharedSourceMap>,
) -> Self {
let loc = range.to_loc(source_map.as_ref());
let loc = range.to_loc(source_map.as_deref());
Self {
id: DependencyId::new(),
request,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl CommonJsRequireDependency {
optional: bool,
source_map: Option<SharedSourceMap>,
) -> Self {
let loc = range.to_loc(source_map.as_ref());
let loc = range.to_loc(source_map.as_deref());
Self {
id: DependencyId::new(),
request,
Expand Down
Loading
Loading