Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

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

13 changes: 7 additions & 6 deletions src/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ let joined = resolve_path::join_string_buf::<platform::Auto>(&mut *buf, &[a, b]
`bun_paths::os_path_buffer_pool` selects the wide (`u16`) variant on Windows
and the narrow (`u8`) variant on POSIX.

## URL Parsing (`bun_jsc::URL`)
## URL Parsing (`bun_url::whatwg::URL`, re-exported as `bun_jsc::URL`)

WHATWG-compliant, backed by WebKit's URL parser. Returns `None` for invalid input.

Expand All @@ -169,14 +169,15 @@ let url = URL::from_utf8(href)?; // Option<NonNull<URL>>
url.protocol() // bun_core::String
url.pathname() // bun_core::String
url.host() // bun_core::String — the hostname WITHOUT the port (opposite of JS `host`!)
url.hostname() // bun_core::String — the host WITH the port (opposite of JS `hostname`!)
url.port() // u32 (u32::MAX = unset; otherwise u16 range)
```

`URL::href_from_js`, `URL::file_url_from_string`, `URL::path_from_file_url`
do whole-string conversions. The JSC-free shim `bun_url::whatwg::URL` exposes
`hostname()`, which returns the host WITH the port (also the opposite of JS
`hostname`) — so `bun_jsc::URL::host` and `bun_url::whatwg::URL::hostname`
are effectively swapped relative to their JS namesakes.
`URL::file_url_from_string` / `URL::path_from_file_url` (and the free
`bun_url::href_from_string` / `bun_url::join`) do whole-string conversions and
are usable from JSC-free crates. The entry points that take a `JSValue`
(`href_from_js`, `from_js`) live on the `bun_jsc::UrlJsc` extension trait, so
callers need `use bun_jsc::UrlJsc as _;`.

## MIME Types (`bun_http_types::MimeType`)

Expand Down
4 changes: 1 addition & 3 deletions src/js_parser_jsc/Macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,9 +584,7 @@ impl<'a> Run<'a> {

pub(crate) fn run(&mut self, value: JSValue) -> Result<Expr, MacroError> {
use ConsoleObject::formatter::Tag as T;
// `Tag::get` returns `TagResult { tag: TagPayload, .. }`;
// collapse the payload to its discriminant via `.tag()`.
match T::get(value, self.global)?.tag.tag() {
match T::get(value, self.global)?.tag {
T::Error => self.coerce(T::Error, value),
T::Undefined => self.coerce(T::Undefined, value),
T::Null => self.coerce(T::Null, value),
Expand Down
285 changes: 123 additions & 162 deletions src/jsc/AsyncModule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,116 @@ impl AsyncModule {
});
}

/// Shared builder for the package resolve/download error objects: creates
/// the error instance from `msg` and sets the `url` (when present),
/// `name`, and `pkg` properties.
fn package_error_instance(
global_this: &JSGlobalObject,
msg: &[u8],
name: &[u8],
url: &[u8],
pkg: &[u8],
) -> JSValue {
let error_instance = ZigString::from_bytes(msg)
.with_encoding()
.to_error_instance(global_this);
if !url.is_empty() {
error_instance.put(
global_this,
b"url",
ZigString::from_bytes(url)
.with_encoding()
.to_js(global_this),
);
}
error_instance.put(
global_this,
b"name",
ZigString::from_bytes(name)
.with_encoding()
.to_js(global_this),
);
error_instance.put(
global_this,
b"pkg",
ZigString::from_bytes(pkg)
.with_encoding()
.to_js(global_this),
);
error_instance
}

fn put_referrer(global_this: &JSGlobalObject, error_instance: JSValue, referrer: &[u8]) {
if !referrer.is_empty() && referrer != b"undefined" {
error_instance.put(
global_this,
b"referrer",
ZigString::from_bytes(referrer)
.with_encoding()
.to_js(global_this),
);
}
}

/// Sets `sourceURL`/`line`/`lineText`/`column` from the import record's
/// source location.
fn put_import_location(
&self,
global_this: &JSGlobalObject,
error_instance: JSValue,
import_record_id: u32,
) {
let location = bun_ast::range_data(
Some(&self.parse_result.source),
self.parse_result.ast.import_records[import_record_id as usize].range,
b"",
)
.location
.unwrap();
error_instance.put(
global_this,
b"sourceURL",
ZigString::from_bytes(self.parse_result.source.path.text)
.with_encoding()
.to_js(global_this),
);
error_instance.put(
global_this,
b"line",
JSValue::js_number(location.line as f64),
);
if let Some(line_text) = location.line_text.as_deref() {
error_instance.put(
global_this,
b"lineText",
ZigString::from_bytes(line_text)
.with_encoding()
.to_js(global_this),
);
}
error_instance.put(
global_this,
b"column",
JSValue::js_number(location.column as f64),
);
}

/// Rejects the module's promise with `error_instance` and drops the event
/// loop keepalive. The caller (`Queue::retain_mut`) returns `false` and
/// Vec drops the element, running Drop.
fn reject_with(&mut self, global_this: &JSGlobalObject, error_instance: JSValue) {
let promise_value = self.promise.swap();
let promise = promise_value.as_internal_promise().unwrap();
promise_value.ensure_still_alive();
self.poll_ref.unref(bun_io::posix_event_loop::get_vm_ctx(
bun_io::AllocatorType::Js,
));
// `JSInternalPromise` is an `opaque_ffi!` ZST handle; `opaque_mut` is
// the centralised non-null deref proof.
let _ =
JSInternalPromise::opaque_mut(promise).reject_as_handled(global_this, error_instance);
}

// write! into Vec<u8>
// is infallible here; `.ok()` collapses the `fmt::Result`, so this never
// actually returns Err — the wide Result is kept for call-site uniformity.
Expand Down Expand Up @@ -863,96 +973,20 @@ impl AsyncModule {
_ => b"PackageResolveError",
};

let error_instance = ZigString::from_bytes(&msg)
.with_encoding()
.to_error_instance(global_this);
if !result.url.is_empty() {
error_instance.put(
global_this,
b"url",
ZigString::from_bytes(result.url)
.with_encoding()
.to_js(global_this),
);
}
error_instance.put(
global_this,
b"name",
ZigString::from_bytes(name)
.with_encoding()
.to_js(global_this),
);
error_instance.put(
global_this,
b"pkg",
ZigString::from_bytes(result.name)
.with_encoding()
.to_js(global_this),
);
let error_instance =
Self::package_error_instance(global_this, &msg, name, result.url, result.name);
error_instance.put(
global_this,
b"specifier",
ZigString::from_bytes(self.specifier())
.with_encoding()
.to_js(global_this),
);
let location = bun_ast::range_data(
Some(&self.parse_result.source),
self.parse_result.ast.import_records[import_record_id as usize].range,
b"",
)
.location
.unwrap();
error_instance.put(
global_this,
b"sourceURL",
ZigString::from_bytes(self.parse_result.source.path.text)
.with_encoding()
.to_js(global_this),
);
error_instance.put(
global_this,
b"line",
JSValue::js_number(location.line as f64),
);
if let Some(line_text) = location.line_text.as_deref() {
error_instance.put(
global_this,
b"lineText",
ZigString::from_bytes(line_text)
.with_encoding()
.to_js(global_this),
);
}
error_instance.put(
global_this,
b"column",
JSValue::js_number(location.column as f64),
);
let referrer = self.referrer();
if !referrer.is_empty() && referrer != b"undefined" {
error_instance.put(
global_this,
b"referrer",
ZigString::from_bytes(referrer)
.with_encoding()
.to_js(global_this),
);
}
self.put_import_location(global_this, error_instance, import_record_id);
Self::put_referrer(global_this, error_instance, self.referrer());

let promise_value = self.promise.swap();
let promise = promise_value.as_internal_promise().unwrap();
promise_value.ensure_still_alive();
let _ = vm;
self.poll_ref.unref(bun_io::posix_event_loop::get_vm_ctx(
bun_io::AllocatorType::Js,
));
// The caller (Queue::retain_mut) returns `false` and Vec drops the
// element, running Drop.
// `JSInternalPromise` is an `opaque_ffi!` ZST handle; `opaque_mut` is
// the centralised non-null deref proof.
let _ =
JSInternalPromise::opaque_mut(promise).reject_as_handled(global_this, error_instance);
self.reject_with(global_this, error_instance);
Ok(())
}

Expand Down Expand Up @@ -1075,50 +1109,12 @@ impl AsyncModule {
_ => b"TarballDownloadError",
};

let error_instance = ZigString::from_bytes(&msg)
.with_encoding()
.to_error_instance(global_this);
if !result.url.is_empty() {
error_instance.put(
global_this,
b"url",
ZigString::from_bytes(result.url)
.with_encoding()
.to_js(global_this),
);
}
error_instance.put(
global_this,
b"name",
ZigString::from_bytes(name)
.with_encoding()
.to_js(global_this),
);
error_instance.put(
global_this,
b"pkg",
ZigString::from_bytes(result.name)
.with_encoding()
.to_js(global_this),
);
let specifier = self.specifier();
if !specifier.is_empty() && specifier != b"undefined" {
error_instance.put(
global_this,
b"referrer",
ZigString::from_bytes(specifier)
.with_encoding()
.to_js(global_this),
);
}

let location = bun_ast::range_data(
Some(&self.parse_result.source),
self.parse_result.ast.import_records[import_record_id as usize].range,
b"",
)
.location
.unwrap();
let error_instance =
Self::package_error_instance(global_this, &msg, name, result.url, result.name);
Self::put_referrer(global_this, error_instance, self.specifier());
// `sourceURL` et al. follow `specifier` here (the resolve-error path
// puts `specifier` first), so the helper runs after this put; the
// location computation itself is pure.
error_instance.put(
global_this,
b"specifier",
Expand All @@ -1130,45 +1126,10 @@ impl AsyncModule {
.with_encoding()
.to_js(global_this),
);
error_instance.put(
global_this,
b"sourceURL",
ZigString::from_bytes(self.parse_result.source.path.text)
.with_encoding()
.to_js(global_this),
);
error_instance.put(
global_this,
b"line",
JSValue::js_number(location.line as f64),
);
if let Some(line_text) = location.line_text.as_deref() {
error_instance.put(
global_this,
b"lineText",
ZigString::from_bytes(line_text)
.with_encoding()
.to_js(global_this),
);
}
error_instance.put(
global_this,
b"column",
JSValue::js_number(location.column as f64),
);
self.put_import_location(global_this, error_instance, import_record_id);

let promise_value = self.promise.swap();
let promise = promise_value.as_internal_promise().unwrap();
promise_value.ensure_still_alive();
let _ = vm;
self.poll_ref.unref(bun_io::posix_event_loop::get_vm_ctx(
bun_io::AllocatorType::Js,
));
// Caller drops via retain_mut → false.
// `JSInternalPromise` is an `opaque_ffi!` ZST handle; `opaque_mut` is
// the centralised non-null deref proof.
let _ =
JSInternalPromise::opaque_mut(promise).reject_as_handled(global_this, error_instance);
self.reject_with(global_this, error_instance);
Ok(())
}

Expand Down
Loading