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
4 changes: 1 addition & 3 deletions src/ast/e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1481,9 +1481,7 @@ impl Object {
}
}

/// Data-file parsers (JSON/JSON5/TOML/YAML) define every key as an own
/// property, so an own `"__proto__"` string key must be marked computed:
/// a plain `"__proto__":` key in a printed object literal sets the prototype.
/// A synthesized `"__proto__"` key is computed: a plain `__proto__:` would set the prototype.
pub fn own_key_property_flags(key: &Expr) -> crate::flags::PropertySet {
match &key.data {
crate::expr::Data::EString(key_str) if key_str.eql_comptime(b"__proto__") => {
Expand Down
8 changes: 4 additions & 4 deletions src/bundler/AstBuilder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,11 +369,11 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> {
self.symbols[ref_.inner_index() as usize].original_name;
// ImportScanner.recordExportedBinding → recordExport
self.record_export(binding.loc, original_name.slice(), ref_)?;
let key =
Expr::init(E::String::init(original_name.slice()), binding.loc);
export_props.push(G::Property {
key: Some(Expr::init(
E::String::init(original_name.slice()),
binding.loc,
)),
flags: E::own_key_property_flags(&key),
key: Some(key),
value: Some(Expr::init_identifier(ref_, binding.loc)),
..Default::default()
});
Expand Down
1 change: 1 addition & 0 deletions src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3638,6 +3638,7 @@ pub mod bv2_impl {
..Default::default()
});
*client_item = G::Property {
flags: E::own_key_property_flags(&export_name),
key: Some(export_name),
value: Some(server.new_expr(E::Object {
properties: bun_ast::g::PropertyList::from_owned_slice(Box::new([
Expand Down
9 changes: 7 additions & 2 deletions src/bundler/linker_context/doStep5.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ impl LinkerContext<'_> {
stmts: stmts_eat1!(Stmt::allocate(arena, S::Return { value: Some(value) }, loc,)),
loc,
};
properties.push(G::Property {
let mut property = G::Property {
key: Some(Expr::allocate(
arena,
// TODO: test emoji work as expected (relevant for WASM exports)
Expand All @@ -577,7 +577,11 @@ impl LinkerContext<'_> {
loc,
)),
..Default::default()
});
};
let key_flags =
E::own_key_property_flags(property.key.as_ref().expect("infallible: prop has key"));
property.flags = key_flags;
properties.push(property);
if lifted_setter_param.is_valid()
&& exp_data.source_index.get() == id
&& self
Expand Down Expand Up @@ -609,6 +613,7 @@ impl LinkerContext<'_> {
..Default::default()
});
setter_properties.push(G::Property {
flags: key_flags,
key: Some(Expr::allocate(
arena,
// SAFETY: as for the getter key above.
Expand Down
3 changes: 3 additions & 0 deletions src/bundler/linker_context/generateCodeForFileInChunkJS.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,8 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>(
// empty" invariant makes that drop a no-op today, but
// `ptr::write` enforces it structurally.
let key = prop.key;
// keeps a "__proto__" key computed
let flags = prop.flags;
let value_loc =
prop.value.as_ref().expect("infallible: prop has value").loc;
// SAFETY: `prop` is a valid `&mut G::Property` slot;
Expand All @@ -470,6 +472,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>(
prop,
G::Property {
key,
flags,
value: Some(Expr::init_identifier(export_ref, value_loc)),
..Default::default()
},
Expand Down
54 changes: 31 additions & 23 deletions src/js_parser/lower/lower_esm_exports_hmr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,14 @@ impl<'a> ConvertESMExportsForHmr<'a> {
// if the symbol is not used, we don't need to preserve
// a binding in this scope. we can move it to the exports object.
if symbol.use_count_estimate == 0 && value.can_be_moved() {
let key = Expr::init(
// SAFETY: arena-owned name slice valid for the parse.
E::EString::init(symbol.original_name.slice()),
binding.loc,
);
self.export_props.push(G::Property {
key: Some(Expr::init(
// SAFETY: arena-owned name slice valid for the parse.
E::EString::init(symbol.original_name.slice()),
binding.loc,
)),
flags: E::own_key_property_flags(&key),
key: Some(key),
value: Some(value),
..Default::default()
});
Expand Down Expand Up @@ -257,16 +259,18 @@ impl<'a> ConvertESMExportsForHmr<'a> {

let class_name_ref = st.class.class_name.unwrap().ref_;
// Export as CommonJS
let key = Expr::init(
// SAFETY: arena-owned name slice valid for the parse.
E::EString::init(
p.symbols[class_name_ref.inner_index() as usize]
.original_name
.slice(),
),
stmt.loc,
);
self.export_props.push(G::Property {
key: Some(Expr::init(
// SAFETY: arena-owned name slice valid for the parse.
E::EString::init(
p.symbols[class_name_ref.inner_index() as usize]
.original_name
.slice(),
),
stmt.loc,
)),
flags: E::own_key_property_flags(&key),
key: Some(key),
value: Some(Expr::init_identifier(class_name_ref, stmt.loc)),
..Default::default()
});
Expand Down Expand Up @@ -350,12 +354,14 @@ impl<'a> ConvertESMExportsForHmr<'a> {

if let Some(alias) = &st.alias {
// 'export * as ns from' creates one named property.
self.export_props.push(G::Property {
let key = Expr::init(
// SAFETY: arena-owned name slice valid for the parse.
key: Some(Expr::init(
E::EString::init(alias.original_name.slice()),
stmt.loc,
)),
E::EString::init(alias.original_name.slice()),
stmt.loc,
);
self.export_props.push(G::Property {
flags: E::own_key_property_flags(&key),
key: Some(key),
value: Some(Expr::init_identifier(deduped.namespace_ref, stmt.loc)),
..Default::default()
});
Expand Down Expand Up @@ -621,11 +627,13 @@ impl<'a> ConvertESMExportsForHmr<'a> {
// no setter is added since live bindings are read-only
} else {
// 'abc,'
let key = Expr::init(
E::EString::init(export_symbol_name.unwrap_or(original_name).slice()),
loc,
);
self.export_props.push(G::Property {
key: Some(Expr::init(
E::EString::init(export_symbol_name.unwrap_or(original_name).slice()),
loc,
)),
flags: E::own_key_property_flags(&key),
key: Some(key),
value: Some(id),
..Default::default()
});
Expand Down
142 changes: 72 additions & 70 deletions src/js_printer/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,25 @@ where
b'"'
}

/// A `__proto__` key keeps its parsed form: `{ __proto__: x }` sets the prototype, `{ __proto__ }` is an own property.
fn can_use_shorthand_property(
key: &js_ast::e::String,
name: &[u8],
flags: js_ast::flags::PropertySet,
) -> bool {
key.eql_bytes(name)
&& (!key.eql_comptime(b"__proto__")
|| flags.contains(js_ast::flags::Property::WasShorthand))
}

/// `["__proto__"]: x` is the only own-property form left once the shorthand value was renamed.
fn must_print_proto_key_as_computed(
key: &js_ast::e::String,
flags: js_ast::flags::PropertySet,
) -> bool {
flags.contains(js_ast::flags::Property::WasShorthand) && key.eql_comptime(b"__proto__")
}

#[derive(Clone, Copy)]
pub struct Whitespacer {
pub normal: &'static [u8],
Expand Down Expand Up @@ -4828,6 +4847,26 @@ pub(crate) mod __gated_printer {
}
}

/// The identifier a property value prints as, if it prints as a bare identifier.
fn shorthand_value_name(&mut self, item: &G::Property) -> Option<&'a [u8]> {
match &item.value.as_ref()?.data {
ExprData::EIdentifier(e) => Some(self.name_for_symbol(e.ref_)),
ExprData::EImportIdentifier(e) => {
if self.options.input_files_for_dev_server.is_some() {
return None;
}
// Make sure we're not using a property access instead of an identifier
let ref_ = self.symbols().follow(e.ref_);
let symbol = self.symbols().get_const(ref_)?;
if symbol.namespace_alias.is_some() {
return None;
}
Some(self.name_for_symbol(e.ref_))
}
_ => None,
}
}

pub(crate) fn print_property(&mut self, item_in: &G::Property) {
// `G::Property` isn't `Copy`, so take a borrow and shallow-copy the
// mutable bits we may rewrite (key + flags).
Expand Down Expand Up @@ -4996,88 +5035,51 @@ pub(crate) mod __gated_printer {
self.add_source_mapping(key.loc);
if key_str.is_utf8() {
self.print_space_before_identifier();
let mut allow_shorthand = true;
if !IS_JSON && lexer::is_identifier(key_str.slice8()) {
self.print_identifier(key_str.slice8());
} else {
allow_shorthand = false;
self.print_string_literal_e_string(&key_str, false);
}

// Use a shorthand property if the names are the same
if let Some(val) = &item.value {
match &val.data {
ExprData::EIdentifier(e) => {
if key_str.slice8() == self.name_for_symbol(e.ref_) {
if let Some(initial) = &item.initializer {
self.print_initializer(*initial);
}
if allow_shorthand {
return;
}
}
}
ExprData::EImportIdentifier(e) => 'inner: {
let ref_ = self.symbols().follow(e.ref_);
if self.options.input_files_for_dev_server.is_some() {
break 'inner;
}
if let Some(symbol) = self.symbols().get_const(ref_) {
if symbol.namespace_alias.is_none()
&& key_str.slice8() == self.name_for_symbol(e.ref_)
{
if let Some(initial) = &item.initializer {
self.print_initializer(*initial);
}
if allow_shorthand {
return;
}
}
// Use a shorthand property if the names are the same
if let Some(name) = self.shorthand_value_name(&item) {
if can_use_shorthand_property(&key_str, name, item.flags) {
self.print_identifier(key_str.slice8());
if let Some(initial) = &item.initializer {
self.print_initializer(*initial);
}
return;
}
_ => {}
}

if must_print_proto_key_as_computed(&key_str, item.flags) {
self.print(b"[");
self.print_string_literal_e_string(&key_str, false);
self.print(b"]");
} else {
self.print_identifier(key_str.slice8());
}
} else {
self.print_string_literal_e_string(&key_str, false);
}
} else if !IS_JSON && self.can_print_identifier_utf16(key_str.slice16()) {
self.print_space_before_identifier();
self.print_identifier_utf16(key_str.slice16())
.expect("unreachable");

// Use a shorthand property if the names are the same
if let Some(val) = &item.value {
match &val.data {
ExprData::EIdentifier(e) => {
if item.flags.contains(js_ast::flags::Property::WasShorthand)
|| strings::utf16_eql_string(
key_str.slice16(),
self.name_for_symbol(e.ref_),
)
{
if let Some(initial) = &item.initializer {
self.print_initializer(*initial);
}
return;
}
if let Some(name) = self.shorthand_value_name(&item) {
if can_use_shorthand_property(&key_str, name, item.flags) {
self.print_identifier_utf16(key_str.slice16())
.expect("unreachable");
if let Some(initial) = &item.initializer {
self.print_initializer(*initial);
}
ExprData::EImportIdentifier(e) => {
let ref_ = self.symbols().follow(e.ref_);
if let Some(symbol) = self.symbols().get_const(ref_) {
if symbol.namespace_alias.is_none()
&& strings::utf16_eql_string(
key_str.slice16(),
self.name_for_symbol(e.ref_),
)
{
if let Some(initial) = &item.initializer {
self.print_initializer(*initial);
}
return;
}
}
}
_ => {}
return;
}
}

if must_print_proto_key_as_computed(&key_str, item.flags) {
self.print(b"[");
self.print_string_literal_e_string(&key_str, false);
self.print(b"]");
} else {
self.print_identifier_utf16(key_str.slice16())
.expect("unreachable");
}
} else {
let c = best_quote_char_for_string(key_str.slice16(), false);
self.print(c);
Expand Down
Loading
Loading