Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,4 @@
- Sync upstream nightly through `d4928764f23213ecf3cd61fa0b5b4a44369a5096`, configuring fetch TLS once per connection so pooled sockets can be reused during renegotiation without repeating session setup.
- Sync upstream nightly through `9bd19c98eacc01530a4e7609bc427abffa87d77e`, preserving PostgreSQL query ordering through errors and limiting MySQL row-decoding failures to the affected query.
- Support real byte-based `node:inspector` HeapProfiler allocation sampling with allocation-site trees, live profiles, and major/minor GC inclusion flags (requires the OpenClaw WebKit allocation sampler).
- Preserve built-in error constructor syntax and returning arrow frames, and use JSC syntax-selected call and property-read stack positions. Retain runtime callee parentheses and computed access, and map call, bracket, and template delimiters back to their original source. Continues the source-position fixes from [oven-sh/bun#35179](https://github.com/oven-sh/bun/pull/35179), [oven-sh/bun#37396](https://github.com/oven-sh/bun/pull/37396), and [oven-sh/bun#41580](https://github.com/oven-sh/bun/pull/41580). Thanks @robobun!
2 changes: 1 addition & 1 deletion docs/runtime/nodejs-compat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Module namespaces have a null prototype. `require(esm)` adds an own enumerable `

`Error.prepareStackTrace` supports deletion, descriptor restoration, and accessor replacement. Nested stack formatting and throwing formatters leave later captures usable.

`CallSite.getLineNumber()` and `getColumnNumber()` return one-based positions, or `null` when a frame has no source position. Stack strings include column 1 and use the same constructor positions as CallSites, pointing to the `new` keyword, including across line breaks and in `node:vm` scripts. Some expression positions still differ from Node.js because JavaScriptCore and Bun's transpiler select different syntax locations.
`CallSite.getLineNumber()` and `getColumnNumber()` return one-based positions, or `null` when a frame has no source position. Stack strings include column 1 and use the same constructor positions as CallSites, pointing to the `new` keyword, including across line breaks and in `node:vm` scripts. The matching OpenClaw engine supplies syntax-selected positions for calls, constructors, property reads, and async continuations. Built-in error constructors retain `new`, including in returning arrows. Runtime transpilation retains callee parentheses and computed access and maps opening call, bracket, and template delimiters to the original source. File-name and eval-origin formatting still follow Bun's existing conventions.

`Intl.Segments.containing()` respects surrogate-pair boundaries for grapheme, word, and sentence segmentation with the pinned OpenClaw WebKit.

Expand Down
7 changes: 7 additions & 0 deletions src/ast/e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,9 @@ pub struct Call {
pub args: ExprNodeList,
pub optional_chain: Option<OptionalChain>,
pub is_direct_eval: bool,
pub open_paren_loc: crate::Loc,
pub close_paren_loc: crate::Loc,
pub preserve_parenthesized_target: bool,

/// True if there is a comment containing "@__PURE__" or "#__PURE__" preceding
/// this call expression. This is an annotation used for tree shaking, and
Expand All @@ -281,7 +283,9 @@ impl Default for Call {
args: bun_alloc::AstAlloc::vec(),
optional_chain: None,
is_direct_eval: false,
open_paren_loc: crate::Loc::EMPTY,
close_paren_loc: crate::Loc::EMPTY,
preserve_parenthesized_target: false,
can_be_unwrapped_if_unused: CallUnwrap::Never,
was_jsx_element: false,
}
Expand Down Expand Up @@ -334,6 +338,7 @@ impl Default for Dot {
}
}
pub struct Index {
pub open_bracket_loc: crate::Loc,
pub index: ExprNodeIndex,
pub target: ExprNodeIndex,
pub optional_chain: Option<OptionalChain>,
Expand Down Expand Up @@ -2114,6 +2119,7 @@ pub struct TemplatePart {
}

pub struct Template {
pub head_loc: crate::Loc,
pub tag: Option<ExprNodeIndex>,
/// Arena-owned mutable slice. Stored as a
/// `StoreSlice` so writers (`substitute_single_use_symbol_in_expr`, the
Expand Down Expand Up @@ -2335,6 +2341,7 @@ impl Template {
// provenance for downstream mutators.
Expr::init(
Template {
head_loc: crate::Loc::EMPTY,
tag: None,
parts: crate::StoreSlice::from_bump(parts),
head: TemplateContents::Cooked(
Expand Down
4 changes: 4 additions & 0 deletions src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2000,7 +2000,9 @@ impl Data {
.try_deep_clone_with(|e| e.deep_clone_no_detach(bump))?,
optional_chain: el.optional_chain,
is_direct_eval: el.is_direct_eval,
open_paren_loc: el.open_paren_loc,
close_paren_loc: el.close_paren_loc,
preserve_parenthesized_target: el.preserve_parenthesized_target,
can_be_unwrapped_if_unused: el.can_be_unwrapped_if_unused,
was_jsx_element: el.was_jsx_element,
});
Expand All @@ -2020,6 +2022,7 @@ impl Data {
}
Data::EIndex(el) => {
let item = bump.alloc(E::Index {
open_bracket_loc: el.open_bracket_loc,
target: el.target.deep_clone_no_detach(bump)?,
index: el.index.deep_clone_no_detach(bump)?,
optional_chain: el.optional_chain,
Expand Down Expand Up @@ -2080,6 +2083,7 @@ impl Data {
}
Data::ETemplate(el) => {
let item = bump.alloc(E::Template {
head_loc: el.head_loc,
tag: match &el.tag {
Some(tag) => Some(tag.deep_clone_no_detach(bump)?),
None => None,
Expand Down
1 change: 1 addition & 0 deletions src/ast/fold_string_addition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ pub fn fold_string_addition(
if right.head.is_utf8() {
return Some(Expr::init(
E::Template {
head_loc: crate::Loc::EMPTY,
tag: None,
parts: right.parts,
head: e::TemplateContents::Cooked(join_strings(
Expand Down
7 changes: 2 additions & 5 deletions src/ast/known_global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,15 @@ impl KnownGlobal {
let constructor = lookup(original_name)?;

match constructor {
// Error constructors can be called without 'new' with identical behavior
// Removing `new` changes stack positions and makes returned errors eligible for tail calls.
KnownGlobal::Error
| KnownGlobal::TypeError
| KnownGlobal::SyntaxError
| KnownGlobal::RangeError
| KnownGlobal::ReferenceError
| KnownGlobal::EvalError
| KnownGlobal::URIError
| KnownGlobal::AggregateError => {
// Convert `new Error(...)` to `Error(...)` to save bytes
Some(Self::call_from_new(e, loc))
}
| KnownGlobal::AggregateError => None,

KnownGlobal::Object => {
let n = e.args.len_u32();
Expand Down
1 change: 1 addition & 0 deletions src/bundler/linker_context/generateCodeForLazyExport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ pub(crate) fn generate_code_for_lazy_export(
bun_ast::StoreSlice::new_mut(arena.alloc_slice_fill_iter(template_parts));
value = Expr::init(
E::Template {
head_loc: bun_ast::Loc::EMPTY,
tag: None,
parts: parts_slice,
head: E::TemplateContents::Cooked(E::String::init(b"")),
Expand Down
2 changes: 2 additions & 0 deletions src/js_parser/lower/lower_decorators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let this = p.new_expr(E::This {}, loc);
let member = p.new_expr(
E::Index {
open_bracket_loc: bun_ast::Loc::EMPTY,
target: this,
index: key,
optional_chain: None,
Expand Down Expand Up @@ -1438,6 +1439,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
);
self.new_expr(
E::Index {
open_bracket_loc: bun_ast::Loc::EMPTY,
target: object,
index: key,
optional_chain: None,
Expand Down
20 changes: 20 additions & 0 deletions src/js_parser/p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ pub(crate) struct ParserSnapshot<'a> {
should_fold_typescript_constant_expressions: bool,
fn_or_arrow_data_parse: FnOrArrowDataParse,
latest_arrow_arg_loc: bun_ast::Loc,
parenthesized_target_loc: bun_ast::Loc,
parenthesized_suffix_loc: bun_ast::Loc,
forbid_suffix_after_as_loc: bun_ast::Loc,
after_arrow_body_loc: bun_ast::Loc,
esm_import_keyword: bun_ast::Range,
Expand Down Expand Up @@ -271,6 +273,8 @@ pub struct P<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> {
// allocated_names: ListManaged(string) = ListManaged(string).init(bun.default_allocator),
// allocated_names_pool: ?*AllocatedNamesPool.Node = null,
pub(crate) latest_arrow_arg_loc: bun_ast::Loc,
pub(crate) parenthesized_target_loc: bun_ast::Loc,
pub(crate) parenthesized_suffix_loc: bun_ast::Loc,
pub(crate) forbid_suffix_after_as_loc: bun_ast::Loc,
pub(crate) current_scope: js_ast::StoreRef<js_ast::Scope>,
pub(crate) scopes_for_current_part: List<'a, *mut js_ast::Scope>,
Expand Down Expand Up @@ -5486,7 +5490,16 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
});
}

pub(crate) fn preserve_parenthesized_call_target(&self, expr: Expr) -> bool {
self.options.features.dont_bundle_twice
&& self.parenthesized_target_loc.eql(expr.loc)
&& self.parenthesized_suffix_loc.eql(self.lexer.loc())
}

pub(crate) fn mark_expr_as_parenthesized(&mut self, expr: &mut Expr) {
// Runtime loads retain the callee parentheses that select JSC's call position.
self.parenthesized_target_loc = expr.loc;
self.parenthesized_suffix_loc = self.lexer.loc();
match &mut expr.data {
js_ast::ExprData::EArray(ex) => ex.is_parenthesized = true,
js_ast::ExprData::EObject(ex) => ex.is_parenthesized = true,
Expand Down Expand Up @@ -7564,6 +7577,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}
_ => self.new_expr(
E::Index {
open_bracket_loc: bun_ast::Loc::EMPTY,
target,
index: key,
optional_chain: None,
Expand Down Expand Up @@ -8351,6 +8365,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
.should_fold_typescript_constant_expressions,
fn_or_arrow_data_parse: self.fn_or_arrow_data_parse.clone(),
latest_arrow_arg_loc: self.latest_arrow_arg_loc,
parenthesized_target_loc: self.parenthesized_target_loc,
parenthesized_suffix_loc: self.parenthesized_suffix_loc,
forbid_suffix_after_as_loc: self.forbid_suffix_after_as_loc,
after_arrow_body_loc: self.after_arrow_body_loc,
esm_import_keyword: self.esm_import_keyword,
Expand Down Expand Up @@ -8385,6 +8401,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
snapshot.should_fold_typescript_constant_expressions;
self.fn_or_arrow_data_parse = snapshot.fn_or_arrow_data_parse;
self.latest_arrow_arg_loc = snapshot.latest_arrow_arg_loc;
self.parenthesized_target_loc = snapshot.parenthesized_target_loc;
self.parenthesized_suffix_loc = snapshot.parenthesized_suffix_loc;
self.forbid_suffix_after_as_loc = snapshot.forbid_suffix_after_as_loc;
self.after_arrow_body_loc = snapshot.after_arrow_body_loc;
self.esm_import_keyword = snapshot.esm_import_keyword;
Expand Down Expand Up @@ -9841,6 +9859,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
fn_only_data_visit: FnOnlyDataVisit::default(),
allocated_names: BumpVec::new_in(arena),
latest_arrow_arg_loc: bun_ast::Loc::EMPTY,
parenthesized_target_loc: bun_ast::Loc::EMPTY,
parenthesized_suffix_loc: bun_ast::Loc::EMPTY,
forbid_suffix_after_as_loc: bun_ast::Loc::EMPTY,
scopes_for_current_part: BumpVec::new_in(arena),
symbols: BumpVec::with_capacity_in(estimated_symbol_count, arena),
Expand Down
3 changes: 2 additions & 1 deletion src/js_parser/parse/parse_prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
p.allow_in = true;

let mut value = p.parse_expr(Level::Lowest)?;
p.mark_expr_as_parenthesized(&mut value);
p.lexer.expect(T::TCloseParen)?;
p.mark_expr_as_parenthesized(&mut value);

p.allow_in = old_allow_in;
return Ok(value);
Expand Down Expand Up @@ -295,6 +295,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O

Ok(p.new_expr(
E::Template {
head_loc: loc,
tag: None,
head: E::TemplateContents::Cooked(head),
parts,
Expand Down
Loading
Loading